进行查询 — Django 文档

来自菜鸟教程
Django/docs/3.2.x/topics/db/queries
跳转至:导航、​搜索

进行查询

一旦您创建了 数据模型 ,Django 会自动为您提供一个数据库抽象 API,让您可以创建、检索、更新和删除对象。 本文档解释了如何使用此 API。 有关所有各种模型查找选项的完整详细信息,请参阅 数据模型参考

在本指南(以及参考资料)中,我们将参考以下模型,它们构成了一个 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

创建对象

为了在 Python 对象中表示数据库表数据,Django 使用了一个直观的系统:模型类表示数据库表,该类的实例表示数据库表中的特定记录。

要创建对象,请使用模型类的关键字参数将其实例化,然后调用 save() 将其保存到数据库中。

假设模型存在于文件 mysite/blog/models.py 中,这是一个示例:

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

这会在幕后执行 INSERT SQL 语句。 在您明确调用 save() 之前,Django 不会访问数据库。

save() 方法没有返回值。

也可以看看

save() 采用了许多此处未描述的高级选项。 有关完整的详细信息,请参阅 save() 的文档。

要在单个步骤中创建和保存对象,请使用 create() 方法。


保存对对象的更改

要保存对数据库中已有对象的更改,请使用 save()

给定一个已经保存到数据库的 Blog 实例 b5,这个例子改变了它的名字并更新了它在数据库中的记录:

>>> b5.name = 'New name'
>>> b5.save()

这会在幕后执行 UPDATE SQL 语句。 在您明确调用 save() 之前,Django 不会访问数据库。

保存 ForeignKey 和 ManyToManyField 字段

更新 ForeignKey 字段的工作方式与保存普通字段的方式完全相同——将正确类型的对象分配给相关字段。 此示例更新 Entry 实例 entryblog 属性,假设 EntryBlog 的适当实例已保存到数据库中(所以我们可以在下面检索它们):

>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

更新 ManyToManyField 的工作方式略有不同 - 在字段上使用 add() 方法向关系添加记录。 本示例将 Author 实例 joe 添加到 entry 对象:

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

要将多条记录一次性添加到 ManyToManyField,请在对 add() 的调用中包含多个参数,如下所示:

>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)

如果您尝试分配或添加错误类型的对象,Django 会抱怨。


检索对象

要从数据库中检索对象,请通过模型类上的 Manager 构造 QuerySet

QuerySet 表示数据库中的对象集合。 它可以有零个、一个或多个 过滤器 。 过滤器根据给定的参数缩小查询结果的范围。 在 SQL 术语中,QuerySet 等同于 SELECT 语句,过滤器是一个限制子句,例如 WHERELIMIT

您可以使用模型的 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."

笔记

Managers 只能通过模型类访问,而不是从模型实例访问,以强制分离“表级”操作和“记录级”操作。


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

检索所有对象

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

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

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


使用过滤器检索特定对象

all() 返回的 QuerySet 描述了数据库表中的所有对象。 但是,通常您只需要选择完整对象集的一个子集。

要创建这样的子集,您需要优化初始 QuerySet,添加过滤条件。 优化 QuerySet 的两种最常见方法是:

filter(**kwargs)
返回一个新的 QuerySet 包含与给定查找参数匹配的对象。
exclude(**kwargs)
返回一个新的 QuerySet,其中包含 不匹配给定查找参数的对象。

查找参数(上述函数定义中的 **kwargs)应采用以下 字段查找 中描述的格式。

例如,要获取 2006 年博客条目的 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)
... )

这需要数据库中所有条目的初始 QuerySet,添加一个过滤器,然后是一个排除,然后是另一个过滤器。 最终结果是一个 QuerySet,其中包含所有标题以“What”开头的条目,这些条目在 2005 年 1 月 30 日和当天之间发布。


过滤后的 QuerySet 是唯一的

每次细化 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很懒

QuerySets 是懒惰的——创建 QuerySet 的行为不涉及任何数据库活动。 您可以整天将过滤器堆叠在一起,并且在 QuerySet评估 之前,Django 不会实际运行查询。 看看这个例子:

>>> 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)

您可以将任何查询表达式与 get() 一起使用,就像使用 filter() 一样 - 再次参见下面的 字段查找

请注意,使用 get() 和使用带有 [0] 切片的 filter() 之间存在差异。 如果没有与查询匹配的结果,get() 将引发 DoesNotExist 异常。 这个异常是正在执行查询的模型类的一个属性——所以在上面的代码中,如果没有主键为 1 的 Entry 对象,Django 将引发 Entry.DoesNotExist .

类似地,如果超过一项与 get() 查询匹配,Django 也会抱怨。 在这种情况下,它将引发 MultipleObjectsReturned,这也是模型类本身的一个属性。


其他 QuerySet 方法

大多数情况下,您会在需要时使用 all()get()filter()exclude()从数据库中查找对象。 然而,这远非全部。 有关所有各种 QuerySet 方法的完整列表,请参阅 QuerySet API 参考


限制 QuerySets

使用 Python 的数组切片语法的子集将 QuerySet 限制为一定数量的结果。 这相当于 SQL 的 LIMITOFFSET 子句。

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

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

这将返回第六到第十个对象 (OFFSET 5 LIMIT 5):

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

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

通常,切片 QuerySet 会返回一个新的 QuerySet——它不会评估查询。 一个例外是,如果您使用 Python 切片语法的“step”参数。 例如,这实际上会执行查询以返回前 10 个每 对象的列表:

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

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

要检索 单个 对象而不是列表(例如 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';

这怎么可能

Python 能够定义接受任意名称-值参数的函数,其名称和值在运行时进行评估。 更多信息参见Python官方教程中的tut-keywordargs


查找中指定的字段必须是模型字段的名称。 但有一个例外,在 ForeignKey 的情况下,您可以指定后缀为 _id 的字段名称。 在这种情况下, value 参数应该包含外部模型主键的原始值。 例如:

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

如果传递无效的关键字参数,查找函数将引发 TypeError

数据库 API 支持大约二打查找类型; 完整的参考可以在 字段查找参考 中找到。 为了让您了解可用的内容,以下是您可能会使用的一些更常见的查找:

:查找:`精确`

“精确”匹配。 例如:

>>> Entry.objects.get(headline__exact="Cat bites dog")

将按照以下方式生成 SQL:

SELECT ... WHERE headline = 'Cat bites dog';

如果您不提供查找类型——也就是说,如果您的关键字参数不包含双下划线——则假定查找类型为 exact

例如,以下两个语句是等效的:

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)         # __exact is implied

这是为了方便起见,因为 exact 查找是常见情况。

:lookup:`iexact`

不区分大小写的匹配。 所以,查询:

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

将匹配标题为 "Beatles Blog""beatles blog" 甚至 "BeAtlES blOG"Blog

:查找:`包含`

区分大小写的包含测试。 例如:

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

大致翻译成这个 SQL:

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

请注意,这将匹配标题 'Today Lennon honored' 但不匹配 'today lennon honored'

还有一个不区分大小写的版本,:lookup:`icontains`

:lookup:`startswith`, :lookup:`endswith`

分别开始于和结束于搜索。 还有一些不区分大小写的版本,称为 :lookup:`istartswith`:lookup:`iendswith`

同样,这只会划伤表面。 完整的参考可以在 字段查找参考 中找到。


跨越关系的查找

Django 提供了一种强大而直观的方式来“跟踪”查找中的关系,在幕后自动为您处理 SQL JOIN。 要跨越关系,请使用跨模型的相关字段的字段名称,用双下划线分隔,直到到达所需的字段。

此示例检索所有具有 Blogname'Beatles Blog'Entry 对象:

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

这个跨越可以像你想要的那样深。

它也向后工作。 虽然它 可以自定义 ,但默认情况下,您使用模型的小写名称在查找中引用“反向”关系。

此示例检索所有 Blog 对象,这些对象至少有一个 Entry,其 headline 包含 'Lennon'

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

如果你在多个关系之间进行过滤并且其中一个中间模型没有满足过滤条件的值,Django 会将其视为有空(所有值都是 NULL),但有效,对象那里。 所有这一切都意味着不会引发任何错误。 例如,在这个过滤器中:

Blog.objects.filter(entry__authors__name='Lennon')

(如果有相关的 Author 模型),如果没有与条目关联的 author,则将被视为没有附加 name,而不是由于缺少 author 而引发错误。 通常这正是您希望发生的事情。 唯一可能令人困惑的情况是,如果您使用 :lookup:`isnull`。 因此:

Blog.objects.filter(entry__authors__name__isnull=True)

将返回在 author 上有空 nameBlog 对象,以及在 entry 上有空 author 的对象。 如果你不想要那些后面的对象,你可以写:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

跨越多值关系

当您根据 ManyToManyField 或反向 ForeignKey 过滤对象时,您可能会对两种不同类型的过滤器感兴趣。 考虑 Blog/Entry 关系(BlogEntry 是一对多关系)。 我们可能有兴趣查找标题中包含 “Lennon” 且发表于 2008 年的条目的博客。 或者,我们可能想要查找标题中包含 “Lennon” 条目以及 2008 年发布的条目的博客。 由于有多个条目与单个 Blog 相关联,因此这两种查询都是可能的,并且在某些情况下是有意义的。

ManyToManyField 也会出现相同类型的情况。 例如,如果 Entry 有一个名为 tagsManyToManyField,我们可能想要找到链接到名为 “music”的标签的条目]“bands” 或者我们可能想要一个包含名称为 “music” 和状态为 “public” 的标签的条目。

为了处理这两种情况,Django 有处理 filter() 调用的一致方式。 单个 filter() 调用中的所有内容都会同时应用以过滤出符合所有这些要求的项目。 连续的 filter() 调用进一步限制了对象集,但对于多值关系,它们适用于链接到主模型的任何对象,不一定是那些由较早的 过滤器选择的对象() 调用。

这可能听起来有点令人困惑,所以希望有一个例子可以澄清。 要选择标题中同时包含 “Lennon” 条目且发布于 2008 年的所有博客(满足两个条件的同一条目),我们将编写:

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

要选择标题中包含 “Lennon” 的条目以及 2008 年发布的条目的所有博客,我们将编写:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

假设只有一个博客同时包含 “Lennon” 和 2008 年的条目,但 2008 年的条目都没有包含 “Lennon”。 第一个查询不会返回任何博客,但第二个查询会返回那个博客。

在第二个示例中,第一个过滤器将查询集限制为链接到标题中带有 “Lennon” 的条目的所有博客。 第二个过滤器将博客集 进一步 限制为那些也链接到 2008 年发布的条目的博客。 第二个过滤器选择的条目可能与第一个过滤器中的条目相同,也可能不同。 我们使用每个过滤器语句过滤 Blog 项,而不是 Entry 项。

笔记

filter() 对于跨越多值关系的查询的行为,如上所述,对于 exclude() 没有等效实现。 相反,单个 exclude() 调用中的条件不一定指向同一个项目。

例如,以下查询将排除包含 both 条目的博客,其中 “Lennon” 在标题 条目发布于 2008 年:

Blog.objects.exclude(
    entry__headline__contains='Lennon',
    entry__pub_date__year=2008,
)

但是,与使用 filter() 时的行为不同,这不会基于满足这两个条件的条目来限制博客。 为了做到这一点,即 要选择所有不包含 2008 年发布的 “Lennon” 发布的条目的博客,您需要进行两个查询:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains='Lennon',
        pub_date__year=2008,
    ),
)

过滤器可以引用模型上的字段

在到目前为止给出的示例中,我们构建了过滤器,将模型字段的值与常量进行比较。 但是,如果您想将模型字段的值与同一模型上的另一个字段的值进行比较,该怎么办?

Django 提供了 F 表达式 来允许这样的比较。 F() 的实例充当对查询中模型字段的引用。 然后可以在查询过滤器中使用这些引用来比较同一模型实例上两个不同字段的值。

例如,要查找评论多于 pingback 的所有博客条目的列表,我们构造一个 F() 对象来引用 pingback 计数,并在查询中使用该 F() 对象:

>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))

Django 支持对 F() 对象使用加法、减法、乘法、除法、取模和幂运算,包括常量和其他 F() 对象。 要查找评论数超过 pingback 的 的所有博客条目,我们修改查询:

>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)

要查找条目评分小于 pingback 计数和评论计数之和的所有条目,我们将发出查询:

>>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks'))

您还可以使用双下划线表示法来跨越 F() 对象中的关系。 带有双下划线的 F() 对象将引入访问相关对象所需的任何连接。 例如,要检索作者姓名与博客名称相同的所有条目,我们可以发出查询:

>>> Entry.objects.filter(authors__name=F('blog__name'))

对于日期和日期/时间字段,您可以添加或减去 timedelta 对象。 以下将返回在发布后超过 3 天修改的所有条目:

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

F() 对象支持 .bitand().bitor().bitxor().bitrightshift().bitleftshift() 的按位运算。 例如:

>>> F('somefield').bitand(16)

甲骨文

Oracle 不支持按位异或运算。


3.1 版更改:添加了 .bitxor() 的支持。


表达式可以引用转换

3.2 版中的新功能。


Django 支持在表达式中使用转换。

例如,要查找与上次修改在同一年发布的所有 Entry 对象:

>>> Entry.objects.filter(pub_date__year=F('mod_date__year'))

要查找条目发布的最早年份,我们可以发出查询:

>>> Entry.objects.aggregate(first_published_year=Min('pub_date__year'))

此示例查找评分最高的条目的值以及每年所有条目的评论总数:

>>> Entry.objects.values('pub_date__year').annotate(
...     top_rating=Subquery(
...         Entry.objects.filter(
...             pub_date__year=OuterRef('pub_date__year'),
...         ).order_by('-rating').values('rating')[:1]
...     ),
...     total_comments=Sum('number_of_comments'),
... )

pk 查找快捷方式

为方便起见,Django 提供了一个 pk 查找快捷方式,它代表“主键”。

在示例 Blog 模型中,主键是 id 字段,因此这三个语句是等效的:

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact

pk 的使用不限于 __exact 查询 - 任何查询词都可以与 pk 组合以对模型的主键执行查询:

# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7])

# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)

pk 查找也适用于连接。 例如,这三个语句是等效的:

>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3)        # __exact is implied
>>> Entry.objects.filter(blog__pk=3)        # __pk implies __id__exact

在 LIKE 语句中转义百分号和下划线

等同于 LIKE SQL 语句的字段查找(iexactcontainsicontainsstartswithistartswithendswithiendswith) 将自动转义 LIKE 语句中使用的两个特殊字符 - 百分号和下划线。 (在 LIKE 语句中,百分号表示多字符通配符,下划线表示单字符通配符。)

这意味着事情应该直观地工作,所以抽象不会泄漏。 例如,要检索包含百分号的所有条目,请将百分号用作任何其他字符:

>>> Entry.objects.filter(headline__contains='%')

Django 会为您处理报价; 生成的 SQL 将如下所示:

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

下划线也是如此。 百分号和下划线都为您透明处理。


缓存和 QuerySets

每个 QuerySet 都包含一个缓存以最小化数据库访问。 了解它的工作原理将使您能够编写最有效的代码。

在新创建的 QuerySet 中,缓存为空。 第一次计算 QuerySet 时——因此,发生数据库查询——Django 将查询结果保存在 QuerySet 的缓存中并返回已明确请求的结果(例如,下一个元素,如果 QuerySet 正在迭代)。 QuerySet 的后续评估重用缓存的结果。

记住这种缓存行为,因为如果你没有正确使用你的 QuerySets,它可能会咬你。 例如,以下将创建两个 QuerySet ,评估它们,然后将它们丢弃:

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

这意味着相同的数据库查询将执行两次,有效地使您的数据库负载加倍。 此外,这两个列表可能不包含相同的数据库记录,因为 Entry 可能已在两个请求之间的瞬间添加或删除。

为了避免这个问题,保存 QuerySet 并重用它:

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

当 QuerySet 未缓存时

查询集并不总是缓存它们的结果。 当仅评估查询集的 part 时,会检查缓存,但如果未填充,则不会缓存后续查询返回的项目。 具体来说,这意味着 使用数组切片或索引限制查询集 不会填充缓存。

例如,重复获取查询集对象中的某个索引,每次都会查询数据库:

>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again

但是,如果已经评估了整个查询集,则会改为检查缓存:

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache

以下是其他操作的一些示例,这些操作将导致评估整个查询集并因此填充缓存:

>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

笔记

简单地打印查询集不会填充缓存。 这是因为对 __repr__() 的调用只返回整个查询集的一部分。


查询JSONField

JSONField 中的查找实现不同,主要是由于存在键转换。 为了演示,我们将使用以下示例模型:

from django.db import models

class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = models.JSONField(null=True)

    def __str__(self):
        return self.name

None的存储和查询

与其他字段一样,将 None 存储为字段的值会将其存储为 SQL NULL。 虽然不推荐,但可以使用 Value('null') 来存储 JSON 标量 null 而不是 SQL NULL

无论存储哪个值,从数据库中检索时,JSON 标量 null 的 Python 表示与 SQL NULL 相同,即 None。 因此,很难区分它们。

这仅适用于 None 作为字段的顶级值。 如果 None 位于 listdict 内,它将始终被解释为 JSON null

查询时,None 值将始终解释为 JSON null。 要查询 SQL NULL,请使用 :lookup:`isnull`

>>> Dog.objects.create(name='Max', data=None)  # SQL NULL.
<Dog: Max>
>>> Dog.objects.create(name='Archie', data=Value('null'))  # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value('null'))
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>
>>> Dog.objects.filter(data__isnull=False)
<QuerySet [<Dog: Archie>]>

除非您确定要使用 SQL NULL 值,请考虑设置 null=False 并为空值提供合适的默认值,例如 default=dict

笔记

存储 JSON 标量 null 不违反 null=False


键、索引和路径转换

要基于给定的字典键进行查询,请使用该键作为查找名称:

>>> Dog.objects.create(name='Rufus', data={
...     'breed': 'labrador',
...     'owner': {
...         'name': 'Bob',
...         'other_pets': [{
...             'name': 'Fishy',
...         }],
...     },
... })
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>

多个键可以链接在一起以形成路径查找:

>>> Dog.objects.filter(data__owner__name='Bob')
<QuerySet [<Dog: Rufus>]>

如果键是整数,它将被解释为数组中的索引转换:

>>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
<QuerySet [<Dog: Rufus>]>

如果您希望查询的键与另一个查找的名称发生冲突,请使用 :lookup:`包含 ` 查找代替。

要查询丢失的键,请使用 isnull 查找:

>>> Dog.objects.create(name='Shep', data={'breed': 'collie'})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>

笔记

由于键路径查询的工作方式,exclude()filter() 不能保证产生详尽的集合。 如果要包含没有路径的对象,请添加 isnull 查找。


警告

由于任何字符串都可能是 JSON 对象中的键,因此除下面列出的那些之外的任何查找都将被解释为键查找。 没有错误被提出。 输入错误时要格外小心,并始终检查您的查询是否按预期工作。


MariaDB 和 Oracle 用户

在键、索引或路径转换上使用 order_by() 将使用值的字符串表示对对象进行排序。 这是因为 MariaDB 和 Oracle 数据库不提供将 JSON 值转换为其等效 SQL 值的函数。


甲骨文用户

在 Oracle 数据库上,在 exclude() 查询中使用 None 作为查找值将返回在给定路径中没有 null 作为值的对象,包括对象没有路径。 在其他数据库后端,查询将返回具有路径且值不是 null 的对象。


PostgreSQL 用户

在 PostgreSQL 上,如果只使用一个键或索引,则使用 SQL 运算符 ->。 如果使用多个运算符,则使用 #> 运算符。


包含和密钥查找

contains

:lookup:`contains` 查找在 JSONField 上被覆盖。 返回的对象是那些给定的 dict 键值对都包含在字段的顶级中的对象。 例如:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.create(name='Fred', data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={'owner': 'Bob'})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={'breed': 'collie'})
<QuerySet [<Dog: Meg>]>

甲骨文和 SQLite

contains 在 Oracle 和 SQLite 上不受支持。


contained_by

这是相反的 :lookup:`包含 ` 查找 - 返回的对象将是那些对象上的键值对是传递值中键值对的子集的对象。 例如:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.create(name='Fred', data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
<QuerySet [<Dog: Fred>]>

甲骨文和 SQLite

contained_by 在 Oracle 和 SQLite 上不受支持。


has_key

返回给定键位于数据顶层的对象。 例如:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_key='owner')
<QuerySet [<Dog: Meg>]>

has_keys

返回所有给定键都位于数据顶层的对象。 例如:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
<QuerySet [<Dog: Meg>]>

has_any_keys

返回任何给定键位于数据顶层的对象。 例如:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

Q 对象的复杂查找

关键字参数查询 - 在 filter() 等中。 – 被“AND”在一起。 如果您需要执行更复杂的查询(例如,使用 OR 语句的查询),您可以使用 Q 对象

Q 对象 (django.db.models.Q) 是用于封装关键字参数集合的对象。 这些关键字参数在上面的“字段查找”中指定。

例如,这个 Q 对象封装了单个 LIKE 查询:

from django.db.models import Q
Q(question__startswith='What')

Q 对象可以使用 &| 运算符进行组合。 当一个运算符用于两个 Q 对象时,它会产生一个新的 Q 对象。

例如,此语句生成一个 Q 对象,表示两个 "question__startswith" 查询的“或”:

Q(question__startswith='Who') | Q(question__startswith='What')

这等效于以下 SQL WHERE 子句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

您可以通过将 Q 对象与 &| 运算符组合并使用括号分组来组合任意复杂的语句。 此外,Q 对象可以使用 ~ 运算符进行否定,允许组合查找,将普通查询和否定 (NOT) 查询结合起来:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每个采用关键字参数的查找函数(例如 filter(), exclude(), get()) 也可以传递一个或多个 Q 对象作为位置(未命名) ) 论点。 如果您向查找函数提供多个 Q 对象参数,则这些参数将被“与”在一起。 例如:

Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

... 大致翻译成 SQL:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

查找函数可以混合使用 Q 对象和关键字参数。 提供给查找函数的所有参数(无论是关键字参数还是 Q 对象)都被“AND”在一起。 但是,如果提供了 Q 对象,则它必须在任何关键字参数的定义之前。 例如:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who',
)

... 将是一个有效的查询,相当于前面的例子; 但:

# INVALID QUERY
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

……无效。

也可以看看

:source:`OR 查找示例 ` 在 Django 的单元测试中显示了一些可能的用途Q .


比较对象

要比较两个模型实例,请使用标准 Python 比较运算符,即双等号:==。 在幕后,它比较了两个模型的主键值。

使用上面的 Entry 示例,以下两个语句是等效的:

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

如果模型的主键不是 id,没问题。 比较将始终使用主键,无论它叫什么。 例如,如果模型的主键字段称为 name,则这两个语句是等效的:

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

删除对象

删除方法方便地命名为 delete()。 此方法立即删除对象并返回删除的对象数和包含每个对象类型删除数的字典。 例子:

>>> e.delete()
(1, {'weblog.Entry': 1})

您还可以批量删除对象。 每个 QuerySet 都有一个 delete() 方法,该方法删除该 QuerySet 的所有成员。

例如,这将删除 pub_date 年为 2005 的所有 Entry 对象:

>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

请记住,只要有可能,这将完全在 SQL 中执行,因此在此过程中不一定会调用各个对象实例的 delete() 方法。 如果您在模型类上提供了自定义 delete() 方法并希望确保它被调用,您将需要“手动”删除该模型的实例(例如,通过迭代 QuerySet 并分别在每个对象上调用 delete()),而不是使用 QuerySet 的批量 delete() 方法。

当 Django 删除一个对象时,默认情况下它会模拟 SQL 约束 ON DELETE CASCADE 的行为——换句话说,任何具有指向要删除的对象的外键的对象都将被删除。 例如:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

此级联行为可通过 ForeignKeyon_delete 参数进行自定义。

请注意, delete() 是唯一未在 Manager 本身上公开的 QuerySet 方法。 这是一种安全机制,可防止您意外请求 Entry.objects.delete(),并删除 all 条目。 如果您 do 想要删除所有对象,那么您必须明确请求一个完整的查询集:

Entry.objects.all().delete()

复制模型实例

尽管没有用于复制模型实例的内置方法,但可以轻松地创建新实例并复制所有字段的值。 在最简单的情况下,您可以将 pk 设置为 None 并将 _state.adding 设置为 True。 使用我们的博客示例:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog._state.adding = True
blog.save() # blog.pk == 2

如果您使用继承,事情会变得更加复杂。 考虑 Blog 的子类:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

由于继承的工作原理,您必须将 pkid 都设置为 None,将 _state.adding 设置为 True

django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
django_blog.save() # django_blog.pk == 4

此过程不会复制不属于模型数据库表的关系。 例如,EntryManyToManyFieldAuthor。 复制条目后,您必须为新条目设置多对多关系:

entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
entry.save()
entry.authors.set(old_authors)

对于 OneToOneField,您必须复制相关对象并将其分配给新对象的字段,以避免违反一对一唯一约束。 例如,假设 entry 已经如上复制:

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail._state.adding = True
detail.entry = entry
detail.save()

一次更新多个对象

有时,您希望为 QuerySet 中的所有对象将字段设置为特定值。 您可以使用 update() 方法执行此操作。 例如:

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

使用此方法只能设置非关系字段和 ForeignKey 字段。 要更新非关系字段,请将新值作为常量提供。 要更新 ForeignKey 字段,请将新值设置为要指向的新模型实例。 例如:

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)

update() 方法立即应用并返回查询匹配的行数(如果某些行已经具有新值,则可能不等于更新的行数)。 QuerySet 更新的唯一限制是它只能访问一个数据库表:模型的主表。 您可以根据相关字段进行过滤,但您只能更新模型主表中的列。 例子:

>>> b = Blog.objects.get(pk=1)

# Update all the headlines belonging to this Blog.
>>> Entry.objects.filter(blog=b).update(headline='Everything is the same')

请注意,update() 方法直接转换为 SQL 语句。 这是直接更新的批量操作。 它不会在您的模型上运行任何 save() 方法,也不会发出 pre_savepost_save 信号(这是调用 save() 的结果) ),或使用 auto_now 字段选项。 如果您想将每个项目保存在 QuerySet 中并确保在每个实例上调用 save() 方法,您不需要任何特殊函数来处理它。 遍历它们并调用 save()

for item in my_queryset:
    item.save()

调用 update 还可以使用 F 表达式 根据模型中另一个字段的值更新一个字段。 这对于根据当前值递增计数器特别有用。 例如,要增加博客中每个条目的 pingback 计数:

>>> Entry.objects.all().update(number_of_pingbacks=F('number_of_pingbacks') + 1)

但是,与 filter 和 exclude 子句中的 F() 对象不同,在更新中使用 F() 对象时不能引入连接——您只能引用正在更新的模型的本地字段。 如果您尝试使用 F() 对象引入连接,则会引发 FieldError

# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))

回退到原始 SQL

如果您发现自己需要编写一个 Django 的数据库映射器无法处理的过于复杂的 SQL 查询,您可以退回到手动编写 SQL。 Django 有几个用于编写原始 SQL 查询的选项; 请参阅 执行原始 SQL 查询

最后,重要的是要注意 Django 数据库层只是数据库的接口。 您可以通过其他工具、编程语言或数据库框架访问您的数据库; 您的数据库没有任何特定于 Django 的内容。