Django【执行查询】(一)

官方Django3.2 文档https://docs.djangoproject.com/en/3.2/topics/db/queries/
本文大部分内容参考官方3.2版本文档撰写,仅供学习使用
官方PDF下载链接https://media.readthedocs.org/pdf/django/3.2.x/django.pdf

Django 执行查询(一)

(本文中涉及的到的代码,建议复制粘贴的会以可折叠代码块展示,其他代码块建议手动练习。如果你对Models没有初步认识,欢迎去看看官方文档-Models或者其他文章。)

初步准备

将参考以下模型,它们构成了一个 Weblog 应用程序,文章中所有查询操作都基于该模型。

点击查看代码
from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField()
    number_of_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):
        return self.headline

将修改保存到对象

使用模型类的关键字参数对其进行实例化,然后调用save()以将其保存到数据库中:

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

检索对象

如果你想查看Queryset相关API,可以点击这个链接
为了从数据库中检索对象,通过你的模型类上的Manager(管理器)构造一个QuerySet。过滤器根据给定的参数缩小查询结果的范围。在SQL术语中,一个QuerySet相当于一个SELECT语句,而过滤器是一个限制性子句,如WHERE或LIMIT。
您可以使用模型的 Manager 获得一个 QuerySet。每个模型至少有一个Manager(管理器),默认称为objects。通过模型类直接访问它,如下所示:

>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
    ...
AttributeError: "Manager isn't accessible via Blog instances."

【注意】

管理器(manger)只能通过模型类而不是 模型实例 来访问,以强制区分“表级”操作和“记录级”操作。

Manager(管理器) 是模型的 QuerySet 的主要来源。例如,Blog.objects.all() 返回一个包含数据库中所有 Blog 对象的 QuerySet。

检索所有对象

从表中检索对象的最简单方法是获取所有对象。为此,请在 Manager 上使用 all() 方法:

>>> all_entries = Entry.objects.all()

all() 方法返回了数据库中所有对象的 QuerySet。

使用过滤器检索特定对象

all() 返回的 QuerySet 描述了数据库表中的所有对象。但是,通常您只需要选择完整对象集的一个子集。
要创建这样的子集,您需要优化初始 QuerySet,添加过滤条件。细化 QuerySet 的两种最常见的方法是:

filter(**kwargs)

返回一个新的 QuerySet,其中包含与给定查找参数匹配的对象。

exclude(**kwargs)

返回一个新的 QuerySet,其中包含与给定查找参数匹配的对象。

返回一个新的QuerySet,包含不符合给定查找参数的对象。
查询参数(上述函数定义中的**kwargs)应该采用下面字段查询中描述的格式。
例如,要包含获取 2006 年的博客条目(entries blog)的 QuerySet,像这样使用 filter():

Entry.objects.filter(pub_date__year=2006)
通过默认管理器类也一样:

Entry.objects.all().filter(pub_date__year=2006)

链式过滤器

细化QuerySet 的结果本身还是一个 QuerySet,所以能串联多个细化过程。例子:

 >>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime.date(2005, 1, 30)
... )

这个例子先获取包含数据库所有条目(entry)的 QuerySet,添加一个过滤器,然后排除一些,再进入另一个过滤器。最终的 QuerySet 包含标题以 "What" 开头的,发布日期介于 2005 年 1 月 30 日与今天之间的所有条目。

每个Queryset都是唯一的

每次精炼(细化)一个 QuerySet,你就会获得一个全新的 QuerySet,后者与前者毫无关联。每次精炼都会创建一个单独的、不同的 QuerySet,能被存储,使用和复用。

>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

这三个 QuerySets 是独立的。第一个是基础 QuerySet,包含了所有标题以 "What" 开头的条目。第二个是第一个的子集,带有额外条件,排除了 pub_date 是今天和今天之后的所有记录。第三个是第一个的子集,带有额外条件,只筛选 pub_date 是今天或未来的所有记录。最初的 QuerySet (q1) 不受筛选操作影响。

QuerySet 是惰性的

QuerySet 是惰性的 —— 创建 QuerySet 并不会引发任何数据库活动。你可以将一整天的过滤器都堆积在一起,Django 只会在 QuerySet 被计算时执行查询操作。来瞄一眼这个例子:

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)

虽然这看起来像是三次数据库操作,实际上只在最后一行 (print(q)) 做了一次。一般来说, QuerySet 的结果直到你 “要使用” 时才会从数据库中拿出。当你要用时,才通过数据库计算出 QuerySet。

用 get() 检索单个对象

filter() 总是返回一个 QuerySet,即便只有一个对象满足查询条件 —— 这种情况下,QuerySet 只包含了一个元素。

若你知道只会有一个对象满足查询条件,你可以在 Manager 上使用 get() 方法,它会直接返回这个对象:
>>> one_entry = Entry.objects.get(pk=1)

【注意】

使用切片 [0] 时的 get() 和 filter() 有点不同。如果没有满足查询条件的结果, get() 会抛出一个 DoesNotExist 异常。该异常是执行查询的模型类的一个属性 —— 所有,上述代码中,若没有哪个 Entry 对象的主键是 1,Django 会抛出 Entry.DoesNotExist。

类似了,Django 会在有不止一个记录满足 get() 查询条件时发出警告。这时,Django 会抛出 MultipleObjectsReturned,这同样也是模型类的一个属性。

所以说get()方法的使用中无论不存在或者存在多条都会报错,抛出的异常是可以被合理应用的

其它 QuerySet 方法

大多数情况下,你会在需要从数据库中检索对象时使用 all(), get(), filter() 和 exclude()。然而,这样远远不够;完整的各种 QuerySet 方法请参阅 QuerySet API reference

限制Queryset条目数

总结一下,可以切边,切边后的结果不能再排序or过滤。而且不能使用副索引

利用 Python 的数组切片语法将 QuerySet 切成指定长度。这等价于 SQL 的 LIMIT 和 OFFSET 子句。

例如,这将返回前 5 个对象 (LIMIT 5):

>>> Entry.objects.all()[:5]

这会返回第 6 至第 10 个对象 (OFFSET 5 LIMIT 5):

>>> Entry.objects.all()[5:10]

不支持负索引 (例如 Entry.objects.all()[-1])

一般情况下, QuerySet 的切片返回一个新的 QuerySet —— 其并未执行查询。一个特殊情况是使用了的 Python 切片语法的 “步长”。例如,这将会实际的执行查询命令,为了获取从前 10 个对象中,每隔一个抽取的对象组成的列表:

>>> Entry.objects.all()[:10:2]

由于对 queryset 切片工作方式的模糊性,禁止对其进行进一步的排序或过滤。

要检索 单个 对象而不是一个列表时(例如 SELECT foo FROM bar LIMIT 1),请使用索引,而不是切片。例如,这会返回按标题字母排序后的第一个 Entry:

>>> Entry.objects.order_by('headline')[0]

这大致等价于:

>>> Entry.objects.order_by('headline')[0:1].get()

然而,注意一下,若没有对象满足给定条件,前者会抛出 IndexError,而后者会抛出 DoesNotExist。参考 get() 获取更多细节。

字段查询

字段查询即你如何制定 SQL WHERE 子句。它们以关键字参数的形式传递给 QuerySet 方法 filter(), exclude() 和 get()。

基本的查询关键字参数遵照 field__lookuptype=value。(有个双下划线)。例如:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')

转换为 SQL 语句大致如下:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

查询子句中指定的字段必须是模型的一个字段名。不过也有个例外,在 ForeignKey 中,你可以指定以 _id 为后缀的字段名。这种情况下,value 参数需要包含 foreign 模型的主键的原始值。例子:
(点击此连接 ForeignKey 官方详解(多对一关系))

>>> Entry.objects.filter(blog_id=4)

若你传入了无效的关键字参数,查询函数会抛出 TypeError。

数据库 API 支持两套查询类型;完整参考文档位于 QuerySet API reference 字段查询参考。为了让你了解能干啥,以下是一些常见的查询:

exact

完全匹配。如果提供的比较值是 None,它将被解释为 SQL NULL (详见 isnull)。

一个 "exact" 匹配的例子:

>>> Entry.objects.get(headline__exact="Cat bites dog")
官方翻译:
若你没有提供查询类型 —— 也就说,若关键字参数未包含双下划线 —— 查询类型会被指定为 exact。
google翻译:
如果您不提供查找类型(即,如果您的关键字参数不包含双下划线),则假定查找类型是exact(精确)。
(下图是官方翻译,我是觉得有点问题)
image

iexact

不区分大小写的完全匹配。如果提供的比较值是 None,它将被解释为 SQL NULL (详见 isnull)。

>>> Blog.objects.get(name__iexact="beatles blog")

会匹配标题为 "Beatles Blog", "beatles blog", 甚至 "BeAtlES blOG" 的 Blog。

contains

区分大小写的包含测试。

举例:

Entry.objects.get(headline__contains='Lennon')

SQL 等价于:

SELECT ... WHERE headline LIKE '%Lennon%';

icontains

不区分大小写的包含测试。

举例:

Entry.objects.get(headline__icontains='Lennon')

SQL 等价于:

SELECT ... WHERE headline ILIKE '%Lennon%';

startswith, endswith

区分大小写的开头为;区分大小写的结尾为.
以……开头和以……结尾的查找。当然也有大小写不敏感的版本,名为 istartswith 和 iendswith。

(官方文档中对 field 查询参考 没有再详细介绍,对更多字段查询感兴趣可以查看我google翻译的文章,点击这段话即可)

跨关系查询

Django 提供了一种强大而直观的方式来“追踪”查询中的关系,在幕后自动为你处理 SQL JOIN 关系。为了跨越关系,跨模型使用关联字段名,字段名由双下划线分割,直到拿到想要的字段。

本例检索出所有的 Entry 对象,其 Blog 的 name 为 'Beatles Blog' :

>>> Entry.objects.filter(blog__name='Beatles Blog')

这个跨度可以是你想要的深度。

它也可以向后工作。虽然它可以被定制,但默认情况下,你在查询中使用模型的小写名称来引用 "反向 "关系。
本例检索的所有 Blog 对象均拥有少一个 标题 含有 'Lennon' 的条目:

>>> Blog.objects.filter(entry__headline__contains='Lennon')

posted @ 2022-07-05 11:25  libai1024  阅读(166)  评论(0编辑  收藏  举报