QuerySet API 参考

本篇文档包含 QuerySet API的详细信息. 它建立在 模型数据库查询 指南之上, 所以在阅读本文档之前, 你需要先阅读和理解这两部分的文档.

本文档将通篇使用在 数据库查询指南 中用到的 WebBlog模型例子.

QuerySet 何时求值

实际上, 当一个 QuerySet 被创建, 过滤, 切片和传递时并不会实际操作数据库. 在对查询集做求值之前, 不会产生任何实际的数据库操作.

QuerySet 求值有以下几种方式:

  • 迭代. QuerySet 是可迭代的, 当它被首次迭代是会执行数据库查询. 例如, 下面的语句会将数据库中所有Entry的headline打印出来:

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

    主要: 不要使用上面语句来验证数据库中是否存在某条记录, 使用 exists() 方法更高效.

  • 切片. 正如 QuerySet 的Limit 中描述一样, 可以使用Python的序列切片语法对 QuerySet 进行切片操作, 对一个未求值的 QuerySet 进行切片操作会返回另一个未求值的 QuerySet, 但是如果使用了 “step” 参数, Django将执行数据库查询, 然后返回查询结果的列表. 对已经求值过的 QuerySet 切片也是返回一个列表.

    需要注意的是, 对未求值的 QuerySet 切片返回的 QuerySet 不可以再进行修改操作(e.g., 新增过滤器, 或者修改排序). 因为这样不难再转化成SQL而且这样的需求没有实际意义.

  • Pickling/缓存. 有关 pickling QuerySets 的细节, 请参阅 pickling QuerySets 部份. 这里提到它的目的是强调序列化时会读取数据库.

  • repr(). 当对 QuerySet 调用 repr() 方法时会对其求值. 这是为了在Python交互式解释器中使用方便, 这样就可以在交互式解释器中使用这个API立即看到结果.

  • len(). 当对 QuerySet 调用 len() 方法时会对其求值. 正如猜想那样,会返回查询集的长度.

    注意: 如果只是想确认集合中的记录条数(而并不需要实际对象), 使用SQL的 SELECT COUNT(*) 来处理数据库级别的计数更有效. Django为此提供了 count() 方法.

  • list().QuerySet 调用 list() 可以对其进行强制求值, 例如:

    entry_list = list(Entry.objects.all())
    
  • bool(). 试探 QuerySet 的布尔值, 例如使用 bool(), or, and 或者 if 判断, 都触发求值操作. 如果结果至少包含一条记录, 则 QuerySetTrue, 否则为 False. 例如:

    if Entry.objects.filter(headline="Test"):
       print("Entry 中至少有一条记录的headline为Test")
    

    注意: 如果只是想确认结果中是否至少存在一条记录(并且不需要实际对象), 使用 exists() 方法更加高效.

Pickling QuerySet

如果对 QuerySet 进行 pickle 操作, 它将在Pickle之前强制将所有的结果加载到内存中. Pickling 通常用缓存之前, 当下次重新加载缓存的查询集时, 其结果已经就是能够直接使用的了(免去了再次从数据库读取的耗时). 也就是说当unpickle 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结果

QuerySets 的Pickle只能用于生成它们的Django版本中. 如果使用Django的版本N生成一个Pickle, 不保证这个Pickle在Django 的版本N+1中可以读取. Pickle不可用于归档的长期策略.

因为Pickle兼容性的错误很难诊断例如产生损坏的对象, 当试图Unpickle的查询集与Pickle时的Django 版本不同时,将引发一个 RuntimeWarning.

QuerySet API

下面是对 QuerySet 的正式定义:

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

通常使用 QuerySet 时会以 链式过滤 来使用. 因此大部分 QuerySet 方法返回的是一个新的查询集. 本节将会详细介绍这些方法.

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

ordered

如果 QuerySet 是有序的则为 True — 例如 order_by() 子句或者模型默认的排序. 否则为 False .

db

如果执行查询, 将使用该数据库.

注解

QuerySet 存在 query 参数是为了让具有特殊查询用途的子类如 GeoQuerySet 可以重新构造内部查询状态. 这个参数的值是查询状态的不透明表示, 不是一个公开的API. 简而言之:如果你有疑问,其实你实际上不需要使用它.

返回新 QuerySet 的方法

Django提供了一系列的 QuerySet 筛选方法,用于修改 QuerySet 返回的结果类型或者SQL的查询方式.

filter()

filter(**kwargs)

返回一个新的包含满足查询参数的 QuerySet 对象.

查询参数(**kwargs) 必须满足下文 Field 查询 的格式.

如果需要更复杂的查询 (例如 OR 语句), 可以使用 Q查询.

exclude()

exclude(**kwargs)

返回一个新的不包含满足查询参数的 QuerySet 对象.

查询参数(**kwargs) 必须满足下文 Field 查询 的格式. 在底层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')

下面示例排序所有 whose 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查询.

annotate()

annotate(*args, **kwargs)

使用 查询表达式 注解 QuerySet 中的每个对象. 表达式可以是简单的值、模型或关联模型的字段引用,或者是对与 QuerySet 中对象相关的对象进行计算的聚合表达式(平均值、总和等).

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

Django提供的聚合函数在下文的 聚合函数 文档中有详细介绍.

关键字参数指定的注解将使用关键字作为注解的别名. 匿名参数的别名将基于聚合函数的名称和模型的字段生成. 只有引用单个字段的聚合表达式才可以使用匿名参数. 其它所有形式都必须用关键字参数.

例如,如果操作一个Blog列表,如果想知道每个Blog有多少Entry:

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

有关聚合的深入讨论,参考 聚合主题指南.

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

如果 Blog 设置了 ordering = ['name'], 那么第一个查询等价于:

Entry.objects.order_by('blog__name')

通过引用相关字段的 _id , 同样可以通过相关字段来排序查询集,而不会导致JOIN开销:

# No Join
Entry.objects.order_by('blog_id')

# Join
Entry.objects.order_by('blog__id')
你也可以通过 查询表达式 调用

asc() 或者 desc() 排序:

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

当使用关联模型排序还使用到了 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 可能有多个排序数据;具有多个 children 的每个 Event 将被多次返回到 order_by() 创建的新的 QuerySet 中. 换句话说, 用 order_by() 方法对 QuerySet 对象进行操作会返回一个扩大版的新 QuerySet 对象——新增的条目也许并没有什么用,你也用不着它们.

因此,当使用多值字段对结果进行排序时要格外小心. 如果 可以确保每个排序项只有一个排序数据, 这种方法不会出现问题. 如果不确定,请确保结果是你期望的.

是没有方法指定排序是否对大小写敏感. 对于大小写的敏感性, 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() 方法用于反向排序QuerySet中的元素. 再次调用 reverse() 将恢复原有排序.

比如要获取QuerySet中的最后五个元素,可以这样:

my_queryset.reverse()[:5]

注意, 这和Python中的在列表末尾切片不一样. 上面例子将先返回最后一个元素,然后是倒数第二个,依次类推. 如果在Python序列中调用 seq[-5:], 我们将先看到返回的倒数第五个元素. Django 并不支持这种模式访问(从末尾切片), 因此这不好在SQL中高效实现.

同时, reverse() 也只能在定义了ordering的 QuerySet 上调用 (e.g., 一个定义了默认排序的模型,或者使用了 order_by() 方法). 如果给定的 QuerySet 没有定义这样的ordering,那么调用 reverse() 就没有实际效果 (reverse() 之前没有定义ordering, 之后也将保持未定义).

distinct()

distinct(*fields)

返回一个在SQL查询中使用 SELECT DISTINCT 句子的 QuerySet. 它将去除查询结果中重复的行.

默认情况下, QuerySet 不会进行去重操作. 而在实际情况中, 这一般不会有问题, 因为像 Blog.objects.all() 这样简答的查询不会引入重复的行. 但是, 在跨多表查询时, QuerySet 可能就会包含重复的结果. 这时候就应该使用 distinct().

注解

order_by() 调用中使用的任何字段都包含在SQL的 SELECT 列当中. 当和 distinct() 一起使用时,可能会导致意料之外的结果. 如果根据关联模型的字段排序,那么这个字段将被添加到查询字段中,这样它们可能会使其他本来是重复的行看起来不同了. 而由于这个额外的字段不会出现在返回的结果中(它们只用于排序),所以这时看起来返回的结果并不正确.

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

上面的意思是,如果您使用的是 distinct() ,那么使用相关模型字段排序时一定得小心。同样, 当将 distinct()values() 一起使用时,请注意字段在不在 values() 中。

仅在PostgreSQL上, 可以传递位置参数(*fields), 用来指定 DISTINCT 应该应用到的字段的名称. 转换为SQL查询上的 SELECT DISTINCT ON. 区别于其他的普通 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 模型按定义了一个 按 nameordering:

Entry.objects.order_by('blog').distinct('blog')
…将不会生效, 因为查询时将会使用 blog_name 排序, 这与 DISTINCT ON 表达式不匹配. 这种情况下必须使用关联 _id 字段
(该例中为 blog_id ) 或者引用的字段(blog__pk) 显式排序, 保证两个表达式匹配.

values()

values(*fields)

返回一个 QuerySet ,该 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'}]>

值得注意的几点:

  • 如果有一个名为 fooForeignKey 字段, 那么调用默认的 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() 都将忽略其selected的额外字段.

  • values() 之后调用 only()defer() 不太合理, 因此这么做会引发 NotImplementedError.

当只需要少量可用字段的值, 并且不需要模型实例对象的功能时, 只选择需要使用的字段会更有效.

最后, 可以在 values() 调用之后调用 filter()order_by() 等, 这意味着下面这两个调用是相同的:

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

Django的开发者喜欢将所有影响sql的方法放在前面(可选), 然后才是影响输出的方法(例如 values() ), 但是实际上无所谓, 这是卖弄你个性的好机会.

还可以通过 OneToOneField, ForeignKeyManyToManyField 属性来引用具有反向关系的相关模型的字段:

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

警告

因为 ManyToManyField 字段和反向关联关系可以有多个关联的行, 包括这些行可能会使结果集倍数放大.如果在 values() 查询中包含多个此类字段,这会特别明显, 在这种情况下,将返回所有可能的组合.

values_list()

values_list(*fields, flat=False)

它与 values() 非常类似, 只是在迭代时返回的是元组而不是字典. 每个元组包含传递到 values_list() 的相应字段的值——因此第一个项是第一个字段, etc. 例如:

>>> Entry.objects.values_list('id', 'headline')
[(1, 'First entry'), ...]

如果只传递了一个字段, 可以使用 flat 参数. 如何设置为 True, 返回的结果将会是单个值而不是元组, 下面的例子更容易理解其作用:

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

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

如果传入多个字段同时设置了 flat 时将产生错误.

如果没有向``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')
[('Noam Chomsky', 'Impressions of Gaza'),
 ('George Orwell', 'Why Socialists Do Not Believe in Fun'),
 ('George Orwell', 'In Defence of English Cooking'),
 ('Don Quixote', None)]

具有多个entry的Author会多次出现,而没有任何entry的Author则是 None.

类似地, 当查询反向外键时. 对于没有entry的Author仍然是 None

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

dates()

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

返回一个计算结果为 datetime.date 列表的 QuerySet. 内容是 QuerySet 中某一特定类型的所有可用日期.

field 是模型中 DateField 字段的名称. kind 接受 "year""month" 或者 "day" 参数. 结果中每个 datetime.date 对象都会返回按指定的 type 截断的结果.

  • "year" 返回字段的所有不同年份值的列表.
  • "month" 返回字段的所有不同 year/month 的列表.
  • "day" 返回字段的所有不同 year/month/day 的列表.

order, 默认为 'ASC', 接受 'ASC''DESC' 两种参数. 用于指定排序方式.

Examples:

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

返回一个计算结果为 datetime.datetime 列表的 QuerySet. 内容是 QuerySet 中某一特定类型的所有可用日期.

field_name 是模型中 DateTimeField 字段的名称.

kind 接受 "year", "month", "day", "hour", "minute""second" 参数. 结果中每个 datetime.datetime 对象都会返回按指定的 type 截断的结果.

order, 默认为 'ASC', 接受 'ASC''DESC' 两种参数. 用于指定排序方式.

tzinfo 定义在截断之前将数据时间转换到的时区. 这取决于使用的时区. 此参数必须是 datetime.tzinfo 对象. 如果传入为 None, Django 会使用 当前时区. 当 USE_TZ 设置为 False 时该项无效.

注解

此函数直接在数据库中执行时区转换。因此,您的数据库必须能够解析 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 的一个 copy (或者 QuerySet 子类). 它可以用于当你需要传入模型管理器或者对结果做进一步过滤. 无论以哪种方式调用 all() , 都可以获得一个可以正常工作的 QuerySet.

QuerySet求值, Django会缓存其结果. 如果在此之后数据库中的值发生了改变. 可以通过调用求值前调用的 all() 来获取更新后的数据.

extra()

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

有时候, Django查询语法不能很好地表达复杂的 WHERE 子句. 对于这些边缘情况, Django提供了 QuerySet extra() 修饰符—— 一个将特定的子句注入 QuerySet 生成的SQL的钩子.

这是实在没有办法的情况下使用的方法

这是一个旧的API, 我们的目标是在将来的某个时候弃用. 仅当您无法使用其他查询方法表达您的查询时才使用它. 如果您确实需要使用它,请 file a ticket 您用例中的 QuerySet.extra keyword (请先检查现有ticket列表是否已存在), 以便我们可以增强QuerySet API,最终移除 extra() 方法. 我们不再为这个方法改进或修复bug.

例如这样是使用 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注入保护.

由于产品差异的原因,这些自定义的查询难以保障在不同的数据库之间兼容(因为你手写SQL代码的原因), 而且违背了DRY原则,所以如非必要,还是尽量避免写 extra.

extra 可以指定一个或多个 where,``select``,``params`` 或者 tables. 这些参数都不是必须的,但是至少要使用一个.

  • select

    select 参数可以让你在 SELECT 子句中添加其他字段信息, 它是一个字典, 存放着属性名到SQL子句的映射.

    例如:

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

    这样结果集中每个 Entry 都带有一个额外属性 is_recent, 它是一个布尔值,表示 pub_date 是在 Jan. 1. 2006 之后.

    Django 会直接在 SELECT 中加入对应的SQL片段, 所以上面例子的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'
        },
    )
    

    在这个特例中, 需要了解一个事实, 就是 blog_blog 表已经存在于 FROM 从句中.

    上面例子的执行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 参数. 由于 select_params 是一个序列. 并且 select 属性是字典,因此需要注意使参数与额外的选择片段正确匹配. 在这种情况下, 需要 collections.OrderedDict 作为 select 值, 而不仅仅是普通的Python字典.

    比如下面例子:

    Blog.objects.extra(
        select=OrderedDict([('a', '%s'), ('b', '%s')]),
        select_params=('one', 'two'))
    

    如果需要在select字符串中使用文本 %s, 请使用 %%s.

  • where / tables

    可以使用 WHERE 显式定义SQL where 子句. 您可以通过 FROM 手动将表添加到SQL tables 子句.

    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会假定您希望该表包含额外的时间(如果已包括). 这会产生一个问题, 因为表名将会被赋予一个别名. 如果表在SQL语句中多次出现, 则第二次和后续出现必须使用别名,以便数据库可以区分它们. 如果在 where 参数中指定了添加的额外表,这将导致错误.

    通常,您只需添加尚未显示在查询中的额外表. 然而,如果发生上述情况,则有几种解决方案. 首先,看看你是否可以不包括额外的表,并使用已经在查询中的一个. 如果不可能, 请将 extra() 调用放在查询集结构的前面, 以便您的表是该表的第一次使用. 最后, 如果所有失败,请查看生成的查询并重写 where 添加以使用给您的额外表的别名. 每次以相同的方式构造查询集时,别名将是相同的,因此您可以依靠别名不更改.

  • order_by

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

    例如:

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

    这会将所有 is_recent 为true的项排到最前面(在降序排列中 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")

查询集中 deferred 字段仍会返回在模型实例中. 当访问该字段时才会从数据库中检索.(每次只检索一个, 而不是一次性检索所有 deferred 字段).

可以多次调用 defer(). 每次调用都会添加新的字段的 deferred 集:

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

字段添加到 deferred 集的顺序无关紧要. 对已经在 deferred 集中的字段再次调用 defer() 也没有影响 (该字段仍然是 deferred).

可以 延迟 加载关联模型中的字段(如果关联模型是通过 select_related() 加载的), 方法是使用标准的双下划线表示法来分离关联字段:

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

如果要清除 deferred 字段, 调用 defer() 传入一个 None 参数即可

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

模型中有些字段即使设置了延迟加载也不会延迟, 比如永远不能延迟加载主键. 如果使用 select_related() 检索关联模型, 则不能延迟加载从主模型连接到关联模型的字段, 否则会抛出异常.

注解

defer() 方法(及其表亲, 下文中的 only())仅适用于高级用例. 它们用于提供一种优化, 当你仔细分析查询并且完全了解需要什么信息, 知道返回需要的字段与返回模型的全部字段之间的区别非常重要.

即使你认为你是在这种情况下, 只有当你在查询集加载时不能确定是否需要额外的字段时使用 defer(). 如果你经常加载和使用特定的数据子集, 最好的选择是规范你的模型, 将不加载的数据放入单独的模型(或数据库表). 如果列由于某种原因必须保留在一个表中, 请创建一个具有 Meta.managed = False (请参阅 managed attribute 文档)的模型, 只包含你通常需要加载和使用的, 否则就调用 defer() 的字段. 这可以使你的代码对读者更加清晰, 并且在Python进程中消耗更少的内存,加载稍微更快一些.

例如,这两个模型使用相同的底层数据库表:

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'

# 两个查询等价:
CommonlyUsedModel.objects.all()
ManagedModel.objects.all().defer('f2')

如果需要在非托管(unmanaged)模型中复制多个字段, 最好使用共享字段创建一个抽象模型, 然后让非托管模型和托管模型从抽象模型继承.

注解

当对具有延迟(deferred)字段的实例调用 save() 时, 仅保存加载的字段. 有关详细信息,请参见 save().

only()

only(*fields)

only() 方法或多或少与 defer() 相反. 你可以在检索模型时在 延迟加载的字段使用过它. 如果你的模型几乎所有的字段都需要延迟加载, 那么使用 only() 来指定加载字段会使代码变得简单.

假设你有一个包含 name, agebiography 三个字段的模型. 就延迟加载来说, 下面两个querysets等价:

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

只要调用 only(),它就会立即替换要加载的字段集. 该方法的名称可以帮助记忆: 只有 那些字段被立即加载;其余的延迟. 因此, 对 only() 的连续调用只会考虑最终字段:

# 这会延迟加载除了headline的所有字段.
Entry.objects.only("body", "rating").only("headline")

由于 defer() 以递增方式运行(向延迟列表中添加字段), 因此你可以组合 only()defer() 调用, 使它们合乎逻辑地工作.:

# 最终除了 "headline" 都被延迟加载
Entry.objects.only("headline", "body").defer("body")

# 最终结果立即加载标题和正文
# (only()替换字段集中任何字段).
Entry.objects.defer("body").only("headline", "body")

defer() 文档注释中的所有注意事项也适用于 only(). 请谨慎使用它, 只有在没有其它选择时才使用.

仅使用 only() 时用 select_related() 省略字段也会导致错误.

注解

当对具有延迟(deferred)字段的实例调用 save() 时, 仅保存加载的字段. 有关详细信息,请参见 save().

using()

using(alias)

如果使用多个数据库,该方法用于控制 QuerySet 在哪个数据库上求值. 该方法的唯一参数是数据库的别名,定义在 DATABASES 中.

例如:

# 使用别名为 'default' 的数据库.
>>> Entry.objects.all()

# 使用别名为 'backup' 的数据库
>>> Entry.objects.using('backup')

select_for_update()

select_for_update(nowait=False)

返回一个锁住行直到事务结束的查询集, 如果数据库支持,它将生成一个 SELECT ... FOR UPDATE 语句.

例如:

entries = Entry.objects.select_for_update().filter(author=request.user)

所有匹配的行将被锁定,直到事务结束.这样可以通过锁防止数据被其它事务修改.

通常,如果所选的行已经被另一个事务锁住,那么查询将被阻塞,直到释放锁为止. 如果你不希望这样,那么可以调用 select_for_update(nowait=True). 这将使调用非阻塞方式.如果冲突锁已经被另一个事务获取, 则在计算queryset时将引发 DatabaseError.

目前, postgresqloraclemysql 数据库后端都支持 select_for_update(). 但是, MySQL 不支持 nowait 参数. 所以, 使用其他第三方数据库后端的用户应该查看下他们的文档以了解这一细节.

使用像MySQL这种不支持 nowait 参数的数据库, 在调用 select_for_update() 时传入 nowait=True 会导致 DatabaseError . 这是为了防止代码被意外阻塞.

如果在自动提交模式下在支持 SELECT ... FOR UPDATE 的数据库后端使用 select_for_update() 会导致 TransactionManagementError 错误. 因为这种情况下行不会被锁定. 如果允许这种调用可能会造成数据损坏, 而且这也很有可能在事务外被调用.

select_for_update() 使用在不支持 SELECT ... FOR UPDATE 的数据库后端(比如 SQLite) 将没有效果. SELECT ... FOR UPDATE 不会被添加到查询中, 并且在自动给提交模式下使用 select_for_update() 也不会报错.

警告

虽然 select_for_update() 在自动提交模式下通常会失败, 因为 TestCase 会自动将每个测试包装在一个事务中, 即使在 atomic() 块之外调用 TestCaseselect_for_update() 也会在(可能会有意外)不引发 TransactionManagementError 的情况下通过. 但是要正确地测试 select_for_update(), 请务必使用 TransactionTestCase.

raw()

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

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

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

警告

raw() 永远都是触发一个新的查询,和之前的filter无关. 因此通常应该是从``Manager`` 或者新的``QuerySet`` 实例调用.

不返回 QuerySet 的方法

下面的 QuerySet 方法计算 QuerySet 但返回的 不是 QuerySet.

这些方法不会使用 (see QuerySet 缓存). 当然, 这些方法每次被调用都会查询数据库.

get()

get(**kwargs)

返回根据查询参数匹配到的对象, 参数格式应该符合 Field 查询 要求.

如果 get() 匹配到多个对象将会抛出 MultipleObjectsReturned. MultipleObjectsReturned 异常是模型类的属性.

如果 get() 根据查询参数没有匹配到对象将会抛出 DoesNotExist 异常. 这个异常也是模型类的属性.

例子:

Entry.objects.get(id='foo') # raises Entry.DoesNotExist

DoesNotExist 异常继承自 django.core.exceptions.ObjectDoesNotExist, 因此可以同时捕获多个so you can target multiple DoesNotExist 异常. 例如:

from django.core.exceptions import ObjectDoesNotExist
try:
    e = Entry.objects.get(id=3)
    b = Blog.objects.get(id=1)
except ObjectDoesNotExist:
    print("Either the entry or blog doesn't exist.")

如果希望queryset返回一行, 则可以使用 get() 而不使用任何参数来返回该行对象:

entry = Entry.objects.filter(...).exclude(...).get()

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 在其他的文档中有介绍, 它意味着一个新的对象一定会被创建. 正常情况下,你不必担心这点. 然而, 如果你的model中有一个手动设置主键,并且这个值已经存在于数据库中, 调用 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()

如果模型的字段数量较多的话,这种模式就不好用了.上面的例子可以用 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 的元组. 如果查到了多个对象, get_or_create 将引发 MultipleObjectsReturned 异常. 如果 没有 查到对象, get_or_create() 将实例化一个新对象并保存, 返回包含这个新对象和 True 的元组. 新对象按如下逻辑创建:

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

用文字描述就是, 以任何不包含双下划线的非 'defaults' 关键字参数开始(双下划线表示非准确的查找). 然后添加 defaults 的内容,必要时会覆盖原键值, 将结果用作模型类的关键字参数. 如上所述,这是对所使用算法的简单描述, 但是它包含了所有相关的细节. 只是其内部实现有更多的错误检查,和处理一些额外的边缘条件; 如果您感兴趣,请阅读代码.

如果你有一个名为 defaults 的字段,想用 get_or_create() 对其精准查找, 可以使用 'defaults__exact', 像这样:

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

get_or_create() 方法和 create() 有相同的错误行为, 如果你手动指定了主键, 并且查询的对象需要创建且数据库中主键已经重复, 则会导致 IntegrityError 异常.

这种方法是原子性的, 假设正确使用底层数据库、数据库配置没有问题和其他行为都正确. 但是, 如果在调用 get_or_create 中使用的 kwargs 在数据库级别上没有强制唯一性 (请参阅 uniqueunique_together ), 那么该方法很容易出现竞争条件, 导致同时插入具有相同参数的多行.

如果你使用的是Mysql数据库, 请使用 READ COMMITTED 隔离级别而不是 REPEATABLE READ (默认的), 否则可能会出现 get_or_create 抛出 IntegrityError 异常, 但使用 get() 调用却没有对象.

最后在讲一点,在Django视图中使用 get_or_create() 时. 请一定只在 POST 请求中使用, 除非你有很充分的理由. GET 请求不应该去修改数据. 而 POST 则用于修改数据.有关信息请参考HTTP规范中的 Safe methods.

警告

你可以通过 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 的 chapter字段使用 get_or_create(), 但是它只会获取该Book 内部的上下文:

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

发生这个错误是因为它尝试通过Book “Ulysses” 获取或者创建“Chapter 1”, 但它是不可以的: 关联关系不能获取这个chapter, 因为它与这个book不关联, 但因为 title 字段是唯一的,所以它也不能创建.

update_or_create()

update_or_create(defaults=None, **kwargs)

一个通过给定 kwargs 参数来更新对象的快捷方法, 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() 描述的一样, 该方法容易导致竞争条件, 如果在数据库级别没有设置强制唯一性, 则会导致同时插入多个行.

bulk_create()

bulk_create(objs, batch_size=None)

该方法提供了一个批量操作用于同时向数据库中插入一组对象(无论有多少对象, 通常只做一次查询):

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

不过, 有几点需要注意:

  • 模型的 save() 方法不会被调用, 并且 pre_savepost_save 信号也不会被发送.
  • 它不适用于多表继承场景中的子模型.
  • 如果模型的主键是 AutoField , 那么它不会像 save() 方法那样自动检索并设置主键, 除非使用的数据库后端支持(当前只有 PostgreSQL).
  • 它不适用于多对多关系.
Changed in Django 1.9:

新增 bulk_create() 对代理模型(proxy models)支持.

Changed in Django 1.10:

新增PostgreSQL数据库使用 bulk_create() 插入数据时设置主键.

参数 batch_size 用于控制单次创建的对象数. 默认情况下, 除SQLite外一次性创建所有对象, SQLite单次最多支持999个.

count()

count()

返回数据查询结果集 QuerySet 中的对象数量. count() 不会抛出异常.

例子:

# 返回数据库中所有条目数量.
Entry.objects.count()

# 返回数据库中headline包含'Lennon'的条目数量
Entry.objects.filter(headline__contains='Lennon').count()

count() 在后台执行的是 SELECT COUNT(*) , 因此请务必使用 count() 而不是将所有记录加载成Python对象然后调用 len() 函数 (除非你有其他需要必须要将其加载到内存中, 这种情况 len() 会比较快).

根据使用的数据库不同 (e.g. PostgreSQL vs. MySQL), count() 方法可能返回的是一个长整型而不是Python整数.

注意,如果你想计算 QuerySet 中的项目个数并且希望遍历每个数据对象(例如, 通过迭代它), 使用 len(queryset) 将会更好, 它不会像 count() 产生额外的数据库查询.

in_bulk()

in_bulk(id_list=None)

接收一个主键组成的列表, 返回一个字典, 字典的key为传入的主键ID, value为匹配的对象实例. 如果不传入id_list, 那么将返回整个查询集的内容.

例子:

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

如果传入空列表到 in_bulk() , 将返回一个空字典.

Changed in Django 1.10:

在此之前版本, id_list 为必传参数.

iterator()

iterator()

计算 QuerySet (执行数据库查询) 返回一个迭代器 (see PEP 234). 通常 QuerySet 会在其内部缓存结果来防止重复查询. 相反 iterator() 会直接读取结果不会在 QuerySet 级别执行缓存操作. 对于返回大量对象且只查询一次的 QuerySet, 这可以带来更好的性能并显着降低内存.

注意对已经求值过的 QuerySet 调用 iterator() 会使其强制再计算, 导致重复查询.

另外, iterator() 会导致已调用的 prefetch_related() 方法被忽略.

警告

一些Python的数据库驱动比如 psycopg2 使用客户端游标执行缓存(实例化 connection.cursor() 配合 Django’s ORM使用). 使用 iterator() 不会影响到数据库层级的缓存. 如果要禁用此缓存, 请查看 服务端游标.

latest()

latest(field_name=None)

接收一个 field_name 日期字段, 返回表中最新的一个对象.

下面例子返回表中 pub_date 最近的一个 Entry

Entry.objects.latest('pub_date')

如果在模型的 Meta 中指定了 get_latest_by, 则可以调用 earliest()latest() 不传入 field_name . Django 将默认使用 get_latest_by 指定的字段.

get() 方法一样, 如果查不到有效对象 earliest()latest() 也会抛出 DoesNotExist 异常.

注意, earliest()latest() 的存仅是为了方便和可读性.

earliest()latest() 可以返回日期为null的实例.

因为排序是在数据库中执行的, 如果使用了不同的数据库返回的null值顺序可能会不同. 比如PostgreSQL和MySQL中的null值的顺序高于非null值,而在SQLite中则相反.

可以像这样过滤掉非空值:

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

earliest()

earliest(field_name=None)

latest() 一样, 除了方向相反.

first()

first()

返回结果集中的第一个对象, 如果没有查询到内容则返回 None. 如果 QuerySet 没有设置排序, 则默认按照主键排序.

例子:

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提供的所有聚合函数在下文的 聚合函数 中查看. 因为聚合也是 查询表达式, 因此可以结合多个聚合创建复杂的聚合.

聚合时使用了关键字参数将以关键字为名称返回. 如果是匿名参数将以聚合函数的名称和聚合字段的名称组合为名字返回. 复杂聚合不支持匿名参数必须指定关键字参数.

例如, 在博客的例子中查询作者名下的博文数量:

>>> 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 查询几乎相同.

exists() 对搜索 QuerySet 以及其关联对象是否存在相当有用, 特别是对于体量比较大的 QuerySet.

查找一个具有唯一字段(e.g. primary_key)模型的 QuerySet 中是否具有指定成员的最高效方式:

entry = Entry.objects.get(pk=123)
if some_queryset.filter(pk=entry.pk).exists():
    print("Entry contained in queryset")

它会比下面这种求值再遍历的方式快很多:

if entry in some_queryset:
   print("Entry contained in 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.exists() 会比直接使用 bool(some_queryset) 做多余的工作. 后者会求值并检查是否有结果.

update()

update(**kwargs)

对指定字段执行更新语句返回受影响的行数(如果某些行已具备新值, 则可能不等于更新的行数).

例如, 对2010年发布的博客启用评论:

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

(假设 Entry 模型具有 pub_datecomments_on 字段.)

update没有数量限制可以同时更新多个字段. 例如, 同时更新 comments_onheadline 字段:

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

update() 方法是立即执行的, QuerySet update的唯一限制是它只可以更新模型主表中的字段, 不可以更新关联模型. 例如下面这种:

>>> 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 信号. 如果业务需要调用模型自己的 save() 方法, 那么请遍历结果集调用 save(), 例如:

for e in Entry.objects.filter(pub_date__year=2010):
    e.comments_on = False
    e.save()

delete()

delete()

执行SQL删除语句, 删除 QuerySet 所有行, 返回删除数量和每个删除的对象与其数量组成的字典.

delete() 是立即生效的. 不能对已切片和不能过滤的 QuerySet 调用t delete() 方法.

例如, 删除指定博客的所有条目:

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

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

新增返回删除对象及数量情况.

Django的 ForeignKey 仿效了SQL的 ON DELETE CASCADE 约束, — 换句话讲, 默认情况下任何对象别删除时与其关联的外键对象也会被删除. 例如:

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

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

这个行为可以通过 ForeignKeyon_delete 参数进行设置.

delete() 方法是批量删除, 它不会调用模型的 delete() 方法, 但是会为每一个删除的对象触发 pre_deletepost_delete 信号 (包含级联删除).

Django需要将对象加载到内存才可以处理级联和发送信号. 但是, 如果没有级联处理和信号操作, Django会采取快速方式删除对象而不需要加载到内存. 对于大型的查询这可以减少内存消耗和执行查询的量.

ForeignKeys on_delete 设置为 DO_NOTHING 时不会阻止快速删除.

注意,在对象删除中生成的查询是具体实现, 可能会有更改.

as_manager()

classmethod as_manager()

类方法, 返回一个带有 QuerySet 方法的 Manager 实例. 有关详细信息请参考 通过 QuerySet 方法创建管理器.

Field 查询

Field查询就是SQL中的 WHERE 语句. 它们以 QuerySetfilter() 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中, 数据表的 “COLLATE” 设置项会影响到 exact 查询时大小写敏感. 这属于数据库的设置, 不是 Django的设置. 这会影响到查询时的大小写敏感, 但也有解决方案. 参考 数据库 文档中的 collation section.

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数据库和Unicode(非ASCII)字符时, 请注意 数据库备注 中的字符串比较. SQLite在匹配Unicode字符时大小写不敏感.

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 一样. 详见 数据库备注 .

icontains

大小写不敏感的包含查询.

例如:

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

等价于SQL:

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

SQLite用户

当使用SQLite数据库和Unicode(非ASCII)字符时, 请注意 数据库备注 中的字符串比较.

in

存在于给定列表中.

例如:

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

等价于SQL:

SELECT ... WHERE id IN (1, 3, 4);

这里并不是一定要传入一个具有明确值的列表, 也可以嵌套传入一个查询集来查询:

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

如果传入的 QuerySet 调用了 values() 或者 values_list() 作为 __in 查询的参数, 那么你需要确保只提取了一个字段 例如, 下面这种做法是正确的:

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

下面这种会抛出一个异常, 因为 __in 查询只需要一个字段确提取了两个:

# 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='Will')

等价于SQL:

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

SQLite不支持大小写敏感的 LIKE 语句; SQLite下 startswith 被作为 istartswith 执行.

istartswith

大小写不敏感的前缀查询.

例如:

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

等价于SQL:

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

SQLite用户

当使用SQLite数据库和Unicode(非ASCII)字符时, 请注意 数据库备注 中的字符串比较.

endswith

大小写敏感的后缀查询.

例如:

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

等价于SQL:

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

SQLite用户

SQLite不支持大小写敏感的 LIKE 语句; SQLite下 endswith 被作为 iendswith 执行. 请注意 数据库备注 中的字符串比较.

iendswith

大小写不敏感的后缀查询.

例如:

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

等价于SQL:

SELECT ... WHERE headline ILIKE '%will'

SQLite用户

当使用SQLite数据库和Unicode(非ASCII)字符时, 请注意 数据库备注 中的字符串比较.

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 不会包含最后一天的数据, 因为查询范围的边界值会被解释成”给定日期的零点”. 比如如果 pub_date 的类型为 DateTimeField, 那么上面的查询就会变成这样的SQL:

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

一般来讲, 不要把date和datetime混在一起使用.

date

New in Django 1.9:

接收日期值. datetime字段会被转换为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_TZ 设置为 True 时, 执行过滤前字段会被转换为当前的时区.

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_TZ 设置为 True 时, datetime字段会在过滤前转换到当前时区.

Changed in Django 1.9:

允许跟上额外查询.

month

接收1 (一月)至12 (十二月) 的整数. 用于date和datetime字段精确匹配月份. 允许跟上额外查询

例如:

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_TZ 设置为 True 时, datetime字段会在过滤前转换到当前时区. 这需要 数据库时区设置.

Changed in Django 1.9:

允许跟上额外查询.

day

接收整型天数, 用于date和datetime字段精确匹配天数. 允许跟上额外查询.

例如:

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语句因数据库而异.)

注意这会匹配出所有月份3号的记录, 比如1月3号,7月3号等.

USE_TZ 设置为 True 时, datetime字段会在过滤前转换到当前时区. 这需要 数据库时区设置.

Changed in Django 1.9:

允许跟上额外查询.

week_day

接收一周的天数1(星期一)到7(星期天),整型, 用于date和datetime字段匹配’一周中的第几天’. 允许跟上额外查询.

例如:

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

(本次查询没有给出等价的SQL片段, 因为不同的数据库引擎对此的实现不尽相同.)

注意这会匹配出 pub_date 为星期一(一周的第二天), 不管是哪一月或是哪一年. 星期天的数值为1,星期六为7.

USE_TZ 设置为 True 时, datetime字段会在过滤前转换到当前时区. 这需要 数据库时区设置.

Changed in Django 1.9:

允许跟上额外查询.

hour

接收0到23的整数, 用于datetime和time字段精确匹配小时数. 允许跟上额外查询.

例如:

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语句因数据库而异.)

对于datetime字段, USE_TZ 设置为 True 时, 字段值在过滤前将会被转换到当前时区.

Changed in Django 1.9:

新增对SQLite TimeField 支持(其他数据库从1.7开始支持).

Changed in Django 1.9:

允许跟上额外查询.

minute

接收0到59的整数, 用于datetime和time字段精确匹配分钟数. 允许跟上额外查询.

例如:

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语句因数据库而异.)

对于datetime字段, USE_TZ 设置为 True 时, 字段值在过滤前将会被转换到当前时区.

Changed in Django 1.9:

新增对SQLite TimeField 支持(其他数据库从1.7开始支持).

Changed in Django 1.9:

允许跟上额外查询.

second

接收0到59的整数, 用于datetime和time字段精确匹配秒数. 允许跟上额外查询.

例如:

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语句因数据库而异.)

对于datetime字段, USE_TZ 设置为 True 时, 字段值在过滤前将会被转换到当前时区.

Changed in Django 1.9:

新增对SQLite TimeField 支持(其他数据库从1.7开始支持).

Changed in Django 1.9:

允许跟上额外查询.

isnull

接收 True 或者 False, 它们分别对应了SQL查询的 IS NULLIS NOT NULL.

例如:

Entry.objects.filter(pub_date__isnull=True)

等价于SQL:

SELECT ... WHERE pub_date IS NULL;

regex

大小写敏感的正则匹配.

正则表达式必须是数据库使用的语法. SQLite没有内置正则表达式支持, 这一功能是有Python的UDF(user-defined function)的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无法在date/time类型字段使用聚合函数. 这是因为SQLite没有原生的date/time类型, Django是使用text类型还模拟这一功能. 如果在SQLite上对date/time类型字段使用聚合查询将会抛出 NotImplementedError 异常.

Note

对空的 QuerySet 使用聚合函数将会返回 None. 例如, 当 QuerySet 为空时, 聚合函数 Sum 将会返回 None 而不是 0. 但 Count 函数例外, QuerySet 为空时会返回 0 .

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

expression

模型字段的字符串表示, 或者是 查询表达式.

output_field

可选参数, 表示返回的 模型字段 类型.

注解

聚合多个字段时, Django只能在所有字段类型相同的情况下确定 output_field, 否则必须传入 output_field .

**extra

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

Avg

class Avg(expression, output_field=FloatField(), **extra)

返回指定表达式的均值, 如果没有指定其他类型的 output_field 其必须为数值.

  • 默认别名: <field>__avg
  • 返回类型: float (或者是指定的 output_field)
Changed in Django 1.9:

output_field 参数支持非数值字段, 例如 DurationField.

Count

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

返回表达式关联对象的数量.

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

包含一个可选参数:

distinct

如果 distinct=True, 返回数量仅包含非重复的实例. 这等价于SQL中的 COUNT(DISTINCT <field>). 该参数默认值为 False.

Max

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

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

  • 默认别名: <field>__max
  • 返回类型: 和输入字段一致, 或者是传入的 output_field .

Min

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

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

  • 默认别名: <field>__min
  • 返回类型: 和输入字段一致, 或者是传入的 output_field .

StdDev

class StdDev(expression, sample=False, **extra)

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

  • 默认别名: <field>__stddev
  • 返回类型: float

包含一个可选参数:

sample

默认情况下, StdDev 返回的是总体标准差. 如果设置``sample=True``, 则返回样本标准差.

SQLite

SQLite没有直接提供 Variance. 有一个SQLite扩展模块实现了该功能. 详见 SQLite 文档 .

Sum

class Sum(expression, output_field=None, **extra)

计算给定表达式的和.

  • 默认别名: <field>__sum
  • 返回类型: 和输入字段一致, 或者是传入的 output_field .

Variance

class Variance(expression, sample=False, **extra)

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

  • 默认别名: <field>__variance
  • 返回类型: float

包含一个可选参数:

sample

默认情况下, Variance 返回总体方差, 如果设置 sample=True, 则返回样本方差.

SQLite

SQLite没有直接提供 Variance. 有一个SQLite扩展模块实现了该功能. 详见 SQLite 文档 .

相关查询工具

本节提供了一些其他地方没有记载的相关查询的工具和参考.

Q() 对象

class Q

Q()F 类似, 将SQL表达式封装在表示数据库操作的Python对象中.

通常, Q() objects 常用于查询条件复用的情况. 然后使用 | (OR) 和 & (AND)运算符来构建复杂的查询. 详见 构建复杂查询 .

Prefetch() objects

class Prefetch(lookup, queryset=None, to_attr=None)

Prefetch() 对象可以用来控制 prefetch_related() 行为.

lookup 参数描述了要遵循的关系, 其工作原理与传递给 prefetch_related() 的基于字符串的查找相同. 例如:

>>> from django.db.models import Prefetch
>>> Question.objects.prefetch_related(Prefetch('choice_set')).get().choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# This will only execute two queries regardless of the number of Question
# and Choice objects.
>>> Question.objects.prefetch_related(Prefetch('choice_set')).all()
<QuerySet [<Question: Question object>]>

queryset 参数为给定的查询提供基本的 QuerySet . 对于进一步过滤预取操作或从预取关系中调用 select_related() 很有用, 从而进一步减少了查询数量:

>>> voted_choices = Choice.objects.filter(votes__gt=0)
>>> voted_choices
<QuerySet [<Choice: The sky>]>
>>> prefetch = Prefetch('choice_set', queryset=voted_choices)
>>> Question.objects.prefetch_related(prefetch).get().choice_set.all()
<QuerySet [<Choice: The sky>]>

to_attr 参数将预取操作的结果设置为自定义属性:

>>> prefetch = Prefetch('choice_set', queryset=voted_choices, to_attr='voted_choices')
>>> Question.objects.prefetch_related(prefetch).get().voted_choices
<QuerySet [<Choice: The sky>]>
>>> Question.objects.prefetch_related(prefetch).get().choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

注解

当使用 to_attr 时, 预取结果存储在列表中. 与传统的 prefetch_related 调用相比,这可以显着提高速度, 传统的 prefetch_related 调用将缓存的结果存储在 QuerySet 实例中.