QuerySet API 参考

该文档描述了 QuerySet API 的细节。它是建立在 模型数据库查询 指南的材料基础上的,因此,在阅读这篇文档之前,你可能需要阅读和理解这些文档。

在整个参考资料中,我们将使用在 数据库查询指南 中提出的 示例博客模型

什么时候 QuerySet 被执行

QuerySet 本身可以被构造,过滤,切片,或者复制赋值等,是无需访问数据库的。只有在你需要从数据库取出数据或者,向数据库存入数据时才需要访问数据库。

你可以用以下方式执行一个 QuerySet

  • 迭代。 一个 QuerySet 是可迭代的,当你第一次迭代它时,它就会执行其数据库查询。例如,这将打印数据库中所有条目的标题:

    for e in Entry.objects.all():
        print(e.headline)
    

    注意:如果你想做的只是确定至少一个结果是否存在,不要使用这个。使用 exists() 会更有效。

  • 切片。 正如在 限制 QuerySet 条目数 中所解释的那样,QuerySet 可以使用 Python 的数组切片语法进行切片。切片一个未执行的 QuerySet 通常会返回另一个未执行的 QuerySet,但如果使用切片语法的 step 参数,Django 会执行数据库查询,并返回一个列表。切片一个已经执行过的 QuerySet 也会返回一个列表。

    还要注意的是,即使对一个未执行的 QuerySet 进行切片,返回另一个未执行的 QuerySet,也不允许进一步修改它(例如,添加更多的过滤器,或修改排序),因为这不能很好地翻译成 SQL,也没有明确的含义。

  • Pickle 序列化/缓存。 关于 pickling QuerySets 时涉及的细节,请参见下一节。就本节而言,重要的是,结果是从数据库中读取的。

  • repr()。 当你调用 repr() 时,所在 QuerySet 会被执行。这是为了方便 Python 交互式解释器,所以当你交互式使用 API 时,可以立即看到你的结果。

  • len()。 当你调用 len() 时,会执行 QuerySet。正如你所期望的,这将返回结果列表的长度。

    注意:如果你只需要确定集合中的记录数(而不需要实际的对象),那么使用 SQL 的 SELECT COUNT(*) 在数据库层面上处理计数会更有效率。Django 提供了一个 count() 方法正是为了这个原因。

  • list()。 通过调用 list() 强制执行 QuerySet。例如:

    entry_list = list(Entry.objects.all())
    
  • bool()。 在布尔语境中测试 QuerySet,如使用 bool()orandif 语句,将导致查询被执行。如果至少有一个结果,则 QuerySetTrue,否则为 False。例如:

    if Entry.objects.filter(headline="Test"):
       print("There is at least one Entry with the headline Test")
    

    注意:如果你只想确定至少一个结果是否存在(而不需要实际的对象),使用 exences() 更高效。

Pickle 序列化 QuerySet

如果你 pickle 序列化一个 QuerySet,这将迫使所有结果在 pickle 序列化之前加载到内存中。Pickle 序列化通常被用作缓存的前奏,当缓存的查询集被重新加载时,你希望结果已经存在并可以使用(从数据库读取可能需要一些时间,这就违背了缓存的目的)。这意味着,当你取消缓存一个 QuerySet 时,它包含的是它被缓存时的结果,而不是当前在数据库中的结果。

如果你只想提取必要的信息,以便以后从数据库中重新创建 QuerySet,则提取 QuerySetquery 属性。然后,你可以使用这样的代码重新创建原始的 QuerySet (不加载任何结果):

>>> import pickle
>>> query = pickle.loads(s)     # Assuming 's' is the pickled string.
>>> qs = MyModel.objects.all()
>>> qs.query = query            # Restore the original 'query'.

query 属性是一个不透明的对象。它代表了查询结构的内部结构,不是公共 API 的一部分。但是,如这里所述,可以安全地(并完全支持)pickle 序列化和反序列化该属性的内容。

QuerySet.values_list() 的限制

如果你使用 pickle 序列化的 query 属性重新创建 QuerySet.values_list(),它将被转换为 QuerySet.values()

>>> import pickle
>>> qs = Blog.objects.values_list('id', 'name')
>>> qs
<QuerySet [(1, 'Beatles Blog')]>
>>> reloaded_qs = Blog.objects.all()
>>> reloaded_qs.query = pickle.loads(pickle.dumps(qs.query))
>>> reloaded_qs
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

你不能在不同版本之间共享 pickle

QuerySets 的 pickle 只对生成它们的 Django 版本有效。如果你用 Django N 版本生成了一个 pickle,就不能保证这个 pickle 在 Django N+1 版本中可以被读取。Pickle 不应该作为长期存档策略的一部分。

由于 pickle 兼容性错误可能很难诊断,比如静默损坏对象,所以当你试图在与序列化 pickle 时不同版本的 Django 中反序列化查询集时,会发出 RuntimeWarning

QuerySet API

这里是 QuerySet 的正式声明:

class QuerySet(model=None, query=None, using=None, hints=None)

通常当你与 QuerySet 交互时,你会通过 链式过滤器 来使用它。为了实现这一目的,大多数 QuerySet 方法都会返回新的查询集。这些方法将在本节后面详细介绍。

QuerySet 类具有两个可用于自省的公开属性:

ordered

True 如果 QuerySet 是有序的——即有一个 order_by() 子句或模型上的默认排序。否则为 False

db

如果现在执行这个查询,将使用的数据库。

备注

QuerySetquery 参数的存在是为了让专门的查询子类能够重建内部查询状态。该参数的值是该查询状态的不透明表示,不是公共 API 的一部分。

返回新 QuerySet 的方法

Django 提供了一系列的 QuerySet 细化方法,这些方法可以修改 QuerySet 返回的结果类型或其 SQL 查询的执行方式。

filter()

filter(*args, **kwargs)

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

查询参数(**kwargs)的格式应在下文 Field lookups 中描述。多个参数通过底层 SQL 语句中的 AND 连接。

如果你需要执行更复杂的查询(例如,带有 OR 语句的查询),你可以使用 Q 对象*args)。

exclude()

exclude(*args, **kwargs)

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

查询参数(**kwargs)的格式应在下文 Field lookups 中描述。多个参数通过底层 SQL 语句中的 AND 连接,整个过程用 NOT() 括起来。

这个例子排除了所有 pub_date 晚于 2005-1-3 且 headline 为“Hello”的条目:

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')

用 SQL 术语来说,它的值是:

SELECT ...
WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')

这个例子不包括所有 pub_date 晚于 2005-1-3 或 headline 为“Hello”的条目:

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')

用 SQL 术语来说,它的值是:

SELECT ...
WHERE NOT pub_date > '2005-1-3'
AND NOT headline = 'Hello'

请注意,第二个例子的限制性更强。

如果你需要执行更复杂的查询(例如,带有 OR 语句的查询),你可以使用 Q 对象*args)。

annotate()

annotate(*args, **kwargs)

用所提供的 查询表达式 列表对 QuerySet 中的每个对象进行注解。表达式可以是一个简单的值,也可以是对模型(或任何相关模型)字段的引用,或者是对与 QuerySet 中的对象相关的对象进行计算的聚合表达式(平均数、总和等)。

annotate() 的每个参数都是一个注解,将被添加到返回的 QuerySet 中的每个对象。

Django 提供的聚合函数在下面的 聚合函数 中介绍。

使用关键字参数指定的注解将使用关键字作为注解的别名。匿名参数将根据聚合函数的名称和被聚合的模型字段为其生成一个别名。只有引用单个字段的聚合表达式才能成为匿名参数。其他一切都必须是关键字参数。

例如,如果你在操纵一个博客列表,你可能想确定每个博客中已经有多少条记录:

>>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count('entry'))
# The name of the first blog
>>> q[0].name
'Blogasaurus'
# The number of entries on the first blog
>>> q[0].entry__count
42

Blog 模型本身并没有定义 entry__count 属性,但是通过使用关键字参数来指定聚合函数,可以控制注解的名称:

>>> q = Blog.objects.annotate(number_of_entries=Count('entry'))
# The number of entries on the first blog, using the name provided
>>> q[0].number_of_entries
42

关于聚合的深入讨论,见 关于聚合的专题指南

alias()

alias(*args, **kwargs)
New in Django 3.2.

annotate() 相同,但不是注解中的 QuerySet 对象,而是保存表达式,以便以后与其他 QuerySet 方法重复使用。当不需要表达式本身的结果,但用于过滤、排序或作为复杂表达式的一部分时,这很有用。不查找未使用的值可以从数据库中移除多余的工作,这应该会带来更好的性能。

例如,如果你想找到有 5 个条目以上的博客,但对确切的条数不感兴趣,你可以这样做:

>>> from django.db.models import Count
>>> blogs = Blog.objects.alias(entries=Count('entry')).filter(entries__gt=5)

alias() 可以与 annotate()exclude()filter()order_by()update() 一起使用。要将别名表达式与其他方法(例如 aggregate())一起使用,你必须将其提升为注解:

Blog.objects.alias(entries=Count('entry')).annotate(
    entries=F('entries'),
).aggregate(Sum('entries'))

filter()order_by() 可以直接接受表达式,但表达式的构建和使用往往不发生在同一个地方(例如,QuerySet 方法创建表达式,以便以后在视图中使用)。alias() 允许逐步建立复杂的表达式,可能跨越多个方法和模块,用它们的别名指代表达式部分,只用 annotate() 来获得最终结果。

order_by()

order_by(*fields)

默认情况下,QuerySet 返回的结果是按照模型 Meta 中的 ordering 选项给出的排序元组排序的。你可以通过使用 order_by 方法在每个 QuerySet 的基础上覆盖这一点。

举例:

Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')

上述结果将按 pub_date 降序排列,然后按 headline 升序排列。"-pub_date" 前面的负号表示 降序。升序是隐含的。要随机排序,使用 "?",如:

Entry.objects.order_by('?')

注意:order_by('?') 查询可能会很贵,而且速度很慢,这取决于你使用的数据库后端。

要按不同模型中的字段排序,使用与跨模型关系查询时相同的语法。也就是说,字段的名称,后面是双下划线(__),再后面是新模型中的字段名称,以此类推,想加入多少模型就加入多少。例如:

Entry.objects.order_by('blog__name', 'headline')

如果你试图通过与另一个模型有关系的字段进行排序,Django 将使用相关模型上的默认排序,如果没有指定 Meta.ordering,则通过相关模型的主键进行排序。例如,由于 Blog 模型没有指定默认排序:

Entry.objects.order_by('blog')

...等同于:

Entry.objects.order_by('blog__id')

如果 Blogordering = ['name'],那么第一个查询集将与以下内容相同:

Entry.objects.order_by('blog__name')

你也可以通过在表达式上调用 asc()esc(),按 查询表达式 排序:

Entry.objects.order_by(Coalesce('summary', 'headline').desc())

asc()esc() 有参数(nulls_firstnulls_last)来控制如何对空值进行排序。

如果你还使用 distinct(),在按相关模型中的字段排序时要谨慎。请参见 distinct() 中的说明,解释相关模型排序如何改变预期结果。

备注

允许指定一个多值字段来对结果进行排序(例如,一个 ManyToManyField 字段,或者一个 ForeignKey 字段的反向关系)。

考虑到这种情况:

class Event(Model):
   parent = models.ForeignKey(
       'self',
       on_delete=models.CASCADE,
       related_name='children',
   )
   date = models.DateField()

Event.objects.order_by('children__date')

在这里,每个 Event 可能有多个排序数据;每个 Event 有多个 children 将被多次返回到 order_by() 创建的新 QuerySet 中。换句话说,在 QuerySet 上使用 order_by() 可能会返回比你一开始工作更多的项目——这可能既不是预期的,也不是有用的。

因此,在使用多值字段对结果进行排序时要注意。如果 你能确定你要订购的每个项目只有一个订购数据,这种方法应该不会出现问题。如果不是,请确保结果是你所期望的。

没有办法指定排序是否应该区分大小写。关于大小写敏感,Django 会按照数据库后台的正常排序方式来排序。

你可以用 Lower 将一个字段转换为小写,从而实现大小写一致的排序:

Entry.objects.order_by(Lower('headline').desc())

如果你不想在查询中应用任何排序,甚至是默认的排序,可以不使用参数调用 order_by()

你可以通过检查 QuerySet.ordered 属性来判断一个查询是否被排序,如果 QuerySet 以任何方式被排序,则该属性为 True

每次 order_by() 调用都将清除以前的任何排序。例如,此查询将按 pub_date 而不是 headline 排序:

Entry.objects.order_by('headline').order_by('pub_date')

警告

排序不是一个免费的操作。你添加到排序中的每个字段都会给你的数据库带来成本。你添加的每个外键都会隐式地包含其所有的默认排序。

如果查询没有指定顺序,那么结果将以未指定的顺序从数据库中返回。只有当按一组字段排序时,才能保证特定的排序,这些字段唯一地标识结果中的每个对象。例如,如果 name 字段不是唯一的,那么按它排序就不能保证具有相同名称的对象总是以相同的顺序出现。

reverse()

reverse()

使用 reverse() 方法来反向返回查询集元素的顺序。第二次调用 reverse() 会将顺序恢复到正常方向。

要检索一个查询集中的“最后”五个项目,你可以这样做:

my_queryset.reverse()[:5]

请注意,这与 Python 中从一个序列的末尾切分不太一样。上面的例子会先返回最后一项,然后返回倒数第二项,以此类推。如果我们有一个 Python 序列,看 seq[-5:],我们会先看到倒数第五项。Django 不支持这种访问模式(从末尾切分),因为在 SQL 中不可能有效地做到这一点。

另外,请注意,reverse() 一般只应在有定义顺序的 QuerySet 上调用(例如,当对定义了默认顺序的模型进行查询时,或者当使用 order_by() 时)。如果没有为一个给定的 QuerySet 定义这样的排序,对它调用 reverse() 没有实际效果(在调用 reverse() 之前,排序是未定义的,之后也将保持未定义)。

distinct()

distinct(*fields)

返回一个新的 QuerySet,在其 SQL 查询中使用 SELECT DISTINCT。这将消除查询结果中的重复记录。

默认情况下,QuerySet 不会消除重复的记录。在实践中,这很少是一个问题,因为简单的查询,如 Blog.objects.all() 不会引入重复结果行的可能性。但是,如果你的查询跨越了多个表,当 QuerySet 被执行时,就有可能得到重复的结果。这时就应该使用 distinct()

备注

order_by() 调用中使用的任何字段都包含在 SQL SELECT 列中。当与 distinct() 结合使用时,这有时会导致意外的结果。如果按相关模型中的字段排序,这些字段将被添加到选定的列中,它们可能会使原本重复的行看起来是不同的。由于额外的列不会出现在返回的结果中(它们只是为了支持排序),所以有时看起来像是返回了非去重的结果。

同样,如果使用 values() 查询来限制所选的列,任何 order_by() 中使用的列(或默认的模型排序)仍然会被涉及,并可能影响结果的唯一性。

这里的寓意是,如果你使用 distinct(),要小心按相关模型排序。同样,当同时使用 distinct()values() 时,在按 values() 调用中没有的字段排序时要小心。

仅在 PostgreSQL 上,你可以传递位置参数(*fields),以指定 DISTINCT 应适用的字段名称。这相当于一个 SELECT DISTINCT ON 的 SQL 查询。这其中的区别是,对于普通的 distinct() 调用,数据库在确定哪些行是不同的时候,会比较每行中的 每个 字段。对于带有指定字段名的 distinct() 调用,数据库将只比较指定的字段名。

备注

当你指定字段名时,你 必须QuerySet 中提供一个 order_by(),而且 order_by() 中的字段必须以 distinct() 中的字段开始,顺序相同。

例如,SELECT DISTINCT ON (a) 给出了列 a 中每个值的第一行。如果你不指定顺序,你会得到一些任意的行。

例子(第一个例子之后的例子只适用于 PostgreSQL):

>>> Author.objects.distinct()
[...]

>>> Entry.objects.order_by('pub_date').distinct('pub_date')
[...]

>>> Entry.objects.order_by('blog').distinct('blog')
[...]

>>> Entry.objects.order_by('author', 'pub_date').distinct('author', 'pub_date')
[...]

>>> Entry.objects.order_by('blog__name', 'mod_date').distinct('blog__name', 'mod_date')
[...]

>>> Entry.objects.order_by('author', 'pub_date').distinct('author')
[...]

备注

请记住 order_by() 使用任何已经定义的默认相关模型排序。你可能必须明确地按关系 _id 或引用字段排序,以确保 DISTINCT ON 表达式与 ORDER BY 子句开头的表达式匹配。例如,如果 Blog 模型定义了一个通过 name 进行 ordering

Entry.objects.order_by('blog').distinct('blog')

...这是不可行的,因为查询将按 blog__name 排序,从而与 DISTINCT ON 表达式不匹配。你必须明确地按关系 _id 字段(本例中为 blog_id)或被引用的字段(blog__pk)排序,以确保两个表达式匹配。

values()

values(*fields, **expressions)

返回一个 QuerySet,当用作可迭代对象时,返回字典,而不是模型实例。

其中每一个字典都代表一个对象,键与模型对象的属性名相对应。

本例将 values() 的字典与普通模型对象进行比较:

# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith='Beatles')
<QuerySet [<Blog: Beatles Blog>]>

# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith='Beatles').values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

values() 方法接受可选的位置参数 *fields,它指定了 SELECT 应该被限制的字段名。如果你指定了字段,每个字典将只包含你指定字段的字段键/值。如果不指定字段,每个字典将包含数据库表中每个字段的键和值。

举例:

>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values('id', 'name')
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

values() 方法也接受可选的关键字参数 **expressions,这些参数被传递给 annotate()

>>> from django.db.models.functions import Lower
>>> Blog.objects.values(lower_name=Lower('name'))
<QuerySet [{'lower_name': 'beatles blog'}]>

你可以在排序中使用内置和 自定义查找。例如:

>>> from django.db.models import CharField
>>> from django.db.models.functions import Lower
>>> CharField.register_lookup(Lower)
>>> Blog.objects.values('name__lower')
<QuerySet [{'name__lower': 'beatles blog'}]>

values() 子句中的集合在同一 values() 子句中的其他参数之前应用。如果你需要用另一个值来分组,可以把它添加到前面的 values() 子句中。例如:

>>> from django.db.models import Count
>>> Blog.objects.values('entry__authors', entries=Count('entry'))
<QuerySet [{'entry__authors': 1, 'entries': 20}, {'entry__authors': 1, 'entries': 13}]>
>>> Blog.objects.values('entry__authors').annotate(entries=Count('entry'))
<QuerySet [{'entry__authors': 1, 'entries': 33}]>

有几个微妙的地方值得一提:

  • 如果你有一个名为 foo 的字段是一个 ForeignKey,默认的 values() 调用将返回一个名为 foo_id 的字典键,因为这是存储实际值的隐藏模型属性的名称(foo 属性指的是相关模型)。当你调用 values() 并传递字段名时,你可以传递 foofoo_id,你将得到同样的东西(字典键将与你传递的字段名匹配)。

    例子:

    >>> Entry.objects.values()
    <QuerySet [{'blog_id': 1, 'headline': 'First Entry', ...}, ...]>
    
    >>> Entry.objects.values('blog')
    <QuerySet [{'blog': 1}, ...]>
    
    >>> Entry.objects.values('blog_id')
    <QuerySet [{'blog_id': 1}, ...]>
    
  • 当使用 values()distinct() 一起使用时,请注意排序会影响结果。详见 distinct() 中的说明。

  • 如果在 extra() 调用之后使用 values() 子句,则 extra() 中的 select 参数所定义的任何字段必须明确地包含在 values() 调用中。任何在 values() 调用之后进行的 extra() 调用将忽略其额外选择的字段。

  • values() 之后调用 only()defer() 是没有意义的,这样做会引发 TypeError

  • 结合变换和聚合需要使用两个 annotate() 调用,可以是显式的,也可以是 values() 的关键字参数。如上所述,如果已经在相关字段类型上注册了变换,那么第一个 annotate() 可以省略,因此以下例子是等价的:

    >>> from django.db.models import CharField, Count
    >>> from django.db.models.functions import Lower
    >>> CharField.register_lookup(Lower)
    >>> Blog.objects.values('entry__authors__name__lower').annotate(entries=Count('entry'))
    <QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
    >>> Blog.objects.values(
    ...     entry__authors__name__lower=Lower('entry__authors__name')
    ... ).annotate(entries=Count('entry'))
    <QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
    >>> Blog.objects.annotate(
    ...     entry__authors__name__lower=Lower('entry__authors__name')
    ... ).values('entry__authors__name__lower').annotate(entries=Count('entry'))
    <QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
    

当你知道你只需要一小部分可用字段的值,而且你不需要模型实例对象的功能时,它就很有用。只选择你需要使用的字段会更高效。

最后要注意的是,你可以在调用 values() 之后调用 filter()order_by() 等,也就是说这两个调用是相同的:

Blog.objects.values().order_by('id')
Blog.objects.order_by('id').values()

制作 Django 的人喜欢把所有影响 SQL 的方法放在前面,后面(可选的)是任何影响输出的方法(比如 values()),但这并不重要。这是你真正炫耀自己个性的机会。

你也可以通过 OneToOneFieldForeignKeyManyToManyField 属性来引用相关模型上具有反向关系的字段:

>>> Blog.objects.values('name', 'entry__headline')
<QuerySet [{'name': 'My blog', 'entry__headline': 'An entry'},
     {'name': 'My blog', 'entry__headline': 'Another entry'}, ...]>

警告

因为 ManyToManyField 属性和逆向关系可以有多条相关的记录,包括这些可以对你的结果集的大小产生倍增效应。如果你在你的 values() 查询中包含了多个这样的字段,这一点会特别明显,在这种情况下,所有可能的组合都会被返回。

Special values for JSONField on SQLite

Due to the way the JSON_EXTRACT and JSON_TYPE SQL functions are implemented on SQLite, and lack of the BOOLEAN data type, values() will return True, False, and None instead of "true", "false", and "null" strings for JSONField key transforms.

values_list()

values_list(*fields, flat=False, named=False)

这与 values() 类似,只是在迭代时不返回字典,而是返回元组。每一个元组都包含了传入 values_list() 调用的各个字段或表达式的值——所以第一项是第一个字段,等等。例如:

>>> Entry.objects.values_list('id', 'headline')
<QuerySet [(1, 'First entry'), ...]>
>>> from django.db.models.functions import Lower
>>> Entry.objects.values_list('id', Lower('headline'))
<QuerySet [(1, 'first entry'), ...]>

如果你只传入一个字段,你也可以传入 flat 参数。如果 True,这将意味着返回的结果是单个值,而不是一个元组。一个例子应该可以更清楚地说明两者的区别:

>>> Entry.objects.values_list('id').order_by('id')
<QuerySet[(1,), (2,), (3,), ...]>

>>> Entry.objects.values_list('id', flat=True).order_by('id')
<QuerySet [1, 2, 3, ...]>

当有多个字段时,传入 flat 是错误的。

你可以通过 named=True 来获取结果为 namedtuple()

>>> Entry.objects.values_list('id', 'headline', named=True)
<QuerySet [Row(id=1, headline='First entry'), ...]>

使用命名元组可能会使使用结果更易读,但代价是将结果转化为命名元组时要付出很小的性能代价。

如果你不向 values_list() 传递任何值,它将按照声明的顺序返回模型中的所有字段。

一个常见的需求是获取某个模型实例的特定字段值。为了实现这一目标,使用 values_list(),然后调用 get()

>>> Entry.objects.values_list('headline', flat=True).get(pk=1)
'First entry'

values()values_list() 都是针对特定用例的优化:检索数据的子集,而不需要创建一个模型实例的开销。当处理多对多和其他多值关系(如反向外键的一对多关系)时,这个隐喻就失效了,因为“一行一对象”的假设不成立。

例如,请注意在跨 ManyToManyField 查询时的行为:

>>> Author.objects.values_list('name', 'entry__headline')
<QuerySet [('Noam Chomsky', 'Impressions of Gaza'),
 ('George Orwell', 'Why Socialists Do Not Believe in Fun'),
 ('George Orwell', 'In Defence of English Cooking'),
 ('Don Quixote', None)]>

有多个条目的作者出现多次,没有任何条目的作者的条目标题为 None

同样,在查询反向外键时,对于没有任何作者的条目,会出现 None

>>> Entry.objects.values_list('authors')
<QuerySet [('Noam Chomsky',), ('George Orwell',), (None,)]>

Special values for JSONField on SQLite

Due to the way the JSON_EXTRACT and JSON_TYPE SQL functions are implemented on SQLite, and lack of the BOOLEAN data type, values_list() will return True, False, and None instead of "true", "false", and "null" strings for JSONField key transforms.

dates()

dates(field, kind, order='ASC')

返回一个 QuerySet,它的值是一个 datetime.date 对象的列表,代表 QuerySet 内容中所有可用的特定日期。

field 应该是你的模型的 DateField 的名称。kind 应该是``"year""month""week"`` 或 "day"。结果列表中的每个 datetime.date 对象都被“截断”为给定的` type`。

  • "year" 返回字段的所有不同年份值的列表。
  • "month" 返回该字段所有不同年/月值的列表。
  • "week" 返回该字段的所有不同年份/星期值的列表。所有日期都是星期一。
  • "day" 返回该字段的所有不同年/月/日值的列表。

order,默认为 'ASC',应该是 'ASC''DESC'。这指定了如何对结果进行排序。

举例:

>>> Entry.objects.dates('pub_date', 'year')
[datetime.date(2005, 1, 1)]
>>> Entry.objects.dates('pub_date', 'month')
[datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)]
>>> Entry.objects.dates('pub_date', 'week')
[datetime.date(2005, 2, 14), datetime.date(2005, 3, 14)]
>>> Entry.objects.dates('pub_date', 'day')
[datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)]
>>> Entry.objects.dates('pub_date', 'day', order='DESC')
[datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)]
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[datetime.date(2005, 3, 20)]

datetimes()

datetimes(field_name, kind, order='ASC', tzinfo=None, is_dst=None)

返回一个 QuerySet,它的值是一个 datetime.datetime 对象的列表,代表 QuerySet 内容中所有可用的特定日期。

field_name 应该是你的模型中 DateTimeField 的名称。

kind 应该是 "year""month""week""day""hour""minute""second"。结果列表中的每个 datetime.datetime 对象都被“截断”为给定的 type

order,默认为 'ASC',应该是 'ASC''DESC'。这指定了如何对结果进行排序。

tzinfo 定义了在截断之前将日期时间转换为的时区。事实上,一个给定的日期时间根据使用的时区有不同的表示方式。这个参数必须是一个 datetime.tzinfo 对象。如果是 None,Django 会使用 current time zone。当 USE_TZFalse 时,它没有效果。

is_dst 表示 pytz 是否应该解释夏令时中不存在的和含糊不清的日期。默认情况下(当 is_dst=None),pytz 会对这种日期时间产生异常。

4.0 版后已移除: is_dst 参数已被废弃,将在 Django 5.0 中删除。

备注

这个函数直接在数据库中执行时区转换。因此,你的数据库必须能够解释 tzinfo.tzname(None) 的值。这就转化为以下要求:

none()

none()

调用 none() 将创建一个永远不会返回任何对象的查询集,在访问结果时将不执行任何查询。qs.none() 查询集是 EmptyQuerySet 的一个实例。

举例:

>>> Entry.objects.none()
<QuerySet []>
>>> from django.db.models.query import EmptyQuerySet
>>> isinstance(Entry.objects.none(), EmptyQuerySet)
True

all()

all()

返回当前 QuerySet (或 QuerySet 子类)的 副本。 这在以下情况下很有用:你可能想传入一个模型管理器或一个 QuerySet,并对结果做进一步过滤。在任何一个对象上调用 all() 后,你肯定会有一个 QuerySet 可以使用。

当一个 QuerySet执行 时,它通常会缓存其结果。如果数据库中的数据可能在 QuerySet 被评估后发生了变化,你可以通过调用 all() 对以前执行过的 QuerySet 进行更新。

union()

union(*other_qs, all=False)

使用 SQL 的 UNION 操作符来组合两个或多个 QuerySet 的结果。例如:

>>> qs1.union(qs2, qs3)

UNION 操作符默认只选择不同的值。要允许重复的值,使用 all=True 参数。

union()intersection()difference() 返回第一个 QuerySet 类型的模型实例,即使参数是其他模型的 QuerySet。只要 SELECT 列表在所有 QuerySet 中都是一样的(至少是类型,只要类型顺序相同,名字就不重要),传递不同的模型就可以。在这种情况下,你必须使用第一个 QuerySet 中的列名,在 QuerySet 方法中应用到结果的 QuerySet 中。例如:

>>> qs1 = Author.objects.values_list('name')
>>> qs2 = Entry.objects.values_list('headline')
>>> qs1.union(qs2).order_by('name')

此外,只有 LIMITOFFSETCOUNT(*)ORDER BY 和指定列(即切片、count()exists()order_by()values()values_list() )允许在结果 QuerySet 中使用。此外,数据库对组合查询中允许的操作也有限制。例如,大多数数据库不允许在组合查询中使用 LIMITOFFSET

intersection()

intersection(*other_qs)

使用 SQL 的 INTERSECT 操作符来返回两个或多个 QuerySet 的共享元素。例如:

>>> qs1.intersection(qs2, qs3)

一些限制见 union()

difference()

difference(*other_qs)

使用 SQL 的 EXCEPT 操作符,只保留存在于 QuerySet 中的元素,而不保留在其他 QuerySet 中的元素。例如:

>>> qs1.difference(qs2, qs3)

一些限制见 union()

extra()

extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

有时候,Django 查询语法本身并不能很容易地表达一个复杂的 WHERE 子句。对于这些边缘情况,Django 提供了 extra() QuerySet 修饰符——用于将特定的子句注入到由 QuerySet 生成的 SQL 中。

在万不得已的情况下使用这种方法

这是一个老的 API,我们的目标是在未来的某个时间点废弃。只有当你不能使用其他的查询集方法来表达你的查询时才使用它。如果你确实需要使用它,请使用 QuerySet.extra keyword 并和你的用例(请先检查现有的工单列表)一起 file a ticket ,这样我们就可以增强 QuerySet API 以允许删除 extra()。我们不再改进或修复该方法的错误。

例如,extra() 的这种用法:

>>> qs.extra(
...     select={'val': "select col from sometable where othercol = %s"},
...     select_params=(someparam,),
... )

相当于:

>>> qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))

使用 RawSQL 的主要好处是,如果需要的话,可以设置 output_field。主要的缺点是,如果你在原始 SQL 中引用了查询集的某个表的别名,那么 Django 有可能会改变这个别名(例如,当查询集在另一个查询中被用作子查询时)。

警告

每当你使用 extra() 时,你应该非常小心。每次使用它时,你应该使用 params 来转义任何用户可以控制的参数,以防止 SQL 注入攻击。

你也不能在 SQL 字符串中引用占位符。这个例子因为在 %s 周围的引号而容易受到 SQL 注入的影响。

SELECT col FROM sometable WHERE othercol = '%s'  # unsafe!

你可以阅读更多关于 Django 的 SQL 注入保护 的工作原理。

根据定义,这些额外的查找可能无法移植到不同的数据库引擎中(因为你明确地编写了 SQL 代码),并且违反了 DRY 原则,所以你应该尽可能地避免它们。

指定 paramsselectwheretables 中的一个或多个参数。这些参数都不是必须的,但你应该至少使用其中的一个。

  • select

    select 参数让你在 SELECT 子句中放入额外的字段。 它应该是一个将属性名映射到 SQL 子句的字典,用于计算该属性。

    举例:

    Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
    

    因此,每个 Entry 对象将有一个额外的属性,is_recent,一个布尔值,表示该条目的 pub_date 是否大于 2006 年 1 月 1 日。

    Django 将给定的 SQL 片段直接插入到 SELECT 语句中,所以上面例子的 SQL 结果将是这样的。

    SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent
    FROM blog_entry;
    

    下一个例子更高级;它做了一个子查询,给每个结果的 Blog 对象一个 entry_count 属性,一个相关 Entry 对象的整数:

    Blog.objects.extra(
        select={
            'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id'
        },
    )
    

    在这个特殊的情况下,我们利用了这样一个事实,即查询在其 FROM 子句中已经包含 blog_blog 表。

    上述例子的 SQL 结果是:

    SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count
    FROM blog_blog;
    

    需要注意的是,大多数数据库引擎要求在子查询周围加上括号,而 Django 的 select 子句则不需要。还需要注意的是,一些数据库后端,比如一些 MySQL 版本,不支持子查询。

    在一些罕见的情况下,你可能希望在 extra(select=...) 中给 SQL 片段传递参数。为此,使用 select_params 参数。

    这样做就可以了,比如:

    Blog.objects.extra(
        select={'a': '%s', 'b': '%s'},
        select_params=('one', 'two'),
    )
    

    如果你需要在选择字符串中使用 %s,请使用序列 %%s

  • wheretables

    你可以通过使用 where 来定义明确的 SQL WHERE 子句——也许是为了执行非明确的连接。你可以通过使用 tables 手动添加表到 SQL FROM 子句中。

    wheretables 都采用一个字符串列表。所有 where 参数都与任何其他搜索标准“AND”在一起。

    举例:

    Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
    

    ...翻译成(大致)下面的 SQL:

    SELECT * FROM blog_entry WHERE (foo='a' OR bar='a') AND (baz='a')
    

    如果使用 tables 参数时,要注意指定查询中已经使用过的表,当你通过 tables 参数添加额外的表时,Django 会认为你希望额外包含该表,如果已经包含的话。当你通过 tables 参数添加额外的表时,Django 会认为你希望额外地包含该表,如果它已经被包含了。这就会产生一个问题,因为表名会被赋予一个别名。如果一个表在一条 SQL 语句中多次出现,那么第二次和后续的表必须使用别名,这样数据库才能区分它们。如果你指的是你在额外的 where 参数中添加的额外表,这就会造成错误。

    一般情况下,你只会添加查询中还没有出现的额外表。但是,如果确实出现了上面概述的情况,有几种解决办法。首先,看看是否可以不包含额外的表,而使用已经在查询中出现的表。如果不可能的话,把你的 extra() 调用放在查询集构造的前面,这样你的表就是那个表的第一次使用。最后,如果所有其他方法都失败了,看一下产生的查询,重写你的 where 加法,使用给你的额外表的别名。每次以同样的方式构造查询集时,别名都会是一样的,所以你可以信赖别名不会改变。

  • order_by

    如果你需要使用你通过 extra() 所包含的一些新字段或表来对结果查询集进行排序,请使用 extra()order_by 参数,并传入一串字符串。这些字符串应该是模型字段(就像在查询集上的普通 order_by() 方法一样),形式为 table_name.column_name 或者是你在 extra()select 参数中指定的列的别名。

    例子:

    q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
    q = q.extra(order_by = ['-is_recent'])
    

    这将把 is_recent 为真的所有项目排在结果集的前面(True 按降序排列在 False 之前)。

    顺便说一下,这表明你可以多次调用 extra(),它将按照你的期望行事(每次增加新的约束)。

  • params

    上面描述的 where 参数可以使用标准的 Python 数据库字符串占位符——'%s' 来表示数据库引擎应该自动引用的参数。params 参数是一个要被替换的额外参数的列表。

    举例:

    Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    

    始终使用 params 而不是直接将值嵌入 where,因为 params 将确保根据你的特定后台正确引用值。例如,引号将被正确转义。

    不好的:

    Entry.objects.extra(where=["headline='Lennon'"])
    

    正确的:

    Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    

警告

如果你在 MySQL 上执行查询,请注意 MySQL 的静默强制类型转换可能会在混合类型时导致意外的结果。如果你在一个字符串类型的列上查询,但却有一个整数值,MySQL 会在执行比较之前将表中所有值的类型强制转换为整数。例如,如果你的表中包含值 'abc''def',而你查询 WHERE mycolumn=0,这两行都会匹配。为了防止这种情况发生,在查询中使用该值之前,请执行正确的类型转换。

defer()

defer(*fields)

在一些复杂的数据建模情况下,你的模型可能包含很多字段,其中一些字段可能包含很多数据(例如,文本字段),或者需要昂贵的处理来将它们转换为 Python 对象。如果你在某些情况下使用查询集的结果,在最初获取数据时不知道是否需要这些特定的字段,你可以告诉 Django 不要从数据库中检索这些字段。

通过将不加载的字段名称传递给 defer()

Entry.objects.defer("headline", "body")

一个有递延字段的查询集仍然会返回模型实例。如果你访问每个递延字段,将从数据库中检索该字段(一次一个,而不是同时访问所有的递延字段)。

你可以多次调用 defer()。每次调用都会在推迟的集合中增加新的字段:

# Defers both the body and headline fields.
Entry.objects.defer("body").filter(rating=5).defer("headline")

字段被添加到递延集的顺序并不重要。用已经被递延的字段名调用 defer() 是无害的(该字段仍将被递延)。

你可以通过使用标准的双下划线符号来分隔相关的字段,来推迟加载相关模型中的字段(如果相关模型是通过 select_related() 加载的):

Blog.objects.select_related().defer("entry__headline", "entry__body")

如果你想清除一组递延字段,将 None 作为参数传递给 defer()

# Load all fields immediately.
my_queryset.defer(None)

模型中的一些字段不会被推迟,即使你要求它们。你永远不能推迟加载主键。如果你使用 select_related() 来检索相关的模型,你不应该推迟从主模型连接到相关模型的字段的加载,这样做会导致一个错误。

备注

defer() 方法(和它的表兄弟 only(),见下面)只适用于进阶使用情况。它们提供了一种优化,当你仔细分析了你的查询,了解了你所需要的 确切 的信息,并且测算出返回你所需要的字段和模型的全部字段集之间的差异会很大。

Even if you think you are in the advanced use-case situation, only use ``defer()`` when you cannot, at queryset load time, determine if you will need the extra fields or not. If you are frequently loading and using a particular subset of your data, the best choice you can make is to normalize your models and put the non-loaded data into a separate model (and database table). If the columns must stay in the one table for some reason, create a model with Meta.managed = False (see the managed attribute documentation) containing just the fields you normally need to load and use that where you might otherwise call defer(). This makes your code more explicit to the reader, is slightly faster and consumes a little less memory in the Python process.

例如,这两种模式都使用相同的基础数据库表:

class CommonlyUsedModel(models.Model):
    f1 = models.CharField(max_length=10)

    class Meta:
        managed = False
        db_table = 'app_largetable'

class ManagedModel(models.Model):
    f1 = models.CharField(max_length=10)
    f2 = models.CharField(max_length=10)

    class Meta:
        db_table = 'app_largetable'

# Two equivalent QuerySets:
CommonlyUsedModel.objects.all()
ManagedModel.objects.all().defer('f2')

如果很多字段需要在非托管模型中重复,最好的办法是创建一个共享字段的抽象模型,然后让非托管模型和托管模型从抽象模型中继承。

备注

当调用 save() 对有延迟字段的实例进行保存时,只有加载的字段会被保存。更多细节请参见 save()

only()

only(*fields)

only() 方法与 defer() 大致相反。在检索模型时,你调用它的时候,应该调用那些 应该递延的字段。 如果你有一个模型,其中几乎所有的字段都需要递延,使用 only() 来指定补充的字段集可以使代码更简单。

假设你有一个模型,其字段为 nameagebiography。就递延字段而言,以下两个查询集是相同的:

Person.objects.defer("age", "biography")
Person.objects.only("name")

每当你调用 only() 时,它就会 替换 要立即加载的字段集。该方法的名称是记号式的: 那些字段被立即加载;其余的字段被推迟。因此,连续调用 only() 的结果是只考虑最后的字段:

# This will defer all fields except the headline.
Entry.objects.only("body", "rating").only("headline")

由于 defer() 以递增的方式行事(将字段添加到递延列表中),你可以将对 only()defer() 的调用结合起来,事情就会符合逻辑:

# Final result is that everything except "headline" is deferred.
Entry.objects.only("headline", "body").defer("body")

# Final result loads headline and body immediately (only() replaces any
# existing set of fields).
Entry.objects.defer("body").only("headline", "body")

defer() 文档注释中的所有注意事项也适用于 only()。谨慎使用,只有在用尽其他选项后才能使用。

使用 only() 和使用 select_related() 省略请求的字段也是错误的。

备注

当调用 save() 对有延迟字段的实例进行保存时,只有加载的字段会被保存。更多细节请参见 save()

using()

using(alias)

如果你使用多个数据库,该方法用于控制 QuerySet 将针对哪个数据库进行评估。 本方法的唯一参数是数据库的别名,定义在 DATABASES 中。

例子:

# queries the database with the 'default' alias.
>>> Entry.objects.all()

# queries the database with the 'backup' alias
>>> Entry.objects.using('backup')

select_for_update()

select_for_update(nowait=False, skip_locked=False, of=(), no_key=False)

返回一个查询集,该查询集将锁定行直到事务结束,从而在受支持的数据库上生成 SELECT ... FOR UPDATE SQL 语句。

例子:

from django.db import transaction

entries = Entry.objects.select_for_update().filter(author=request.user)
with transaction.atomic():
    for entry in entries:
        ...

当查询集被执行时(这里是 for entry in entries),所有匹配的条目将被锁定,直到事务块结束,这意味着其他事务将被阻止改变或获取它们的锁。

通常情况下,如果另一个事务已经获得了所选行的锁,那么查询将被阻塞,直到锁被释放。如果这不是你想要的行为,调用 select_for_update(nowait=True)。这将使调用非阻塞。如果一个冲突的锁已经被另一个事务获取,那么当查询集被评估时,将引发 DatabaseError。你也可以通过使用 select_for_update( skip_locked=True) 来忽略锁定的记录。nowaitskip_locked 是相互排斥的,在启用这两个选项的情况下调用 select_for_update() 会导致一个 ValueError

默认情况下,select_for_update() 锁定所有被查询选择的行。例如,在 select_related() 中指定的相关对象的行,除了查询集模型的行之外,也会被锁定。如果不希望这样,可以在 select_for_update(of=(...)) 中使用与 select_related() 相同的字段语法指定你要锁定的相关对象。使用 'self' 来表示查询集的模型。

select_for_update(of=(...)) 中锁定父模型

如果在使用 多表继承 时要锁定父模型,必须在 of 参数中指定父链接字段(默认为 <parent_model_name>_ptr)。例如:

Restaurant.objects.select_for_update(of=('self', 'place_ptr'))

在指定的字段使用 select_for_update(of=(...))

如果你想锁定模型并指定选定的字段,例如使用 values(),你必须从每个模型的 of 参数中至少选择一个字段。没有选定字段的模型将不会被锁定。

仅在 PostgreSQL 上,你可以通过 no_key=True 来获得一个较弱的锁,这仍然允许在锁存在的情况下,创建仅仅引用锁定的行(例如,通过外键)。PostgreSQL 文档中有更多关于 行级锁模式的细节

你不能在可空的关系上使用 select_for_update()

>>> Person.objects.select_related('hometown').select_for_update()
Traceback (most recent call last):
...
django.db.utils.NotSupportedError: FOR UPDATE cannot be applied to the nullable side of an outer join

为了避免这种限制,如果你不关心空对象,你可以排除它们:

>>> Person.objects.select_related('hometown').select_for_update().exclude(hometown=None)
<QuerySet [<Person: ...)>, ...]>

postgresqloraclemysql 数据库后端支持 select_for_update()。然而,MariaDB 10.3+ 只支持 nowait 参数,MariaDB 10.6+ 也支持 skip_locked 参数,MySQL 8.0.1+ 支持 nowaitskip_lockedof 参数。只有 PostgreSQL 支持 no_key 参数。

在使用不支持这些选项的数据库后端(如 MySQL)向 select_for_update()``传递 ``nowait=Trueskip_locked=Trueno_key=Trueof,会产生一个 NotSupportedError。这可以防止代码意外地阻塞。

在支持 SELECT ... FOR UPDATE 的后端上,用 select_for_update() 在自动提交模式下执行一个查询集是一个 TransactionManagementError 错误,因为在这种情况下行没有被锁定。如果允许这样做,这将促进数据损坏,并且很容易通过调用期望在一个事务之外的事务中运行的代码而引起。

在不支持 SELECT ... FOR UPDATE 的后端(比如 SQLite)使用 select_for_update() 不会有任何影响。SELECT ... FOR UPDATE 不会被添加到查询中,如果 select_for_update() 在自动提交模式下使用,也不会出现错误。

警告

虽然 select_for_update() 通常在自动提交模式下会失败,但由于 TestCase 会自动将每个测试封装在一个事务中,因此在一个 TestCase 中调用 select_for_update() 甚至在 atomic()`() 块外调用 select_for_update() 会(也许会出乎意料地)通过而不会引发 TransactionManagementError。为了正确测试 select_for_update(),你应该使用 TransactionTestCase

可能不支持某些表达方式

PostgreSQL 不支持 select_for_update()Window 表达式。

Changed in Django 3.2:

增加了 no_key 参数。

of 参数在 MySQL 8.0.1 以上版本中是允许的。

Changed in Django 4.0:

skip_locked 参数在 MariaDB 10.6 以上版本是允许的。

raw()

raw(raw_query, params=(), translations=None, using=None)

获取一个原始 SQL 查询,执行它,并返回一个 django.db.models.query.RawQuerySet 实例。这个 RawQuerySet 实例可以像普通的 QuerySet 一样进行迭代,提供对象实例。

更多信息请参见 执行原生 SQL 查询

警告

raw() 总是触发一个新的查询,并且不考虑以前的过滤。因此,它通常应该从 Manager 或从一个新的 QuerySet 实例中调用。

Changed in Django 3.2:

参数 params 的默认值从 None 改为空元组。

返回新 QuerySet 的操作符

组合的查询集必须使用相同的模型。

AND(&

使用 SQL AND 操作符将两个 QuerySet 组合起来。

以下的都是相同的:

Model.objects.filter(x=1) & Model.objects.filter(y=2)
Model.objects.filter(x=1, y=2)
from django.db.models import Q
Model.objects.filter(Q(x=1) & Q(y=2))

SQL 等价于:

SELECT ... WHERE x=1 AND y=2

OR(|

使用 SQL OR 操作符将两个 QuerySet 组合起来。

以下的都是相同的:

Model.objects.filter(x=1) | Model.objects.filter(y=2)
from django.db.models import Q
Model.objects.filter(Q(x=1) | Q(y=2))

SQL 等价于:

SELECT ... WHERE x=1 OR y=2

| 不是一个换元运算,因为可能会产生不同的(虽然是等价的)查询。

不返回 QuerySet 的方法

以下 QuerySet 方法执行 QuerySet,并返回 QuerySet 以外的东西。

这些方法不使用缓存(参见 缓存和 QuerySet)。相反,它们每次被调用时都会查询数据库。

get()

get(*args, **kwargs)

返回与给定的查找参数相匹配的对象,其格式应该在 Field lookups 中描述。你应该使用保证唯一的查询,比如主键或唯一约束中的字段。例如:

Entry.objects.get(id=1)
Entry.objects.get(Q(blog=blog) & Q(entry_number=1))

如果你希望一个查询集已经返回一条记录,你可以在没有任何参数的情况下使用 get() 来返回该行的对象:

Entry.objects.filter(pk=1).get()

如果 get() 没有找到任何对象,它会引发一个 Model.DoesNotExist 异常:

Entry.objects.get(id=-999) # raises Entry.DoesNotExist

如果 get() 发现多个对象,会引发一个 Model.MultipleObjectsReturned 异常:

Entry.objects.get(name='A Duplicated Name') # raises Entry.MultipleObjectsReturned

这两个异常类都是模型类的属性,并且特定于该模型。如果你想对不同模型的多个 get() 调用处理这样的异常,可以使用它们的通用基类。例如,你可以使用 django.core.exceptions.ObjectDoesNotExist 来处理 DoesNotExist 来自多个模型的异常:

from django.core.exceptions import ObjectDoesNotExist

try:
    blog = Blog.objects.get(id=1)
    entry = Entry.objects.get(blog=blog, entry_number=1)
except ObjectDoesNotExist:
    print("Either the blog or entry doesn't exist.")

create()

create(**kwargs)

一种方便的方法,用于创建一个对象并一步到位地保存。 因此:

p = Person.objects.create(first_name="Bruce", last_name="Springsteen")

和:

p = Person(first_name="Bruce", last_name="Springsteen")
p.save(force_insert=True)

是等效的。

force_insert 参数在其他地方有说明,但它的意思是总是会创建一个新的对象。通常情况下,你不需要担心这个问题。但是,如果你的模型中包含了一个你设置的手动主键值,而且如果这个值已经存在于数据库中,那么对 create() 的调用就会以一个 IntegrityError 失败,因为主键必须是唯一的。如果使用手动主键,要做好处理异常的准备。

get_or_create()

get_or_create(defaults=None, **kwargs)

一个方便的方法,用于查找具有给定 kwargs 的对象(如果你的模型对所有字段都有默认值,则可能为空),必要时创建一个对象。

返回 (object, created) 的元组,其中 object 是检索或创建的对象,created 是指定是否创建新对象的布尔值。

这是为了防止在并行进行请求时创建重复的对象,并作为样板代码的快捷方式。 例如:

try:
    obj = Person.objects.get(first_name='John', last_name='Lennon')
except Person.DoesNotExist:
    obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
    obj.save()

在这里,如果是并发请求,可能会多次尝试用相同的参数保存一个 Person。为了避免这种竞争条件,可以使用 get_or_create() 重写上面的例子,比如:

obj, created = Person.objects.get_or_create(
    first_name='John',
    last_name='Lennon',
    defaults={'birthday': date(1940, 10, 9)},
)

任何传递给 get_or_create() 的关键字参数—— 除了 一个叫 defaults 的可选参数——都将在 get() 调用中使用。如果找到了一个对象,get_or_create() 返回该对象的元组和 False

警告

假设数据库强制执行关键字参数的唯一性(参见 uniqueunique_together),这个方法是原子性的。如果关键字参数中使用的字段没有唯一性约束,那么对该方法的并发调用可能会导致插入具有相同参数的多条记录。

你可以通过将 get_or_create()filter() 串联起来,并使用 Q 对象 为检索对象指定更复杂的条件。例如,如果 Robert 或 Bob Marley 存在,则检索 Robert 或 Bob Marley,否则创建后者:

from django.db.models import Q

obj, created = Person.objects.filter(
    Q(first_name='Bob') | Q(first_name='Robert'),
).get_or_create(last_name='Marley', defaults={'first_name': 'Bob'})

如果找到多个对象,get_or_create() 会引发 MultipleObjectsReturned。如果没有找到对象,get_or_create() 将实例化并保存一个新对象,返回一个新对象的元组和 True。新对象将大致按照以下算法创建:

params = {k: v for k, v in kwargs.items() if '__' not in k}
params.update({k: v() if callable(v) else v for k, v in defaults.items()})
obj = self.model(**params)
obj.save()

在英语中,这意味着从任何不包含双下划线的非 'defaults' 关键字参数开始(这将表明一个非精确的查找)。然后添加 defaults 的内容,必要时覆盖任何键,并将结果作为模型类的关键字参数。如果 defaults 中存在任何可调用对象,则对其进行评估。正如上面所提示的,这是对所使用算法的简化,但它包含了所有相关的细节。内部实现有比这更多的错误检查,并处理一些额外的边缘条件;如果你感兴趣,请阅读代码。

如果你有一个名为 defaults 的字段,并且想在 get_or_create() 中使用它作为精确查询,使用 'defaults__exact',像这样:

Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})

当你使用手动指定的主键时,get_or_create() 方法的错误行为与 create() 类似。如果需要创建一个对象,而该键已经存在于数据库中,则会引发一个 IntegrityError

最后,说一下在 Django 视图中使用 get_or_create() 的问题。请确保只在 POST 请求中使用它,除非你有很好的理由不这样做。GET 请求不应该对数据有任何影响。相反,当对页面的请求对数据有副作用时,请使用 POST。更多内容,请参见在 HTTP 规范中 安全方法

警告

你可以通过 ManyToManyField 属性和反向关系来使用 get_or_create()。在这种情况下,你将限制在该关系的上下文内进行查询。如果你不持续使用它,这可能会导致一些完整性问题。

如以下模型:

class Chapter(models.Model):
    title = models.CharField(max_length=255, unique=True)

class Book(models.Model):
    title = models.CharField(max_length=256)
    chapters = models.ManyToManyField(Chapter)

你可以通过 Book 的 chapters 字段使用 get_or_create(),但它只能在该书的上下文中获取:

>>> book = Book.objects.create(title="Ulysses")
>>> book.chapters.get_or_create(title="Telemachus")
(<Chapter: Telemachus>, True)
>>> book.chapters.get_or_create(title="Telemachus")
(<Chapter: Telemachus>, False)
>>> Chapter.objects.create(title="Chapter 1")
<Chapter: Chapter 1>
>>> book.chapters.get_or_create(title="Chapter 1")
# Raises IntegrityError

出现这种情况是因为它试图通过“Ulysses”这本书获取或创建“Chapter 1”,但它不能做任何事情:关系不能获取该章,因为它与该书无关,但它也不能创建它,因为 title 字段应该是唯一的。

update_or_create()

update_or_create(defaults=None, **kwargs)

用给定的 kwargs 更新对象的一种方便方法,是必要时创建一个新对象。defaults 是用来更新对象的 (field, value) 对的字典。defaults 中的值可以是可调用对象。

返回 (object, created) 的元组,其中 object 是创建或更新的对象,created 是一个布尔值,指定是否创建了一个新对象。

update_or_create 方法根据给定的 kwargs 尝试从数据库中获取一个对象。如果找到了匹配的对象,它就会更新 defaults 字典中传递的字段。

这是作为一个快捷方式来处理样板代码。例如:

defaults = {'first_name': 'Bob'}
try:
    obj = Person.objects.get(first_name='John', last_name='Lennon')
    for key, value in defaults.items():
        setattr(obj, key, value)
    obj.save()
except Person.DoesNotExist:
    new_values = {'first_name': 'John', 'last_name': 'Lennon'}
    new_values.update(defaults)
    obj = Person(**new_values)
    obj.save()

当模型中的字段数量增加时,这种模式就会变得很笨重。上面的例子可以使用 update_or_create() 重写,就像这样:

obj, created = Person.objects.update_or_create(
    first_name='John', last_name='Lennon',
    defaults={'first_name': 'Bob'},
)

关于如何解决在 kwargs 中传递名字的详细描述,见 get_or_create()

如上文 get_or_create() 中所述,这种方法容易出现竞争条件,如果不在数据库层面强制执行唯一性,就会导致同时插入多条记录。

就像 get_or_create()create() 一样,如果你使用的是手动指定的主键,需要创建一个对象,但该键已经存在于数据库中,就会引发 IntegrityError

bulk_create()

bulk_create(objs, batch_size=None, ignore_conflicts=False)

这个方法以有效的方式将提供的对象列表插入数据库(一般来说,不管有多少个对象,只需进行一次查询),并以列表形式返回创建的对象,顺序与提供的相同:

>>> objs = Entry.objects.bulk_create([
...     Entry(headline='This is a test'),
...     Entry(headline='This is only a test'),
... ])

不过这有一些注意事项:

  • 模型的 save() 方法将不会被调用,pre_savepost_save 信号将不会被发送。

  • 在多表继承的情况下,它不能与子模型一起工作。

  • 如果模型的主键是一个 AutoField,主键属性只能在某些数据库(目前是 PostgreSQL,MariaDB 10.5+,和 SQLite 3.35+)上检索到。在其他数据库中,它将不会被设置。

  • 对于多对多的关系,它是行不通的。

  • 它将 objs 转换为一个列表,如果 objs 是一个生成器,则完全执行 objs。这种转换允许检查所有对象,因此任何具有手动设置主键的对象都可以首先插入。如果你想分批插入对象,而不一次性执行整个生成器,你可以使用这种技术,只要对象没有任何手动设置的主键:

    from itertools import islice
    
    batch_size = 100
    objs = (Entry(headline='Test %s' % i) for i in range(1000))
    while True:
        batch = list(islice(objs, batch_size))
        if not batch:
            break
        Entry.objects.bulk_create(batch, batch_size)
    

batch_size 参数控制在一次查询中创建多少对象。默认情况是在一个批次中创建所有对象,但 SQLite 除外,默认情况是每个查询最多使用 999 个变量。

在支持它的数据库上(除了Oracle),将 ignore_conflicts 参数设置为 True 告诉数据库忽略插入任何不合格的约束条件的行,如重复的唯一值。启用该参数会禁用在每个模型实例上设置主键(如果数据库正常支持的话)。

警告

在 MySQL 和 MariaDB 上,将 ignore_conflicts 参数设置为 True 将某些类型的错误,除了重复键之外,变成警告。即使在严格模式下也是如此。例如:无效值或不可空值违规。更多细节请参见 MySQL documentationMariaDB documentation

Changed in Django 4.0:

增加了对在 SQLite 3.35+ 上获取主键属性的支持。

bulk_update()

bulk_update(objs, fields, batch_size=None)

该方法有效地更新所提供的模型实例上的给定字段,一般只需一次查询,并返回更新对象的数量:

>>> objs = [
...    Entry.objects.create(headline='Entry 1'),
...    Entry.objects.create(headline='Entry 2'),
... ]
>>> objs[0].headline = 'This is entry 1'
>>> objs[1].headline = 'This is entry 2'
>>> Entry.objects.bulk_update(objs, ['headline'])
2
Changed in Django 4.0:

更新对象的数量的返回值被添加。

QuerySet.update() 用于保存更改,所以这比遍历模型列表并对每个模型调用 save() 更有效,但它有一些注意事项:

  • 你不能更新模型的主键。
  • 每个模型的 save() 方法没有被调用,而且 pre_savepost_save 信号没有被发送。
  • 如果更新大量行中的大量列,生成的 SQL 可能非常大。通过指定一个合适的 batch_size 来避免这种情况。
  • 更新定义在多表继承祖先上的字段将给每个祖先带来额外的查询。
  • 当一个单独的批次包含重复的内容时,只有该批次的第一个实例会导致更新。
  • 该函数返回的更新对象的数量可能少于传入的对象的数量。这可能是由于传入的对象重复,在同一批次中被更新,或者是竞赛条件,如对象在数据库中不再存在。

batch_size 参数控制一次查询中保存多少对象。默认值是在一个批次中更新所有对象,但 SQLite 和 Oracle 除外,它们对查询中使用的变量数量有限制。

count()

count()

返回一个整数,表示数据库中与 QuerySet 匹配的对象数量。

举例:

# Returns the total number of entries in the database.
Entry.objects.count()

# Returns the number of entries whose headline contains 'Lennon'
Entry.objects.filter(headline__contains='Lennon').count()

count() 调用在幕后执行 SELECT COUNT(*),所以你应该总是使用 count() 而不是将所有的记录加载到 Python 对象中,然后在结果上调用 len() (除非你需要将对象加载到内存中,在这种情况下 len() 会更快)。

请注意,如果你想知道 QuerySet 中的项数,并且也要从它中检索模型实例(例如,通过迭代它),那么使用 len(queryset) 可能更有效,因为它不会像 count() 那样引起额外的数据库查询。

如果查询集已经被完全检索到,count() 将使用该长度,而不是执行额外的数据库查询。

in_bulk()

in_bulk(id_list=None, *, field_name='pk')

接收一个字段值的列表(id_list)和这些值的 field_name,并返回一个字典,将每个值映射到具有给定字段值的对象实例。in_bulk 不会引发任何 django.core.exceptions.ObjectDoesNotExist 异常;也就是说,任何不匹配任何实例的 id_list 值将被简单地忽略掉。如果没有提供 id_list,将返回查询集的所有对象。field_name 必须是一个唯一字段或一个独立的字段(如果只有一个字段在 distinct() 中指定)。field_name 默认为主键。

举例:

>>> Blog.objects.in_bulk([1])
{1: <Blog: Beatles Blog>}
>>> Blog.objects.in_bulk([1, 2])
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
>>> Blog.objects.in_bulk([])
{}
>>> Blog.objects.in_bulk()
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>}
>>> Blog.objects.in_bulk(['beatles_blog'], field_name='slug')
{'beatles_blog': <Blog: Beatles Blog>}
>>> Blog.objects.distinct('name').in_bulk(field_name='name')
{'Beatles Blog': <Blog: Beatles Blog>, 'Cheddar Talk': <Blog: Cheddar Talk>, 'Django Weblog': <Blog: Django Weblog>}

如果你传递 in_bulk() 一个空列表,你将得到一个空字典。

Changed in Django 3.2:

允许使用 distinct 字段。

iterator()

iterator(chunk_size=2000)

执行 QuerySet (通过执行查询),并在结果上返回一个迭代器(见 PEP 234)。QuerySet 通常会在内部缓存其结果,因此重复执行不会导致额外的查询。相反,iterator() 将直接读取结果,而不在 QuerySet 级别做任何缓存(在内部,默认的迭代器调用 iterator() 并缓存返回值)。对于一个只需要访问一次就能返回大量对象的 QuerySet 来说,这可以带来更好的性能,并显著减少内存。

请注意,在已经被执行的 QuerySet 上使用 iterator() 会迫使它再次执行,重复查询。

另外,使用 iterator() 会导致之前的 refetch_related() 调用被忽略,因为这两种优化方式放在一起没有意义。

根据数据库后端,查询结果将被一次性加载或使用服务器端的游标从数据库中流转。

使用服务器端游标

Oracle 和 PostgreSQL 使用服务器端的游标从数据库流式传输结果,而不需要将整个结果集加载到内存中。

Oracle 数据库驱动程序总是使用服务器端的游标。

对于服务器端的游标,chunk_size 参数指定了要在数据库驱动层缓存的结果数量。获取更大的块数会减少数据库驱动和数据库之间的往返次数,但会牺牲内存。

在 PostgreSQL 上,只有当 DISABLE_SERVER_SIDE_CURSORS 设置为 False 时,才会使用服务器端游标。如果你使用的是配置在事务池模式下的连接池器,请阅读 事务池和服务器端游标。当禁用服务器端游标时,其行为与不支持服务器端游标的数据库相同。

没有服务器端游标

MySQL 不支持流式结果,因此 Python 数据库驱动将整个结果集加载到内存中。然后数据库适配器使用 PEP 249 中定义的 fetchmany() 方法将结果集转化为 Python 行对象。

SQLite 可以使用 fetchmany() 分批获取结果,但由于 SQLite 不提供连接内查询之间的隔离,所以在向被迭代的表写入时要小心。参见 使用 QuerySet.iterator() 时的隔离 了解更多信息。

chunk_size 参数控制 Django 从数据库驱动中获取的批次大小。批量越大,就会减少与数据库驱动通信的开销,但代价是略微增加内存消耗。

chunk_size 的默认值 2000 来自 psycopg 邮件列表的计算

假设行数为 10-20 列,文字数据和数字数据混合,2000 要取不到 100KB 的数据,这似乎是一个很好的折中方案,在传输的行数和提前退出循环时丢弃的数据之间。

latest()

latest(*fields)

根据给定的字段,返回表中最新的对象。

这个例子根据 pub_date 字段返回表中最新的 Entry

Entry.objects.latest('pub_date')

你也可以根据几个字段选择最新的。例如,当两个条目具有相同的 pub_date 时,要选择最早的 expire_date 条目:

Entry.objects.latest('pub_date', '-expire_date')

'-expire_date' 中的负号表示按 序排列 expire_date。由于 latest() 得到的是最后一个结果,所以选择了最早的 expire_dateEntry

如果你模型的 Meta 指定了 get_latest_by,你可以省略 earliest()latest() 的任何参数。get_latest_by 中指定的字段将被默认使用。

get()earliest()latest(),如果没有给定参数的对象,就会引发 DoesNotExist

请注意,earliest()latest() 的存在纯粹是为了方便和可读性。

earliest()latest() 可能返回日期为空的实例。

由于排序是委托给数据库的,如果使用不同的数据库,允许空值的字段上的结果可能会有不同的排序。例如,PostgreSQL 和 MySQL 将空值排序为高于非空值,而 SQLite 则相反。

你可能想过滤掉空值:

Entry.objects.filter(pub_date__isnull=False).latest('pub_date')

earliest()

earliest(*fields)

除了方向改变外,其他工作方式都像 last()

first()

first()

返回查询集匹配的第一个对象,如果没有匹配的对象,则返回 None。如果 QuerySet 没有定义排序,那么查询集自动按主键排序。这可能会影响聚合结果,如 Interaction with order_by() 中所述。

举例:

p = Article.objects.order_by('title', 'pub_date').first()

请注意,first() 是一个方便的方法,下面的代码示例相当于上面的例子:

try:
    p = Article.objects.order_by('title', 'pub_date')[0]
except IndexError:
    p = None

last()

last()

first() 工作原理相同,但返回的是查询集中的最后一个对象。

aggregate()

aggregate(*args, **kwargs)

返回对 QuerySet 计算的聚合值(平均值、总和等)的字典。aggregate() 的每个参数都指定了一个将被包含在返回的字典中的值。

Django 提供的聚合函数在下面的 Aggregation Functions 中介绍。由于聚合函数也是 查询表达式,所以你可以将聚合函数与其他聚合函数或值结合起来,创建复杂的聚合函数。

使用关键字参数指定的聚合将使用关键字作为注解的名称。匿名参数将根据聚合函数的名称和被聚合的模型字段为其生成一个名称。复杂的聚合不能使用匿名参数,必须指定一个关键字参数作为别名。

例如,当你在处理博客条目时,你可能想知道贡献过博客条目的作者数量:

>>> from django.db.models import Count
>>> q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}

通过使用关键字参数来指定聚合函数,可以控制返回的聚合值的名称:

>>> q = Blog.objects.aggregate(number_of_entries=Count('entry'))
{'number_of_entries': 16}

关于聚合的深入讨论,见 关于聚合的专题指南

exists()

exists()

如果 QuerySet 包含任何结果,则返回 True,如果不包含,则返回 False。该函数试图以最简单、最快速的方式执行查询,但它 执行 的查询与普通的 QuerySet 查询几乎相同。

existence() 对于与 QuerySet 中任何对象的存在有关的搜索很有用,特别是在一个大的 QuerySet 的背景下。

要查找一个查询集是否包含任何项目:

if some_queryset.exists():
    print("There is at least one object in some_queryset")

这种方法比下面的方法更快:

if some_queryset:
    print("There is at least one object in some_queryset")

...但程度不高(因此需要大量的查询集才能获得效率提升)。

此外,如果一个 some_queryset 还没有被执行,但你知道它会在某个时候被执行,那么使用 some_queryset.exences() 比使用 bool(some_queryset) 会做更多的总体工作(一个存在性检查的查询加上一个额外的查询,以便以后检索结果),后者检索结果,然后检查是否有任何返回。

contains()

contains(obj)
New in Django 4.0.

如果 QuerySet 包含 obj,返回 True,如果不包含 False。这试图以最简单和最快的方式执行查询。

contains() 对于检查一个对象在 QuerySet 中的成员资格很有用,特别是在一个大的 QuerySet 的情况下。

要检查一个查询集是否包含一个特定的项目:

if some_queryset.contains(obj):
    print('Entry contained in queryset')

这将比以下需要执行和迭代整个查询集的方法更快:

if obj in some_queryset:
    print('Entry contained in queryset')

exists() 一样,如果 some_queryset 还没有被执行,但你知道它将在某个时候被执行,那么使用 some_queryset.contains(obj) 将进行额外的数据库查询,通常会导致整体性能变慢。

update()

update(**kwargs)

对指定的字段执行 SQL 更新查询,并返回匹配的行数(如果有些行已经有了新的值,则可能不等于更新的行数)。

例如,要关闭 2010 年发表的所有博客条目的评论,你可以这样做:

>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False)

(假定你的 Entry 模型有 pub_datecomments_on。)

你可以更新多个字段——没有数量限制。例如,这里我们更新了 comments_onheadline 字段:

>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old')

update() 方法是即时应用的,对被更新的 QuerySet 唯一的限制是只能更新模型主表中的列,不能更新相关模型。你不能这样做,比如:

>>> Entry.objects.update(blog__name='foo') # Won't work!

但仍可根据相关字段进行过滤:

>>> Entry.objects.filter(blog__id=1).update(comments_on=True)

你不能在一个 QuerySet 上调用 update(),因为它已经被取走了一个片断或者不能再被过滤。

update() 方法返回受影响的行数:

>>> Entry.objects.filter(id=64).update(comments_on=True)
1

>>> Entry.objects.filter(slug='nonexistent-slug').update(comments_on=True)
0

>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
132

如果你只是更新一条记录,不需要对模型对象做任何事情,最有效的方法是调用 update(),而不是将模型对象加载到内存中。例如,不要这样做:

e = Entry.objects.get(id=10)
e.comments_on = False
e.save()

...要这样做:

Entry.objects.filter(id=10).update(comments_on=False)

使用 update() 还可以防止在加载对象和调用 save() 之间的短暂时间内数据库中的某些东西可能发生变化的竞争条件。

最后,要知道,update() 是在 SQL 级别上进行更新,因此,它不会在模型上调用任何 save() 方法,也不会发出 pre_savepost_save 信号(这是调用 Model.save() 的结果)。如果你想为一个有自定义 save() 方法的模型更新一堆记录,在它们上面循环并调用 save(),像这样:

for e in Entry.objects.filter(pub_date__year=2010):
    e.comments_on = False
    e.save()
有序的查询集
New in Django 3.2.

order_by()update() 串联起来,只在 MariaDB 和 MySQL 上支持,对于不同的数据库会被忽略。这对于按照指定的顺序更新一个唯一的字段是很有用的,没有冲突。例如:

Entry.objects.order_by('-number').update(number=F('number') + 1)

备注

order_by() 子句如果包含注释、继承字段或跨越关系的查找,将被忽略。

delete()

delete()

QuerySet 中的所有行执行 SQL 删除查询,并返回删除的对象数量和每个对象类型的删除数量的字典。

delete() 是即时应用的。你不能对已经被取走一个片断或不能再被过滤的 QuerySet 调用 delete()

例如,要删除某个博客中的所有条目:

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

# Delete all the entries belonging to this Blog.
>>> Entry.objects.filter(blog=b).delete()
(4, {'blog.Entry': 2, 'blog.Entry_authors': 2})

默认情况下,Django 的 ForeignKey 模拟了 SQL 约束 ON DELETE CASCADE——换句话说,任何外键指向要删除的对象的对象都会被一起删除。例如:

>>> blogs = Blog.objects.all()

# This will delete all Blogs and all of their Entry objects.
>>> blogs.delete()
(5, {'blog.Blog': 1, 'blog.Entry': 2, 'blog.Entry_authors': 2})

这种级联行为通过 ForeignKeyon_delete 参数定义。

delete() 方法进行批量删除,并不调用模型上的任何 delete() 方法。但是,它确实为所有被删除的对象(包括级联删除)发出 pre_deletepost_delete 信号。

Django 需要将对象获取到内存中来发送信号和处理级联。但是,如果没有级联和信号,那么 Django 可能会采取快速路径删除对象,而不需要将其获取到内存中。对于大面积的删除,这可以使内存使用量大大降低。也可以减少执行查询的数量。

设置为 on_delete DO_NOTHING 的外键不会阻止在删除时采取快速路径。

需要注意的是,对象删除中产生的查询是一个实现细节,可能会发生变化。

as_manager()

classmethod as_manager()

类方法,该方法返回一个 Manager 的实例,其中包含 QuerySet 的方法的副本。详见 创建带有 QuerySet 方法的管理器

explain()

explain(format=None, **options)

返回一个 QuerySet 的执行计划的字符串,它详细说明了数据库将如何执行查询,包括将使用的任何索引或联接。了解这些细节可以帮助你提高慢速查询的性能。

例如,当使用 PostgreSQL:

>>> print(Blog.objects.filter(title='My Blog').explain())
Seq Scan on blog  (cost=0.00..35.50 rows=10 width=12)
  Filter: (title = 'My Blog'::bpchar)

不同数据库之间的输出有很大的不同。

explain() 得到了所有内置数据库后端的支持,但 Oracle 除外,因为在那里的实现并不直接。

format 参数改变数据库默认的输出格式,通常是基于文本的。PostgreSQL 支持 ''TEXT''JSON''YAML''XML' 格式。MariaDB 和 MySQL 支持 ''TEXT' (也叫 'TRADITIONAL')和 'JSON' 格式。MySQL 8.0.16+ 还支持改进的 'TREE' 格式,它类似于 PostgreSQL 的 'TEXT' 输出,如果支持的话,默认使用。

一些数据库接受可以返回更多查询信息的标志。将这些标志作为关键字参数传递。例如,当使用 PostgreSQL:

>>> print(Blog.objects.filter(title='My Blog').explain(verbose=True, analyze=True))
Seq Scan on public.blog  (cost=0.00..35.50 rows=10 width=12) (actual time=0.004..0.004 rows=10 loops=1)
  Output: id, title
  Filter: (blog.title = 'My Blog'::bpchar)
Planning time: 0.064 ms
Execution time: 0.058 ms

有些数据库接受的标志可以返回更多 在一些数据库上,标志可能会导致查询被执行,这可能会对你的数据库产生不利影响。 例如,MariaDB、MySQL 8.0.18+ 和 PostgreSQL 支持的 ANALYZE 标志,如果有触发器或调用函数,即使是 SELECT 查询,也可能导致数据的改变。

Field 查找

字段查找是指定 SQL WHERE 子句的方法。它们被指定为 QuerySet 方法 filter()exclude()get() 的关键字参数。

介绍见 模型和数据库查询文档

Django 的内置查找功能如下。也可以为模型字段写 自定义查找

为方便起见,当没有提供查找类型时(如 Entry.objects.get(id=14)),查找类型被假定为 exact

exact

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

举例:

Entry.objects.get(id__exact=14)
Entry.objects.get(id__exact=None)

SQL 等价于:

SELECT ... WHERE id = 14;
SELECT ... WHERE id IS NULL;

MySQL 比较

在 MySQL 中,数据库表的“字符序”设置决定了 act 比较是否区分大小写。这是一个数据库设置,而 不是 Django 设置。可以配置你的 MySQL 表来使用区分大小写的比较,但是会有一些折衷。关于这方面的更多信息,请参阅 数据库 文档中的 字符序部分

iexact

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

举例:

Blog.objects.get(name__iexact='beatles blog')
Blog.objects.get(name__iexact=None)

SQL 等价于:

SELECT ... WHERE name ILIKE 'beatles blog';
SELECT ... WHERE name IS NULL;

注意第一个查询会匹配 'Beatles Blog''beatles blog''BeAtLes BLoG' 等。

SQLite 用户

当使用 SQLite 后台和非 ASCII 字符串时,请记住 database note 中关于字符串比较的内容。SQLite 对非 ASCII 字符串不进行区分大小写的匹配。

contains

区分大小写的包含测试。

举例:

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

SQL 等价于:

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

请注意,这将与标题 'Lennon honored today' 相匹配,而不是 'lennon honored today'

SQLite 用户

SQLite 不支持区分大小写的 LIKE 语句;contains 的作用就像 SQLite 的 icontains。更多信息请参见 database note

icontains

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

举例:

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

SQL 等价于:

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

SQLite 用户

当使用 SQLite 后端和非 ASCII 字符串时,请记住 database note 中关于字符串比较的内容。

in

在一个给定的可迭代对象中;通常是一个列表、元组或查询集。这不是一个常见的用例,但字符串(可迭代)是可以接受的。

举例:

Entry.objects.filter(id__in=[1, 3, 4])
Entry.objects.filter(headline__in='abc')

SQL 等价于:

SELECT ... WHERE id IN (1, 3, 4);
SELECT ... WHERE headline IN ('a', 'b', 'c');

你也可以使用一个查询集来动态计算值列表,而不是提供一个字面值列表:

inner_qs = Blog.objects.filter(name__contains='Cheddar')
entries = Entry.objects.filter(blog__in=inner_qs)

该查询集将作为子选择语句执行:

SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')

如果你把一个由 values()values_list() 产生的 QuerySet 作为值传递给 __in 查找,你需要确保你只提取结果中的一个字段。例如,这样做就可以了(过滤博客名):

inner_qs = Blog.objects.filter(name__contains='Ch').values('name')
entries = Entry.objects.filter(blog__name__in=inner_qs)

这个例子会引发一个异常,因为内部查询正试图提取两个字段值,而预期只有一个:

# Bad code! Will raise a TypeError.
inner_qs = Blog.objects.filter(name__contains='Ch').values('name', 'id')
entries = Entry.objects.filter(blog__name__in=inner_qs)

性能考量

谨慎使用嵌套查询,了解你的数据库服务器的性能特点(如果有疑问,请做基准测试!)。一些数据库后端,最主要的是 MySQL,并不能很好地优化嵌套查询。在这些情况下,提取一个值的列表,然后将其传递到第二个查询中会更有效率。也就是说,执行两个查询而不是一个查询:

values = Blog.objects.filter(
        name__contains='Cheddar').values_list('pk', flat=True)
entries = Entry.objects.filter(blog__in=list(values))

注意 Blog QuerySet 周围的 list() 调用,以强制执行第一个查询。如果没有它,一个嵌套查询就会被执行,因为 QuerySet 是惰性的

gt

大于。

举例:

Entry.objects.filter(id__gt=4)

SQL 等价于:

SELECT ... WHERE id > 4;

gte

大于等于。

lt

小于。

lte

小于等于

startswith

区分大小写的开头为。

举例:

Entry.objects.filter(headline__startswith='Lennon')

SQL 等价于:

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

SQLite 不支持区分大小写的 LIKE 语句;startswith 的作用就像 SQLite 的 istartswith

istartswith

不区分大小写的开头为。

举例:

Entry.objects.filter(headline__istartswith='Lennon')

SQL 等价于:

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

SQLite 用户

当使用 SQLite 后端和非 ASCII 字符串时,请记住 database note 中关于字符串比较的内容。

endswith

区分大小写的结尾为。

举例:

Entry.objects.filter(headline__endswith='Lennon')

SQL 等价于:

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

SQLite 用户

SQLite 不支持区分大小写的 LIKE 语句;endswith 的作用类似于 SQLite 的 iendswith。更多内容请参考 database note 文档。

iendswith

不区分大小写的结尾为。

举例:

Entry.objects.filter(headline__iendswith='Lennon')

SQL 等价于:

SELECT ... WHERE headline ILIKE '%Lennon'

SQLite 用户

当使用 SQLite 后端和非 ASCII 字符串时,请记住 database note 中关于字符串比较的内容。

range

范围测试(含)。

举例:

import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))

SQL 等价于:

SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';

在 SQL 中,你可以在任何你可以使用 BETWEEN 的地方使用 range——用于日期、数字甚至字符。

警告

用日期过滤 DateTimeField 不会包括最后一天的项目,因为界限被解释为“给定日期的 0 点”。如果 pub_date 是一个 DateTimeField,上面的表达式就会变成这个 SQL:

SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00';

一般来说,你不能把日期和日期时间混在一起。

date

对于日期时间字段,将值投射为日期。允许链接其他字段的查找。取一个日期值。

举例:

Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))

(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。

USE_TZTrue 时,字段在过滤前会被转换为当前时区。这需要 数据库中的时区定义

year

对于日期和日期时间字段,精确匹配年份。允许链接其他字段的查询。取整数年。

举例:

Entry.objects.filter(pub_date__year=2005)
Entry.objects.filter(pub_date__year__gte=2005)

SQL 等价于:

SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31';
SELECT ... WHERE pub_date >= '2005-01-01';

(确切的 SQL 语法因每个数据库引擎而异)。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

iso_year

对于日期和日期时间字段,精确的 ISO 8601 周号年份匹配。允许链接其他字段的查询。取整数年。

举例:

Entry.objects.filter(pub_date__iso_year=2005)
Entry.objects.filter(pub_date__iso_year__gte=2005)

(确切的 SQL 语法因每个数据库引擎而异)。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

month

对于日期和日期时间字段,精确的月份匹配。允许链接其他字段的查询。取整数 1(1 月)到 12(12 月)。

举例:

Entry.objects.filter(pub_date__month=12)
Entry.objects.filter(pub_date__month__gte=6)

SQL 等价于:

SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12';
SELECT ... WHERE EXTRACT('month' FROM pub_date) >= '6';

(确切的 SQL 语法因每个数据库引擎而异)。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

day

对于日期和日期时间字段,精确匹配日期。允许链接其他字段的查询。取整数日。

举例:

Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3)

SQL 等价于:

SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3';
SELECT ... WHERE EXTRACT('day' FROM pub_date) >= '3';

(确切的 SQL 语法因每个数据库引擎而异)。

请注意,这将匹配任何带有 pub_date 的月份第三天的记录,如 1 月 3 日,7 月 3 日等。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

week

对于日期和日期时间字段,根据 ISO-8601 ,返回星期号(1-52 或 53),即星期从星期一开始,第一周包含一年的第一个星期四。

举例:

Entry.objects.filter(pub_date__week=52)
Entry.objects.filter(pub_date__week__gte=32, pub_date__week__lte=38)

(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

week_day

对于日期和日期时间字段,“星期几”匹配。允许链接其他字段的查询。

从 1(星期日)到 7(星期六)取一个整数值,代表一周的一天。

举例:

Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)

(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。

请注意,这将匹配任何带有 pub_date 的记录,这些记录都是在星期一(一周的第 2 天)发生的,不管它发生在哪一年哪一月。周日的索引,第 1 天是周日,第 7 天是周六。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

iso_week_day

对于日期和日期时间字段,精确匹配 ISO 8601 星期几。允许链接其他字段的查询。

取一个整数值,代表一周的 1(星期一)到 7(星期日)。

举例:

Entry.objects.filter(pub_date__iso_week_day=1)
Entry.objects.filter(pub_date__iso_week_day__gte=1)

(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。

请注意,这将匹配任何带有 pub_date 的记录,这些记录都是在星期一(一周的第 1 天)发生的,不管它发生在哪个月或哪个年。周日的索引是第 1 天是星期一,第 7 天是星期天。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

quarter

对于日期和日期时间字段,“一年的四分之一”匹配。允许链接额外的字段查找。取 1 到 4 之间的整数值,代表一年中的季度。

检索第二季度(4 月 1 日至 6 月 30 日)的条目示例:

Entry.objects.filter(pub_date__quarter=2)

(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

time

对于日期时间字段,将其值强制转换为时间。允许链式附加字段查找。取一个 datetime.time 的值。

举例:

Entry.objects.filter(pub_date__time=datetime.time(14, 30))
Entry.objects.filter(pub_date__time__range=(datetime.time(8), datetime.time(17)))

(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。

USE_TZTrue 时,字段在过滤前会被转换为当前时区。这需要 数据库中的时区定义

hour

对于日期时间和时间字段,精确的小时匹配。允许链式查找其他字段。取 0 到 23 之间的整数。

举例:

Event.objects.filter(timestamp__hour=23)
Event.objects.filter(time__hour=5)
Event.objects.filter(timestamp__hour__gte=12)

SQL 等价于:

SELECT ... WHERE EXTRACT('hour' FROM timestamp) = '23';
SELECT ... WHERE EXTRACT('hour' FROM time) = '5';
SELECT ... WHERE EXTRACT('hour' FROM timestamp) >= '12';

(确切的 SQL 语法因每个数据库引擎而异)。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

minute

对于日期时间和时间字段,精确的分钟匹配。允许链式查找其他字段。取 0 到 59 之间的整数。

举例:

Event.objects.filter(timestamp__minute=29)
Event.objects.filter(time__minute=46)
Event.objects.filter(timestamp__minute__gte=29)

SQL 等价于:

SELECT ... WHERE EXTRACT('minute' FROM timestamp) = '29';
SELECT ... WHERE EXTRACT('minute' FROM time) = '46';
SELECT ... WHERE EXTRACT('minute' FROM timestamp) >= '29';

(确切的 SQL 语法因每个数据库引擎而异)。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

second

对于日期时间和时间字段,完全秒配。允许链式查找其他字段。取 0 到 59 之间的整数。

举例:

Event.objects.filter(timestamp__second=31)
Event.objects.filter(time__second=2)
Event.objects.filter(timestamp__second__gte=31)

SQL 等价于:

SELECT ... WHERE EXTRACT('second' FROM timestamp) = '31';
SELECT ... WHERE EXTRACT('second' FROM time) = '2';
SELECT ... WHERE EXTRACT('second' FROM timestamp) >= '31';

(确切的 SQL 语法因每个数据库引擎而异)。

USE_TZTrue 时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义

isnull

TrueFalse,分别对应 IS NULLIS NOT NULL 的 SQL 查询。

举例:

Entry.objects.filter(pub_date__isnull=True)

SQL 等价于:

SELECT ... WHERE pub_date IS NULL;

regex

区分大小写的正则表达式匹配。

正则表达式语法是使用中的数据库后端的语法。对于没有内置正则表达式支持的 SQLite 来说,这个功能是由(Python)用户定义的 REGEXP 函数提供的,因此正则表达式语法是 Python 的 re 模块的语法。

举例:

Entry.objects.get(title__regex=r'^(An?|The) +')

SQL 等价于:

SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; -- MySQL

SELECT ... WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c'); -- Oracle

SELECT ... WHERE title ~ '^(An?|The) +'; -- PostgreSQL

SELECT ... WHERE title REGEXP '^(An?|The) +'; -- SQLite

建议使用原始字符串(例如,用 r'foo' 代替 'foo')来传递正则表达式语法。

iregex

不区分大小写的正则表达式匹配。

举例:

Entry.objects.get(title__iregex=r'^(an?|the) +')

SQL 等价于:

SELECT ... WHERE title REGEXP '^(an?|the) +'; -- MySQL

SELECT ... WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i'); -- Oracle

SELECT ... WHERE title ~* '^(an?|the) +'; -- PostgreSQL

SELECT ... WHERE title REGEXP '(?i)^(an?|the) +'; -- SQLite

聚合函数

Django 在 django.db.models 模块中提供了以下聚合函数。关于如何使用这些聚合函数的细节,请参见 关于聚合的主题指南。参见 Aggregate 文档,了解如何创建你的聚合函数。

警告

SQLite 无法处理日期/时间字段的聚合。这是因为 SQLite 中没有原生的日期/时间字段,而 Django 目前使用文本字段来模拟这些功能。试图在 SQLite 中使用日期/时间字段上的聚合将引发 NotSupportedError

注解

聚合函数在与空的 QuerySet 一起使用时,返回 None。例如,如果 QuerySet 不包含任何条目,Sum 聚合函数返回 None 而不是 0。要返回另一个值,请向 default 参数传递一个值。一个例外是 Count,如果 QuerySet 是空的,它将返回 0Count 不支持 default 参数。

所有聚合体都有以下共同的参数:

expressions

引用模型上的字段的字符串,字段的转换,或 查询表达式

Changed in Django 3.2:

增加了对字段变换的支持。

output_field

一个可选的参数,表示返回值的 模型字段

备注

当组合多个字段类型时,Django 只能在所有字段类型相同的情况下确定 output_field。否则,你必须自己提供 output_field

filter

一个可选的 Q 对象,用于过滤被聚合的行。

参见 条件聚合过滤注解 的用法示例。

default

New in Django 4.0.

一个可选的参数,允许指定一个值作为默认值,当查询集(或分组)不包含条目时使用。

**extra

关键字参数,可以为聚合生成的 SQL 提供额外的上下文。

Avg

class Avg(expression, output_field=None, distinct=False, filter=None, default=None, **extra)

返回给定表达式的平均值,除非指定不同的 output_field,否则必须是数值。

  • 默认别名:<field>__avg
  • 返回类型。如果输入是 int,则返回 float,否则与输入字段相同,如果提供了 output_field,则返回 ``output_field`。

包含一个可选参数:

distinct

如果 distinct=TrueAvg 返回非重复值的平均值。这相当于 SQL 中的 AVG(DISTINCT <field>)。默认值是 False

Count

class Count(expression, distinct=False, filter=None, **extra)

返回通过提供的表达式关联的对象数量。

  • 默认别名:<field>__count
  • 返回类型:int

包含一个可选参数:

distinct

如果 distinct=True,计数将只包括非重复的实例。这相当于 SQL 中的 COUNT(DISTINCT <field>)。默认值是 False

备注

不支持 default 参数。

Max

class Max(expression, output_field=None, filter=None, default=None, **extra)

返回给定表达式的最大值。

  • 默认别名:<field>__max
  • 返回类型:与输入字段相同,或 output_field 如果有。

Min

class Min(expression, output_field=None, filter=None, default=None, **extra)

返回给定表达式的最小值。

  • 默认别名:<field>__min
  • 返回类型:与输入字段相同,或 output_field 如果有。

StdDev

class StdDev(expression, output_field=None, sample=False, filter=None, default=None, **extra)

返回给定表达式中数据的标准差。

  • 默认别名:<field>__stddev
  • 返回类型。如果输入是 int,则返回 float,否则与输入字段相同,如果提供了 output_field,则返回 ``output_field`。

包含一个可选参数:

sample

默认情况下,StdDev 返回总体标准差。 但是,如果 sample=True,则返回值为样本标准差。

Sum

class Sum(expression, output_field=None, distinct=False, filter=None, default=None, **extra)

计算给定表达式的所有值的总和。

  • 默认别名:<field>__sum
  • 返回类型:与输入字段相同,或 output_field 如果有。

包含一个可选参数:

distinct

如果 distinct=True,则 Sum 返回非重复值的总和。 这与 SQL 的 SUM(DISTINCT <field>) 等效。 默认值为 False

Variance

class Variance(expression, output_field=None, sample=False, filter=None, default=None, **extra)

返回给定表达式中数据的方差。

  • 默认别名:<field>__variance
  • 返回类型。如果输入是 int,则返回 float,否则与输入字段相同,如果提供了 output_field,则返回 ``output_field`。

包含一个可选参数:

sample

默认情况下, Variance 返回总体方差。 但是,如果 sample=True,则返回值将是样本方差。

Back to Top