django 查询(QuerySet) API 接口参考

查询结果集(QuerySets)的产生

django内部的一个查询(QuerySet)可以在不涉入数据库访问的情况下,被构造,过滤,切片,和一般的正常操作。
只有在执行了查询(QuerySet)的情况下,才会去访问数据库,从数据库中取回数据。不然查询(QuerySet)只是软件层面上的ORM对象映射关系。

你可以用下面来执行一个查询QuerySet.

  • 一个查询QuerySet是一个可迭代对象,当你第一次开始对它进行迭代时,它去才去查询数据库。比如,下面这个将打印所有存在数据库中entries的headline数据:
for e in Entry.objects.all():
    print(e.headline)

注:如果你只是想确认下是否至少存在一条记录,就不要用这种方法。使用exists()来判断更加高效。

  • slicing(切片):一个查询QuerySet可以使用python的语法进行切片。如果一个查询QuerSet没有被执行,那么进行切片得到的查询QuerSet一样也未执行的,但是,如果你使用,切片语法中的步长语法,那么django将执行数据库查询,并返回一个列表。切片一个已经执行过的查询QuerySet也会返回一个列表。
  • picking/Caching: 这个非常重要,请查看我的 ”django 缓存与查询结果集(Caching and QuerySets)“博文
  • repr():当你对查询QuerySet调用repr()函数时,将执行查询(查数据库)。repr()函数是为了在python的交互解释器中的方便存在的,因此,当你用这个交互式地API时,你能直接在交互解释器中看到你的结果。
  • len():当你把查询QuerySety作为参数调用repr()函数时,那么将执行查询。正如你想的那样,将返回列表的长度。

注:如果你只想只到结果集中的记录的条数(不需要直正的记录对象),那么在数据库层面使用count,执行SQL语句SELECT COUNT(*), 将会更加的高效。为此Django提供了一个count方法

  • list(): 对QuerySet调用list()函数,将强行执行查询。比如:
entry_list = list(Entry.objects.all())

bool(): 如果查询结果集至少有一个结果,那么这个查询QuerySet就是真,否则是假。对查询QuerySet使用 bool(), or, and ,if 语句时,将会引起数据库的查询。比如:

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

注意:如果您只想确定是否存在至少一个结果(并且不需要实际的对象),那么使用exists()会更有效。

序列化结果集

如果你序列化一个查询Queryset, 在序列化前,会强制所有的结果加载到内部。序列化通常作为一个预备器用于缓存,且当缓存的查询QuerySet被加载,你想要的结果已经存在,并且准备被使用(从数据库中读取数据可能会花费一些时间,从而破坏了缓存的目的). 这意味着当你反序列化一个查询时, 得到的结果是它序列化时的结果,而不是当前数据库中的结果。

如果你只是相序列化一些必要的信息,随后从数据库中重新执行这个查询,那么,序列化查询QuerySet的query属性。使用像下的代码,然后你可以重新创建原始的QuerSet(不加载任何结果):

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

注意
序列化的结果集只在计算他的那个Django中是合法的。在不同的版本中,N版本中序列化的结果集, 在N+1的版本中是不可读的

返回新的结果集的方法

  • filter(**kwargs)
    返回一个满足给定查询参数的新的查询QuerSet(是一个对象结集).多个查询条件在SQL中是and关系。
  • exclude(kwargs)
    返回一个不满足给定查询参数的新的查询QuerSet(是一个对象结集). 多个查询条件在SQL中是and关系,且所有条件都在包在
    NOT()
    里。

这个例子排除了所有pub_date晚于2005年1月3日且标题为“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晚于2001 -3或标题为“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(*args, **kwargs)
    annotate中的查询表达式会对QuerySet中的每一个对象独立执行,为每一个生成对应的注释。这个表达式可以是一个简单的值value,模型的字段(或任何关联模型的字段),或着是对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

默认返回的QuerySet是依据由模型Meta中的ordering选项的元组来排序的。你可以通过使用order_by方法来对一个QuerySet进行重新排序。
例:

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

结果将按pub_date降序排列,然后按*headline升序排序。负号“-”说明是降序排列。
随机排序可以用“?”,像这样:

Entry.objects.order_by('?')

注意:order_by(‘?’)查询可能开销很大而且很慢,这取决于您使用的数据库后端。

如果您尝试按与另一个模型相关的字段排序,如果关联模型没有用Meta.ordering指定排序,Django将在关联模型上使用默认顺序或按关联模型的主键排序。
例如,因为Blog没有指定默认的顺序:

Entry.objects.order_by('blog')

…等同于:

Entry.objects.order_by('blog__id')

如果Blog 有定义 ordering = [“name”],那么第一个查询等同于:

Entry.objects.order_by('blog__name')

你也可以在查询表达式中调用asc() 或 desc() ,进行排序。
如果您还使用distinct(),那么在按关联模型中的字段排序时要小心。有关联型排序如何改变预期结果的解释,请参阅*distinct()*中的说明
注意
允许指定多值字段用于对结果进行排序(比如一个多对多字段,或着外键字段的反向关系)。
考虑这情况:

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会返回多个项在新的QuerySet中,那么都是ordery_by()产生的结果。也就是说,在QuerySet上使用ordery_by()会返回比你开始排序前工作时的项更多的项—这些可能不是想要的也是没有用的。

因此,使用多值字段进行排序要小心。如果你能确定对于每一项对应的排序字段数据只有一个值,那么不会有什么问题,如果不能,请确保结果是我们想要的。

无法指定排序是否区分大小写。在区分大小写方面,Django将按照数据库后端通常的顺序排序结果.

您可以用Lower通过一个转换为小写的字段来排序,这将实现大小写一致的排序.

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

如果不希望将任何排序应用于查询,甚至不希望将默认排序应用于查询,请调用没有参数的order_by()。

你可以通过QuerySet.ordered属性来知道一个查询是否被排序了,如果QuerySet.ordered为真,那么这个查询QuerySet已经通过某个途径进行了排序。

每个order_by()会清除之前的排序。比如,下面的查询会按pub_date排序而不是headline:

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

警告
*排序也是会带来开销的操作。你加到order_by中的排序字段会给数据库带来开销。你加入的每个外键字也会涉及它的默认排序。
如果一个查询没有指定排序,从数据库返回的结果是没有具体序的。只有在排序的字段能唯一标识一个对象,排序的结果才能保证唯一。举个例子,如果一个name字段不是唯一个unique,用name来排序不能保证相同名字的对象出现前后关系。

reverse()
使用*reverse()*方法让查询返回的结果集中的元素的排序反向。调用reverse()使正常排序的方向反向。

查询结果集中最后5个项,你可以这样做:

my_querset.reverse()[:5]

注意,这与python中的列表从后切片不一样。上面的例子,会把最后一个项作为第一个要返回的项返回,如此,返回所有的项。如果我们有一个python的列表se1[-5:],我们会得到的第一个项会是列表的第五个项。Django不支持这负索引,是因为在SQL中不可能有效地做到这一点。

一般来说调用reverse()方法的QuerySet都是有排序的(比如,查询时使用了默认的排序,或用了order_by() 方法进行排序)。如果一个查询没有定义排序,那么调用revers()是没有实际效果的(没有排序前调用reverse(),那结果还没排序前的样子)。

*distinct(fields)
返回一个新的QuerySet,它在SQL查询中使用SELECT DISTINCT。这消除了查询结果中的重复行.

默认情况下,QuerySet不会消除重复的行。实际上,这很少是一个问题,因为像blog .object .all()这样的简单查询不会引入重复结果行的可能性。但是,如果您的查询跨多个表,那么在计算一个QuerySet时可能会得到重复的结果。这时就需要使用distinct()。

注意
*order_by()调用中使用的任何字段都包含在SQL SELECT 列 中。当与distinct()结合使用时,这有时会导致意想不到的结果。如果您从related model 中按字段排序,那么这些字段将被添加到所选的列中,并且它们可能使重复的行看起来是不同的。由于额外的列不会出现在返回的结果中(它们只是用于支持排序),所以有时看起来返回的结果并不明显。

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

这里的想要表达的是,如果您正在使用distinct(),请小心按关联模型的字段的排序。类似地,当同时使用distinct()和values()时,要小心排序时调用values()中没有的字段参与排序。*

仅在PostgreSQL上,您可以传递位置参数(fields),以便指定distinct()*应用哪些字段的名。 这会直接映射到SQL查询上的SELECT DISTINCT。不同的地方就在这里。对于一个普通的distinct()调用,当确定哪些行是不同的时,数据库会比较每一行中的每个字段。对于具有指定字段名的distinct()调用,数据库将只比较指定的字段名。
注意
当指定字段名时,必须在queryset中提供order_by(), order_by()中的字段必须以distinct()中的字段开头,顺序相同。
例如,SELECT DISTINCT ON (a)为a列中的每个值提供第一行。如果不指定顺序order,就会得到任意一行

示例(第一个示例之后的示例只适用于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()会使用任何关联模型已定义的默认排序进行排序。您可能必须显式地按照relation_id或引用字段排序,以确保不同的ON表达式与order by子句开头的表达式匹配。例如,如果博客模型定义了一个按name进行的排序:

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

…无法工作,因为查询将由blog_name排序,因此与DISTINCT ON表达式不匹配。您必须显式地对relationship _id字段(本例中是blog_id)或referencedone (blog_pk)进排序,以确保两个表达式匹配。

values()
返回一个QuerySet,当作为迭代器使用时,它返回字典,而不是模型实例。

这些字典中的每一个都表示一个对象,键对应于模型对象的属性名。

本例将values()的字典与普通 model object 进行比较:

# 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, *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'}]>

values()子句中的聚合aggregate应用于相同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()并传入字段名时,您可以传入foo或foo_id,您将得到相同的结果(dictionary键将匹配您传入的字段名)。
    比如:
>>> 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}, ...]>
  • 如果在*extra()调用之后使用values()子句,那么在extra()调用中由select参数定义的任何字段都必须显式包含在values()调用中。在values()调用之后执行的任何extra()*调用都将忽略额外选种的字段。
  • 在values()之后只调用only()deferred()是没有意义的,这样做会引发NotImplementedError

当您知道只需要一小部分可用字段的值,而不需要模型对象的功能时,这是非常有用的。只选择需要使用的字段更有效。

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

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

Django的开发人员喜欢将所有影响sql的方法放在前面,然后(可选地)使用任何影响输出的方法(比如values(),但这并不重要。这是你真正展现个人主义的机会。

你也可以通过onetoonefield, ForeignKey和ManyToManyField属性来引用具有反向关系的相关模型的字段:

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

如果只传递一个字段,还可以传递flat 参数。如果为真,这将意味着返回的结果是单个值,而不是一个元组。举个例子应该能让区别更清楚:

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

拥有多个entry的作者会出现多次,而没有任何entry的作者对于entry的标题为None.
类似地,当查询反向外键时,对于没有任何作者的entry也不会出现:

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

dates(field, kind, order=‘ASC’)
返回一个QuerySet,其计算结果为一个datetime.date对象列表, datetime.date对象表示QuerySet本体中特定类型的所有可用日期的。

字段field应该是模型的DateField的名称。kind应该是“year”、“month”或“day”。每个datetime.date对象在结果列表中被“截断”为给定的类型 type:

  • “year” returns a list of all distinct year values for the field.
  • “month” returns a list of all distinct year/month values for the field.
    *“day” returns a list of all distinct year/month/day values for the field.
    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', '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(field_name, kind, order=‘ASC’, tzinfo=None)
同上

tzinfo定义了datetimes在截断之前转换到的时区。事实上,给定的datetime根据使用的时区有不同的表示。该参数必须是date .tzinfoobject。如果没有,Django使用当前时区。当USE_TZ为false时,则没有效果。

none()
调用none()将创建一个不返回任何实例对象queryset,且当使用结果是不会执行数据库查询。一个qs.none()查询是一个EmptyQuerySet实例。
例:

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

all()
返回当前QuerySet(或QuerySet子类)的副本。这在您可能希望传入模型管理器r或QuerySet并对结果进行进一步筛选的情况下非常有用。在调用这两个对象上的all()方法之后,您将得到一个明确的QuerySet用于进一步的操作。

当查询集queryset被计算时,它通常会缓存它的结果。如果数据库中的数据可能在QuerySet被求值后发生了变化,那么您可以通过调用先前求值的QuerySet上的all()来更新QuerySet结果.

union()
使用SQL的UNION操作符合并两个或多个QuerySets.比如:

>>> qs1.union(qs2, qs3)

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

union()、intersection() 和 difference()返回第一个QuerySet类型的模型实例,即使参数是其他模型的QuerySet。只要SELECT列在所有queryset中都是相同的,那么传递不同的模型就可以工作(至少在类型中,名称并不重要,重要的是类型的顺序相同)。在这种情况下,必须使用QuerySet方法中第一个QuerySet的列名,这些方法应用于查询集。例如:

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

此外,结果集上只允许LIMIT, OFFSET, COUNT(), ORDER BY*,和指定列(即切片、COUNT()、order_by()和values()/values_list())。此外,数据库对组合查询中允许哪些操作进行了限制。例如,大多数数据库在组合查询中不允许LIMIT,或OFFSET,

*intersection(other_qs)
使用SQL的INTERSECT操作符返回两个或更多queryset的共享元素。例如:

>>> qs1.intersection(qs2, qs3)

有关一些限制,请参见union()。
*difference(other_qs)
使用SQL的EXCEPT操作符只保留queryset中的元素,而不保留其他queryset中的元素。例如:

>>> qs1.difference(qs2, qs3)

有关一些限制,请参见union()。

  • select_related(*fields)
    当我们执行查询时,返回一个"带有"外键关系的查询QuerySet, 选取了额外的关联对象数据.这是一个性能增强器,会生成一个更复杂的查询 , 但这意味着以后使用外键关系将不需要数据库查询了。

下面的示例说明了普通查找和select_related()查找之间的区别。
这是标准的查找:

# 查询数据库
# Hits the database.
e = Entry.objects.get(id=5)

# 为获得关联的Blog对象,再次查询数据库
# Hits the database again to get the related Blog object.
b = e.blog

这里是select_related查找:

# Hits the database.
e = Entry.objects.select_related('blog').get(id=5)
# 不再查询数据数,直接从原来的query中获取
# Doesn't hit the database, because e.blog has been prepopulated
# in the previous query.
b = e.blog

您可以对任何queryset对象使用select_related()

from django.utils import timezone

# Find all the blogs with entries scheduled to be published in the future.
blogs = set()

for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
    # Without select_related(), this would make a database query for each
    # loop iteration in order to fetch the related blog for each entry.
    blogs.add(e.blog)

filter()和select_related()链接的顺序并不重要。这些查询集是等价的

Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())

您可以按照与查询外键类似的方式跟踪外键。如果你有以下模型

from django.db import models

class City(models.Model):
    # ...
    pass

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(
        City,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

…接着调用 **Book.objects.select_related(‘author__hometown’).get(id=4)**将缓存关联的Person和关联的Ciity:

# Hits the database with joins to the author and hometown tables.
b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author         # Doesn't hit the database.
c = p.hometown       # Doesn't hit the database.

# Without select_related()...
b = Book.objects.get(id=4)  # Hits the database.
p = b.author         # Hits the database.
c = p.hometown       # Hits the database.

你可以在select_related()的参数列表中,引用任何外键或一对一字段关系。
还可以在传递给select_related的字段列表中引用 OneToOneField 的反向关系–通过反各关找到对象定义的字段。不要使用指定字段名,而是使用字段关联对象的related_name。

在某些情况下,您可能希望调用select_related()找出大量相关对象,或者不知道所有关系。这个时候你可以不带参数调用select_reated()。这将遵循所有非空外键将它们可以找到—空外键必须指定。不过在大多数情况下这是不推荐的,因为会使底层查询更加复杂,返回更多的数据。多于实际需要的数据。

如果需要清除QuerySet上过去调用的select_related添加的相关字段列表,可以将None作为参数传递:

>>> without_relations = queryset.select_related(None)

链式调用select_related的工作方式与其他方法类似——即select_related(‘foo’, ‘bar’)等价于select_related(‘foo’).select_related(‘bar’)

  • prefetch_related(*lookups)
    返回一个QuerySet,它将在单个批处理中为每个指定的查找自动检索关联对象。

这与select_related有类似的目的,因为两者都是为了阻止由于访问关联对象而导致的大量数据库查询,但是策略却完全不同。

select_related的工作原理是创建一个SQL连接,并在SELECT语句中包含关联对象的字段。因此,select_related 用同一数据库SQL查询的关联对象。然而,为了避免由于“对多”关系连接而导致更大的结果集,select_related被限制为单值关系外键(正向,定义外键处)和一对一关系。

另一方面,prefetch_related为每个关系执行单独的查找,并在Python中执行’join '。这允许它预取多对多和多对一对象,除了由select_related支持的外键和一对一关系之外,这不能使用select_related来完成。它还支持预取genericrelationship和genericforeignkey,但是,它必须限制在一个同构的结果集。例如,只有当查询被限制为一个ContentType时,才支持预取关联GenericForeignKey的对象。

举例,假设你有下面的模型:

from django.db import models

class Topping(models.Model):
    name = models.CharField(max_length=30)

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

    def __str__(self):
        return "%s (%s)" % (
            self.name,
            ", ".join(topping.name for topping in self.toppings.all()),
        )

接着运行:

>>> Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...

这个问题在于self.toppings.all()每次调用*Pizza.__ str __()时同会查询数据加,因此Pizza.objects.all() *会为QuerySet中的每一项都在Toppins的数据表中进行一次SQL查询.

使用prefetch_related可以减少到只要两条SQL查询:

>>> Pizza.objects.all().prefetch_related('toppings')

这意味着每个Pizza都有一个self.topping .all();现在,每次调用self.toppings.all(),它将在一个预先获取的QuerySet缓存中找到它们,而不是到数据库中查找这些项,该缓存是在一个查询中填充的.

也就是说,所有相关的toppings都将在一个查询中获取,并用于创建查询集,该查询集具有预先填充的相关结果的缓存;然后在self.topping .all()调用中使用这些查询集。

如果您有一个可迭代的模型实例,您可以使用prefetch_related_objects()函数对这些实例预取相联的属性。

注意,主QuerySet的结果缓存和所有指定的相联对象将被完全加载到内存中。这改变了queryset的典型行为,queryset通常试图避免在需要之前将所有对象加载到内存中,即使在数据库中执行了查询之后也是如此。

注意
请记住,与QuerySets一样,任何表示不同数据库查询的后续链接方法都将忽略以前的缓存结果,并使用新的数据库查询检索数据。所以,如果你写下以下内容:

>>> pizzas = Pizza.objects.prefetch_related('toppings')
>>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]

…那么*Pizza.topping .all()已经被预取的事实将不会帮助你。prefetch_related(‘toppings’)包含pizza.topping .all(),但是pizza.topping .filter()*是一个新的不同的查询。预取的缓存在这里不起作用;事实上,它会影响性能,因为您已经完成了一个从未使用过的数据库查询。谨慎使用此功能!

此外,如果在关联模型管理器上调用数据库更改方法add()、remove()、clear()或set(),则预取缓存的关系的任何都将被清除。

您还可以使用普通的连接语法来处理关联字段的关联字段。假设我们对上面的例子有一个额外的模型:

class Restaurant(models.Model):
    pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
    best_pizza = models.ForeignKey(Pizza, related_name='championed_by', on_delete=models.CASCADE)

以下都是合法的:

>>> Restaurant.objects.prefetch_related('pizzas__toppings')

这将预取属于Restaurant的所有pizzas,以及属于这些pizzas的所有toppings。这将导致总共3个数据库查询—一个用于Restaurant,一个用于pizzas,一个用于toppings.

>>> Restaurant.objects.prefetch_related('best_pizza__toppings')

这将为每个Restaurant获取best_pizza 和best_pizza的所有toppings.这将导致总共3个数据库查询—一个用于Restaurant,一个用于best_pizza ,一个用于toppings.

当然,best_pizza关系也可以使用select_related来获取,从而将查询数量减少到2:

>>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')

因为预取是在主查询之后执行的(其中包括select_related()所需的连接),所以它能够检测到best_pizza对象已经被获取,并且它将再次跳过获取它们。

链式调用prefetch_related将积累预取的查找。若要清除任何prefetch_related行为,请将None作为参数传递:

>>> non_prefetched = qs.prefetch_related(None)

使用prefetch_related时需要注意的一个区别是,一次查询创建的对象可以在与之相关联的不同对象之间共享。一个Python模型实例可以出现在返回对象树中的多个点上。这通常会发生在外键关系中。通常这种行为不会成为问题,而且实际上会节省内存和CPU时间。

  • extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值