(Django2.1)QuerySet API参考
QuerySets什么时候执行
在内部,创建、过滤、切片和传递一个QuerySet不会真实操作数据库,在你对查询集提交之前,不会发生任何实际的数据库操作。
-
QuerySet是可迭代的,并且在您第一次迭代它时执行其数据库查询。例如,这将打印数据库中所有条目的标题:注意:如果您只想确定是否存在至少一个结果,请不要使用此选项。使用exists()更有效。
for e in Entry.objects.all(): print(e.headline)
-
可以使用Python的数组切片语法对QuerySet进行切片。 未提交的QuerySet通常会返回另一个未提交的QuerySet,但如果使用切片语法的“step”参数,Django将执行数据库查询,并返回一个列表。 已经提交的QuerySet 切片也会返回一个列表。
另请注意,即使未提交的QuerySet通常会返回另一个未评估的QuerySet,也不允许进一步修改它(例如,添加更多过滤器或修改排序),因为这不能很好地转换为SQL,也不会有明确的含义。
-
Pickling/Caching
-
repr()
-
len()
-
list() entry_list = list(Entry.objects.all())
-
bool()
if Entry.objects.filter(headline="Test"): print("There is at least one Entry with the headline Test")
Pickling QuerySets(序列化)
如果你要 序列化(pickle) 一个 QuerySet,Django 首先就会将查询对象载入到内存中以完成序列化,这样你就可以第一时间使用对象(直接从数据库中读取数据需要一定时间,这正是缓存所想避免的)。而序列化是缓存化的先行工作,所以在缓存查询时,首先就会进行序列化工作。这意味着当你反序列化 QuerySet 时,第一时间就会从内存中获得查询的结果,而不是从数据库中查找。
如果你只是想序列化部分必要的信息以便晚些时候可以从数据库中重建 Queryset ,那只序列化 QuerySet 的 query 属性即可。接下来你就可以使用下面的代码重建原来的 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 属性是一个不透明的对象。这就意味着它的内部结构并不是公开的。即便如此,对于本节提到的序列化和反序列化来说,它仍是安全和被完全支持的。
QuerySets的API
返回QuerySets的API
filter(**kwargs)
返回一个新的 QuerySet ,它包含了与所给的筛选条件相匹配的对象。
这些筛选条件(**kwargs)在下面的字段筛选(Field lookups) 中有详细介绍。多个条件之间在 SQL 语句中是 AND 关系。
exclude(**kwargs)
返回一个新的 QuerySet,它包含那些与所给筛选条件不匹配的对象。
这些筛选条件(**kwargs)也在下面的 字段筛选(Field lookups) 中有详细描述。多个条件之间在 SQL 语句中也是 AND 关系,但是整体又是一个 NOT() 关系。
下面的例子剔除了出版日期 pub_date 晚于 2005-1-3 并且大标题 headline 是 “Hello” 的所有博文(entry):
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
# 等价于
SELECT ...
WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
下面的例子剔除出版日期 pub_date 晚于 2005-1-3 或者大标题是 “Hello” 的所有博文:
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')
# 等价于
SELECT ... WHERE NOT pub_date > '2005-1-3' OR NOT headline = 'Hello'
要注意第二个例子是有很多限制的。
annotate(*args, **kwargs)
我们可以为 QuerySet 中的每个对象添加annotate。可以通过计算查询结果中每个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和,等等),做为 QuerySet 中对象的annotate。
关键字参数指定的Annotation将使用关键字作为Annotation 的别名。 匿名参数的别名将基于聚合函数的名称和模型的字段生成。只有引用单个字段的聚合表达式才可以使用匿名参数。 其它所有形式都必须用关键字参数。
例如,你正在操作一个博客列表,你想知道一个博客究竟有多少篇博文:
>>> 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 model 类本身并没有定义 entry__count 属性,但是通过使用一个关键字参数来指定聚合函数,可以控制Annotation的名称:
>>> 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(*fields)
默认情况下, QuerySet 返回的查询结果是根据 model 类的 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('?') 可能会非常缓慢和消耗过多资源,这取决于你所使用的数据库。
要根据其他 model 字段排序,所用语法和跨关系查询的语法相同。就是说,用两个连续的下划线(__)连接关联 model 和 要排序的字段名称, 而且可以一直延伸。例如:
Entry.objects.order_by('blog__name', 'headline')
如果你想对关联字段排序,在没有指定 Meta.ordering 的情况下,Django 会采用默认排序设置,就是按照关联 model 的主键进行排序。例如:
Entry.objects.order_by('blog')
Entry.objects.order_by('blog__id') # 与上句等效的,这是因为 Blog model 没有声明排序项的原故。
如果Blog设置了ordering = ['name'], # 那么第一个QuerySet将等同于:
Entry.objects.order_by('blog__name')
Entry.objects.order_by(Coalesce('summary', 'headline').desc())
如果你使用了 distinct() 方法,那么在对关联字段排序时要格外谨慎。
在 Django 当中是可以按照多值字段(例如 ManyToMany 字段)进行排序的。不过,这个特性虽然先进,但是并不实用。除非是你已经很清楚过滤结果或可用数据中的每个对象,都只有一个相关联的对象时(就是相当于只是一对一关系时),排序才会符合你预期的结果,所以对多值字段排序时要格外注意。
考虑下面的情况,指定一个多值字段来排序(例如,一个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 并没有提供设置方法,这完全取决于后端的数据库对排序大小写如何处理。
Entry.objects.order_by(Lower('headline').desc()) # 变成小写后排序
如果你不想对任何字段排序,也不想使用 model 中原有的排序设置,那么可以调用无参数的 order_by() 方法。
你可以令某个查询结果是可排序的,也可以是不可排序的,这取决于 QuerySet.ordered 属性。如果它的值是 True ,那么 QuerySet 就是可排序的。可以通过检查QuerySet.ordered属性来知道查询是否是排序的。
每个order_by()都将清除前面的任何排序。 例如下面的查询将按照pub_date排序,而不是headline:
Entry.objects.order_by('headline').order_by('pub_date')
reverse()
使用 reverse() 方法会对查询结果进行反向排序。调用两次 reverse() 方法相当于排序没发生改过。
要得到查询结果中最后五个对象,可以这样写:
my_queryset.reverse()[:5]
要注意这种方式与 Python 语法中的从尾部切片是完全不一样的。在上面的例子中,是先得到最后一个元素,然后是倒数第二个,依次处理。但是如果我们有一个 Python 队列,使用 seq[-5:]时,却是先得到倒数第五个元素。Django 之所以采用 reverse 来获取倒数的记录,而不支持切片的方法,原因就是后者在 SQL 中难以做好。
Django不支持负索引。
还有一点要注意,就是 reverse() 方法应该只作用于已定义了排序项 QuerySet (例如,在查询时使用了order_by()方法,或是在 model 类当中直接定义了排序项). 如果并没有明确定义排序项,那么调用 QuerySet, calling reverse() 就没什么实际意义(因为在调用 reverse() 之前,数据没有定义排序,所以在这之后也不会进行排序。)
distinct()
返回一个新的 QuerySet ,它会在执行 SQL 查询时使用 SELECT DISTINCT。这意味着返回结果中的重复记录将被剔除。
默认情况下, QuerySet 不会剔除重复的记录。在实际当中,这不是什么问题,因为象 Blog.objects.all() 这样的查询并不会产生重复的记录。但是,如果你使用 QuerySet 做多表查询时,就很可能会产生重复记录。这时,就可以使用 distinct() 方法。
Note
在 order_by(*fields) 中出现的字段也会包含在 SQL SELECT 列中。如果和 distinct() 同时使用,有时返回的结果却与预想的不同。这是因为:如果你对跨关系的关联字段进行排序,这些字段就会被添加到被选取的列中,这就可能产生重复数据(比如,其他的列数据都相同,只是关联字段的值不同)。但由于 order_by 中的关联字段并不会出现在返回结果中(他们仅仅是用来实现order),所以有时返回的数据看上去就象是并没有进行过 distinct 处理一样。
同样的原因,如果你用 values() 方法获得被选取的列,就会发现包含在 order_by() (或是 model 类的 Meta 中设置的排序项)中的字段也包含在里面,就会对返回的结果产生影响。
这章节强调的就是在你使用 distinct() 时,要谨慎对待关联字段排序。同样的,在同时使用 distinct() 和 values() 时,如果排序字段并没有出现在 values() 返回的结果中,那么也要引起注意。
Author.objects.distinct()
values(*fields)
返回一个 ValuesQuerySet ----一个特殊的 QuerySet ,运行后得到的并不是一系列 model 的实例化对象,而是一个可迭代的字典序列。每个字典都表示一个对象,而键名就是 model 对象的属性名称。
下面的例子就对 values() 得到的字典与传统的 model 对象进行了对比:
# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith='Beatles')
[<Blog: Beatles Blog>]
# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith='Beatles').values()
[{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]
values() 可以接收可选的位置参数,*fields,就是字段的名称,用来限制 SELECT 选取的数据。如果你指定了字段参数,每个字典就会以 Key-Value 的形式保存你所指定的字段信息;如果没有指定,每个字典就会包含当前数据表当中的所有字段信息。
>>> Blog.objects.values()
[{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}],
>>> Blog.objects.values('id', 'name')
[{'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}]> # 先按照entry__authors分组
下面这些细节值得注意:
如果你有一个名为 foo 的ForeignKey 字段,默认情况下调用 values() 返回的字典中包含键名为 foo_id 的字典项,因为它是一个隐含的 model 字段,用来保存关联对象的主键值( foo 属性用来联系相关联的 model )。当你使用 values() 并传递字段名称时, 传递foo 或 foo_id 都会得到相同的结果 (字典中的键名会自动换成你传递的字段名称)。
>>> Entry.objects.values()
[{'blog_id': 1, 'headline': u'First Entry', ...}, ...]
>>> Entry.objects.values('blog')
[{'blog': 1}, ...]
>>> Entry.objects.values('blog_id')
[{'blog_id': 1}, ...]
在 values() 和 distinct() 同时使用时,要注意排序项会影响返回的结果,详情请查看上面 distinct() 一节。
在values()之后使用defer()和only()是无用的,将引发一个NotImplementedError。
最后,要提醒的是,ValuesQuerySet 是 QuerySet 的一个子类,所以它拥有 QuerySet 所有的方法。你可以对它调用 filter() 或是 order_by() 以及其他方法。所以下面俩种写法是等价的:
Blog.objects.values().order_by('id')
Blog.objects.order_by('id').values()
Django 的编写者们更喜欢第二种写法,就是先写影响 SQL 的方法,再写影响输出的方法(比如例中先写 order,再写values ),但这些都无关紧要,完全视你个人喜好而定。
可以通过ManyToManyField、ForeignKey 和 OneToOneFiel 属性反向引用关联的模型的字段:
Blog.objects.values('name', 'entry__headline')
[{'name': 'My blog', 'entry__headline': 'An entry'},
{'name': 'My blog', 'entry__headline': 'Another entry'}, ...]
当指向多对多关系时,因为关联对象可能有很多,所以同一个对象会根据不同的多对多关系返回多次。
values_list(*fields, flat=False, named=False)
它与 values() 非常相似,只不过后者返回的结果是字典序列,而 values_list() 返回的结果是元组序列。每个元组都包含传递给 values_list() 的字段名称和内容。比如第一项就对应着第一个字段,例如:
>>> Entry.objects.values_list('id', 'headline')
[(1, u'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 就会导致错误。
you can pass named=True to get results as a namedtuple():
>>> Entry.objects.values_list('id', 'headline', named=True)
<QuerySet [Row(id=1, headline='First entry'), ...]>
如果你没给 values_list() 传递参数,它就会按照字段在 model 类中定义的顺序返回所有的字段。
values()和values_list()都用于特定情况下的优化:检索数据子集,而无需创建模型实例。
常见的需求是获取特定模型实例的特定字段值。要实现这一点,请使用values_list()后跟get()调用:
>>> Entry.objects.values_list('headline', flat=True).get(pk=1)
'First entry'
注意通过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。
>>> Entry.objects.values_list('authors')
<QuerySet [('Noam Chomsky',), ('George Orwell',), (None,)]>
dates(field, kind, order=‘ASC’)
返回一个 DateQuerySet ,就是提取 QuerySet 查询中所包含的日期,将其组成一个新的 datetime.date 对象的列表。
field 是你的 model 中的 DateField 字段名称。
kind 是 “year”, “month” 或 “day” 之一。 每个 datetime.date对象都会根据所给的 type 进行截减。
“year” 返回所有时间值中非重复的年分列表。
“month” 返回所有时间值中非重复的年/月列表。
“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', '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, kind, order='ASC')
datetimes(field_name, kind, order=‘ASC’, tzinfo=None)
返回一个QuerySet,该QuerySet值为datetime.datetime对象的列表,该对象表示QuerySet内容中特定类型的所有可用日期。
field_name应该是模型的DateTimeField的名称。
kind是“year”,“month”,“week”,“day”,“hour”,“minute”或“second”。 结果列表中的每个datetime.datetime对象都“截断”为给定类型。
默认为“ASC”的订单应为“ASC”或“DESC”。 这指定了如何订购结果。
tzinfo定义截断前转换日期时间的时区。 实际上,给定的日期时间根据使用的时区具有不同的表示。 此参数必须是datetime.tzinfo对象。 如果它是None,Django使用当前时区。 当USE_TZ为False时无效。
none()
返回一个 EmptyQuerySet – 它是一个运行时只返回空列表的 QuerySet。它经常用在这种场合:你要返回一个空列表,但是调用者却需要接收一个 QuerySet 对象。(这时,就可以用它代替空列表)
>>> Entry.objects.none()
[]
>>> from django.db.models.query import EmptyQuerySet
>>> isinstance(Entry.objects.none(), EmptyQuerySet)
True
all()
返回当前 QuerySet (或者是传递的 QuerySet 子类)的一分拷贝。 这在某些场合是很用的,比如,你想对一个 model manager 或是一个 QuerySet 的查询结果做进一步的过滤。你就可以调用 all() 获得一分拷贝以继续操作,从而保证原 QuerySet 的安全。
当一个QuerySet查询后,它会缓存查询结果。如果数据库发生了改变,就可以调用all()来更新查询过的QuerySet。
union(*other_qs, all=False)
使用SQL的UNION运算符组合两个或多个QuerySet的结果。例如:
>>> qs1.union(qs2, qs3)
UNION运算符默认情况下仅选择不同的值。 要允许重复值,请使用all = True参数。
union(),intersection()和difference()返回第一个QuerySet类型的模型实例,即使参数是其他模型的QuerySets。 只要所有QuerySet中的SELECT列表相同,传递不同的模型就会起作用(至少类型,只要类型相同,名称无关紧要)。 在这种情况下,您必须使用应用于生成的QuerySet的QuerySet方法中的第一个QuerySet中的列名。 例如:
>>> qs1 = Author.objects.values_list('name')
>>> qs2 = Entry.objects.values_list('headline')
>>> qs1.union(qs2).order_by('name')
intersection(*other_qs)
使用SQL的INTERSECT运算符返回两个或多个QuerySet的共享元素。 例如:
>>> qs1.intersection(qs2, qs3)
difference(*other_qs)
使用SQL的EXCEPT运算符仅保留QuerySet中存在的元素,而不保留其他一些QuerySet中的元素。 例如:
>>> qs1.difference(qs2, qs3)
select_related()
返回一个 QuerySet ,它会在执行查询时自动跟踪外键关系,从而选取所关联的对象数据。它是一个增效器,虽然会导致较大的数据查询(有时会非常大),但是接下来再使用外键关系获得关联对象时,就会不再次读取数据库了。
下面的例子展示在获得关联对象时,使用 select_related() 和不使用的区别,首先是不使用的例子:
# Hits the database.
e = Entry.objects.get(id=5)
# Hits the database again to get the related Blog object.
b = e.blog
接下来是使用 select_related 的例子:
# Hits the database.
e = Entry.objects.select_related().get(id=5)
# Doesn't hit the database, because e.blog has been prepopulated in the previous query.
b = e.blog
select_related()可用于objects任何的查询集:
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'):
# 没有select_related(),下面的语句将为每次循环迭代生成一个数据库查询,以获得每个entry关联的blog。
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)
class Book(models.Model):
# ...
author = models.ForeignKey(Person)
# 接下来调用 Book.objects.select_related().get(id=4) 将缓存关联的 Person 和 City:
b = Book.objects.select_related('person__city').get(id=4)
p = b.author # Doesn't hit the database.
c = p.hometown # Doesn't hit the database.
b = Book.objects.get(id=4) # No select_related() in this example.
p = b.author # Hits the database.
c = p.hometown # Hits the database.
如果需要清除QuerySet上过去调用select_related所添加的相关字段列表,则可以将None作为参数传递:
without_relations = queryset.select_related(None)
Chaining select_related calls works in a similar way to other methods - that is that select_related(‘foo’, ‘bar’) is equivalent to select_related(‘foo’).select_related(‘bar’).
在传递给select_related()的字段中,可以使用任何ForeignKey和OneToOneField。
在传递给select_related的字段中,还可以反向引用OneToOneField。也就是说,可以回溯到定义OneToOneField 的字段。 此时,可以使用关联对象字段的related_name,而不要指定字段的名称。
prefetch_related(*lookups)
对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。或许你会说,没有一个叫OneToManyField的东西啊。实际上 ,ForeignKey就是一个多对一的字段,而被ForeignKey关联的字段就是一对多字段了。
prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。
prefetch_related对每个关系进行单独查找,并在Python中进行“连接”。这允许它预取多对多和多对一对象,这是select_related无法使用的
select_related,支持的foreign key 和 one-to-one关系,它还支持预取GenericRelation和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): # __unicode__ on Python 2
return "%s (%s)" % (self.name, ", ".join([topping.name
for topping in self.toppings.all()]))
并运行:
>>> Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...
问题是每次QuerySet要求Pizza.objects.all()查询数据库,因此self.toppings.all()将在Pizza Pizza.str()中的每个项目的Toppings表上运行查询。
可以使用prefetch_related减少为只有两个查询:
>>> Pizza.objects.all().prefetch_related('toppings')
这意味着现在每次self.toppings.all()被调用,不会再去数据库查找,而是在一个预取的QuerySet缓存中查找。
您还可以使用常规连接语法来执行相关字段的相关字段。假设我们在上面的例子中有一个额外的模型:
class Restaurant(models.Model):
pizzas = models.ManyToMany(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by')
下面的例子都可以
>>> Restaurant.objects.prefetch_related('pizzas__toppings')
# 这将预取所有属于餐馆的比萨饼以及属于这些比萨饼的所有配料。这将导致总共3个数据库查询 - 一个用于餐馆,一个用于比萨饼,一个用于配料
>>> Restaurant.objects.prefetch_related('best_pizza__toppings')
# 这将为每家餐厅提供最好的比萨饼和所有配料,以获得最佳比萨饼。 这将在3个数据库查询中完成 - 一个用于餐馆,一个用于'最佳比萨饼',一个用于配料。
#当然,也可以使用select_related获取best_pizza关系,以将查询计数减少到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对象进一步控制预取操作。
最简单的形式Prefetch相当于传统的基于字符串的查找:
>>> from django.db.models import Prefetch
>>> Restaurant.objects.prefetch_related(Prefetch('pizzas__toppings'))
您可以使用可选的queryset参数提供自定义查询集。这可用于更改查询集的默认顺序:
>>> Restaurant.objects.prefetch_related(
Prefetch('pizzas__toppings', queryset=Toppings.objects.order_by('name')))
或者调用select_related()以进一步减少查询数量:
>>> Pizza.objects.prefetch_related(
Prefetch('restaurants', queryset=Restaurant.objects.select_related('best_pizza')))
您还可以使用可选的to_attr参数将预取结果分配给自定义属性。结果将直接存储在列表中。
>>> vegetarian_pizzas = Pizza.objects.filter(vegetarian=True)
>>> Restaurant.objects.prefetch_related(
Prefetch('pizzas', to_attr='menu'),
Prefetch('pizzas', queryset=vegetarian_pizzas, to_attr='vegetarian_menu'))
在过滤预取结果时建议使用to_attr,因为它比将过滤结果存储在相关管理器缓存中更明确:
>>> queryset = Pizza.objects.filter(vegetarian=True)
>>> # Recommended:
>>> restaurants = Restaurant.objects.prefetch_related(
Prefetch('pizzas', queryset=queryset, to_attr='vegetarian_pizzas'))
>>> vegetarian_pizzas = restaurants[0].vegetarian_pizzas
>>> # Not recommended:
>>> restaurants = Restaurant.objects.prefetch_related(
Prefetch('pizzas', queryset=queryset))
>>> vegetarian_pizzas = restaurants[0].pizzas.all()
extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
有些情况下,Django 的查询语法难以简练地表达复杂的 WHERE 子句。对于这种情况,Django 提供了 extra() QuerySet 修改机制,它能在QuerySet 生成的 SQL 从句中注入新子句。
由于产品差异的原因,这些自定义的查询难以保障在不同的数据库之间兼容(因为你手写 SQL 代码的原因),而且违背了 DRY 原则,所以如非必要,还是尽量避免写 extra。
>>> qs.extra(
... select={'val': "select col from sometable where othercol = %s"},
... select_params=(someparam,),
... )
# is equivalent to:
>>> qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))
在 extra 可以指定一个或多个 params 参数,如 select,where 或 tables。所有参数都是可选的,但你至少要使用一个。
-
select
select 参数可以让你在 SELECT 从句中添加其他字段信息。它应该是一个字典,存放着属性名到 SQL 从句的映射。Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
结果中每个 Entry 对象都有一个额外的 is_recent 属性,它是一个布尔值,表示 pub_date 是否晚于2006年1月1号。
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 属性是一个字典,所以两者在匹配时应正确地一一对应。在这种情况下中,你应该使用 django.utils.datastructures.SortedDict 匹配 select 的值,而不是使用一般的 Python 队列。
Blog.objects.extra( select=SortedDict([('a', '%s'), ('b', '%s')]), select_params=('one', 'two'))
在使用 extra() 时要避免在 select 字串含有 “%%s” 子串, 这是因为在 Django 中,处理 select 字串时查找的是 %s 而并非转义后的 % 字符。所以如果对 % 进行了转义,反而得不到正确的结果。
-
where / tables
你可以使用 where 参数显示定义 SQL 中的 WHERE 从句,有时也可以运行非显式地连接。你还可以使用 tables 手动地给 SQL FROM 从句添加其他表。
where 和 tables 都接受字符串列表做为参数。所有的 where 参数彼此之间都是 “AND” 关系。Entry.objects.extra(where=['id IN (3, 4, 5, 20)']) # 等价于 SELECT * FROM blog_entry WHERE id IN (3, 4, 5, 20);
在使用 tables 时,如果你指定的表在查询中已出现过,那么要格外小心。当你通过 tables 参数添加其他数据表时,如果这个表已经被包含在查询中,那么 Django 就会认为你想再一次包含这个表。这就导致了一个问题:由于重复出现多次的表会被赋予一个别名,所以除了第一次之外,每个重复的表名都会分别由 Django 分配一个别名。所以,如果你同时使用了 where 参数,在其中用到了某个重复表,却不知它的别名,那么就会导致错误。
一般情况下,你只会添加一个未在查询中出现的新表。但是如果上面所提到的特殊情况发生了,那么可以采用如下措施解决。首先,判断是否有必要要出现重复的表,能否将重复的表去掉。如果这点行不通,就试着把 extra() 调用放在查询结构的起始处,因为首次出现的表名不会被重命名,所以可能能解决问题。如果这也不行,那就查看生成的 SQL 语句,从中找出各个数据库的别名,然后依此重写 where 参数,因为只要你每次都用同样的方式调用查询(queryset),表的别名都不会发生变化。所以你可以直接使用表的别名来构造 where。
-
order_by
如果你已通过 extra() 添加了新字段或是数据库,此时若想对新字段进行排序,就可以给 extra() 中的 order_by 参数传递一个排序字符串序列。字符串可以是 model 原生的字段名(与使用普通的 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 的排在后面。(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'])
defer(*fields)
在某些数据复杂的环境下,你的 model 可能包含非常多的字段,可能某些字段包含非常多的数据(比如,文档字段),或者将其转化为 Python 对象会消耗非常多的资源。在这种情况下,有时你可能并不需要这种字段的信息,那么你可以让 Django 不读取它们的数据。
通过传递字段名称到defer()实现不加载:
Entry.objects.defer("headline", "body")
延后截入字段的查询返回的仍是 model 类的实例。在你访问延后载入字段时,你仍可以获得字段的内容,所不同的是,内容是在你访问延后字段时才读取数据库的,而普通字段是在运行查询(queryset)时就一次性从数据库中读取数据的。
你可以多次调用 defer() 方法。每个调用都可以添加新的延后载入字段:
# Defers both the body and headline fields.
Entry.objects.defer("body").filter(rating=5).defer("headline")
对延后载入字段进行排序是不会起作用的;重复添加延后载入字段也不会有何不良影响。
你也可以延后载入关联 model 中的字段(前提是你使用 select_related() 载入了关联 model),用法也是用双下划线连接关联字段:
Blog.objects.select_related().defer("entry__lede", "entry__body")
如果你想清除延后载入的设置,只要使用将 None 做为参数传给 defer() 即可:
# Load all fields immediately.
my_queryset.defer(None)
有些字段无论你如何指定,都不会被延后加载。比如,你永远不能延后加载主键字段。如果你使用 select_related() 获得关联 model 字段信息,那么你就不能延后载入关联 model 的主键。(如果这样做了,虽然不会抛出错误,事实上却不完成延后加载)
Note
defer() 方法(和随后提到的 only() 方法) 都只适用于特定情况下的高级属性。它们可以提供性能上的优化,不过前提是你已经对你用到的查询有过很深入细致的分析,非常清楚你需要的究竟是哪些信息,而且已经对你所需要的数据和默认情况下返回的所有数据进行比对,清楚两者之间的差异。这完成了上述工作之后,再使用这两种方法进行优化才是有意义的。所以当你刚开始构建你的应用时,先不要急着使用 defer() 方法,等你已经写完查询并且分析成哪些方面是热点应用以后,再用也不迟。
only(*fields)
only() 方法或多或少与 defer() 的作用相反。如果你在提取数据时希望某个字段不应该被延后载入,而应该立即载入,那么你就可以做使用 only() 方法。如果你一个 model ,你希望它所有的字段都延后加载,只有某几个字段是立即载入的,那么你就应该使用 only() 方法。
如果你有一个 model,它有 name, age 和 biography 三个字段,那么下面两种写法效果一样的:
Person.objects.defer("age", "biography")
Person.objects.only("name")
你无论何时调用 only(),它都会立刻更改载入设置。这与它的命名非常相符:只有 only 中的字段会立即载入,而其他的则都是延后载入的。因此,连续调用 only() 时,只有最后一个 only 方法才会生效:
# This will defer all fields except the headline.
Entry.objects.only("body", "lede").only("headline")
由于 defer() 以递增方式动作(每次都添加字段到延后载入的列表中),所以你可以将 only() 和 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()一起使用的注意事项。只有在用完其他选项后才能小心使用。
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(nowait=False, skip_locked=False, of=())
返回queryset,并将需要更新的行锁定,类似于SELECT … FOR UPDATE的操作。
from django.db import transaction
entries = Entry.objects.select_for_update().filter(author=request.user)
with transaction.atomic():
for entry in entries:
...所有匹配的entries都会被锁定直到此次事务结束。
所有匹配的行将被锁定,直到事务结束。这意味着可以通过锁防止数据被其它事务修改。
一般情况下如果其他事务锁定了相关行,那么本查询将被阻塞,直到锁被释放。使用select_for_update(nowait=True)将使查询不阻塞。如果其它事务持有冲突的锁,那么查询将引发DatabaseError异常。也可以使用select_for_update(skip_locked=True)忽略锁定的行。nowait和skip_locked是互斥的。
目前,postgresql,oracle和mysql数据库后端支持select_for_update()。但是,MySQL不支持nowait和skip_locked参数。
不能在可以为空的关系上使用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: ...)>, ...]>
raw(raw_query, params=None, translations=None)
接收一个原始的SQL查询,执行它并返回django.db.models.query.RawQuerySet实例。这个RawQuerySet实例可以像普通的QuerySet一样进行迭代,以提供对象实例。
返回QuerySets的操作符
AND(&)
# The following are equivalent:
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))
SELECT ... WHERE x=1 AND y=2
OR (|)
# The following are equivalent:
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))
SELECT ... WHERE x=1 OR y=2
不返回QuerySets的方法(QuerySet methods that do not return QuerySets)
下面所列的 QuerySet 方法作用于 QuerySet,却并不返回 querySet。作用非常强大。
这些方法并不使用缓存(请查看 缓存与查询(Caching and QuerySets))。所以它们在运行时是立即读取数据库的。
get(**kwargs)
返回与所给的筛选条件相匹配的对象,筛选条件在 字段筛选条件(Field lookups) 一节中有详细介绍。
在使用 get() 时,如果符合筛选条件的对象超过一个,就会抛出 MultipleObjectsReturned 异常。MultipleObjectsReturned 是 model 类的一个属性。
在使用 get() 时,如果没有找到符合筛选条件的对象,就会抛出 DoesNotExist 异常。这个异常也是 model 对象的一个属性。例如:
Entry.objects.get(id='foo') # raises Entry.DoesNotExist
# DoesNotExist 异常继承自 django.core.exceptions.ObjectDoesNotExist,所以你可以直接截获 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.")
如果希望查询器只返回一行,则可以使用get()而不使用任何参数来返回该行的对象:
entry = Entry.objects.filter(...).exclude(...).get()
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 当成一个新对象来创建。一般情况下,你不必担心这一点,但是如果你的 model 的主键是你手动指定的,而且它的值已经在数据库中存在,那么调用 create() 就会失败,并抛出 IntegrityError。这是因为主键值必须是唯一的。所以当你手动指定主键时,记得要做好处理异常的准备。
get_or_create(defaults=None,**kwargs)
这是一个方便实际应用的方法,它根据所给的筛选条件查询对象,如果对象不存在就创建一个新对象。
它返回的是一个 (object, created) 元组,其中的 object 是所读取或是创建的对象,而 created 则是一个布尔值,它表示前面提到的 object 是否是新创建的。
这意味着它可以有效地减少代码,并且对编写数据导入脚本非常有用。例如:
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()
上面的代码会随着 model 中字段数量的激增而变得愈发庸肿。接下来用 get_or_create() 重写:
obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon',
defaults={'birthday': date(1940, 10, 9)})
在这里要注意 defaults 是一个字典,它仅适用于创建对象时为字段赋值,而并不适用于查找已存在的对象。 get_or_create() 所接收的关键字参数都会在调用 get() 时被使用,有一个参数例外,就是 defaults。在使用get_or_create() 时如果找到了对象,就会返回这个对象和 False。如果没有找到,就会实例化一个新对象,并将其保存;同时返回这个新对象和 True。创建新对象的步骤大致如下:
params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
params.update(defaults)
obj = self.model(**params)
obj.save()
用自然语言描述:从非 ‘defaults’ 关键字参数中排除含有双下划线的参数(因为双下划线表示非精确查询),然后再添加 defaults 字典的内容,如果键名与已有的关键字参数重复,就以 defaults 中的内容为准, 然后将整理后的关键字参数传递给 model 类。当然,这只是算法的简化描述,实际上对很多细节没有提及,比如对异常和边界条件的处理。如果你对此感兴趣,不妨看一下原代码。
如果你的 model 恰巧有一个字段,名称正是 defaults,而且你想在 get_or_create() 中用它做为精确查询的条件, 就得使用 ‘defaults__exact’ (之前提过 defaults 只能在创建时对对象赋值,而不能进行查询),象下面这样:
Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})
如果你手动指定了主键,那么使用 get_or_create() 方法时也会象 create() 一样,抛出类似的异常。当你手动指定了主键,若主键值已经在数据库中存在,就会抛出一个 IntegrityError 异常。
最后提一下在 Django 视图(views)中使用 get_or_create() 时要注意的一点。如上所说,对于在脚本中分析数据和添加新数据而言,get_or_create() 是非常有用的。但是如果你是在视图中使用 get_or_create() ,那么就要格外留意,要确认是在 POST 请求中使用,除非你有很必要和很充分的理由才不这么做。而在 GET 请求中使用的话,不会对数据产生任何作用。而使用 POST 的话,每个发往页面的请求都会对数据有一定的副作用。
Note
可以通过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 = 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”,但是它不能,因为它与这个book不关联,但因为title 字段是唯一的它仍然不能创建。
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()
# 可以简写为:
obj, created = Person.objects.update_or_create(
first_name='John', last_name='Lennon',
defaults={'first_name': 'Bob'},
)
和get_or_create()一样,这个方法也容易导致竞态条件,如果数据库层级没有前置唯一性会让多行同时插入。
bulk_create(objs, batch_size=None)
批量创建
>>> Entry.objects.bulk_create([
... Entry(headline="Django 1.0 Released"),
... Entry(headline="Django 1.1 Announced"),
... Entry(headline="Breaking: Django is awesome")
... ])
# 优于:
Entry.objects.create(headline="Python 1.0 Released")
Entry.objects.create(headline="Python 1.1 Planned")
以高效的方式(通常只有1次数据库操作,无论有多少对象)将提供的对象列表插入到数据库中:
注意事项:
- 不会调用模型的save()方法,并且不会发送pre_save和post_save信号。
- 不适用于多表继承场景中的子模型。
- 如果模型的主键是AutoField,则不会像save()那样检索并设置主键属性,除非数据库后端支持。
- 不适用于多对多关系。
- batch_size参数控制在单个查询中创建的对象数。
count()
返回数据库中匹配查询(QuerySet)的对象数量。 count() 不会抛出任何异常。
# 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() 而不是对返回的查询结果使用 len() 。
根据你所使用的数据库(例如 PostgreSQL 和 MySQL),count() 可能会返回长整型,而不是普通的 Python 整数。这确实是一个很古怪的举措,没有什么实际意义。
in_bulk(id_list=None, field_name=‘pk’)
接收一个主键值列表,然后根据每个主键值所其对应的对象,返回一个主键值与对象的映射字典。
>>> 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([])
{}
如果你给 in_bulk() 传递的是一个空列表明,得到就是一个空字典。
iterator(chunk_size=2000)
运行查询(QuerySet),然后根据结果返回一个 迭代器(iterator。 做为比较,使用 QuerySet 时,从数据库中读取所有记录后,一次性将所有记录实例化为对应的对象;而 iterator() 则是读取记录后,是分多次对数据实例化,用到哪个对象才实例化哪个对象。相对于一次性返回很多对象的 QuerySet,使用迭代器不仅效率更高,而且更节省内存。
请注意,在已经提交了的iterator()上使用QuerySet会强制它再次提交数据库操作,进行重复查询。此外,使用iterator()会导致先前的prefetch_related()调用被忽略,因为这两个一起优化没有意义。
latest(*fields)
根据时间字段 field_name 得到最新的对象。
下面这个例子根据 pub_date 字段得到数据表中最新的 Entry 对象:
Entry.objects.latest('pub_date')
Entry.objects.latest('pub_date', '-expire_date')
如果你在 model 中 Meta 定义了 get_latest_by 项, 那么你可以略去 field_name 参数。Django 会将 get_latest_by 做为默认设置。
和 get(), latest() 一样,如果根据所给条件没有找到匹配的对象,就会抛出 DoesNotExist 异常。earliest()和latest()可能会返回空日期的实例,可能需要过滤掉空值:
Entry.objects.filter(pub_date__isnull=False).latest('pub_date')
注意 latest()和earliest() 是纯粹为了易用易读而存在的方法。
earliest(*fields)
类似于latest()
first()
返回结果集的第一个对象, 当没有找到时返回None。如果QuerySet没有设置排序,则将会自动按主键进行排序。例如:
p = Article.objects.order_by('title', 'pub_date').first()
# 相当于:
try:
p = Article.objects.order_by('title', 'pub_date')[0]
except IndexError:
p = None
last()
类似于first(),只是返回的是查询集中最后一个对象。
aggregate(*args, **kwargs)
通过对 QuerySet 进行计算,返回一个聚合值的字典。 aggregate() 中每个参数都指定一个包含在字典中的返回值。
聚合使用关键字参数做为Annotation的名称。每个参数都有一个为其订做的名称,这个名称取决于聚合函式的函数名和聚合字段的名称。
例如,你正在处理博文,你想知道博客中一共有多少篇博文:
>>> from django.db.models import Count
>>> q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}
通过在 aggregate 指定关键字参数,你可以控制返回的聚合名称:
>>> q = Blog.objects.aggregate(number_of_entries=Count('entry'))
{'number_of_entries': 16}
exists()
如果 QuerySet 包含有数据,就返回 True 否则就返回 False。这可能是最快最简单的查询方法了.
查找具有唯一字段的模型(例如primary_key)是否是QuerySet成员最高效方法是:
entry = Entry.objects.get(pk=123)
if some_queryset.filter(pk=entry.pk).exists():
print("Entry contained in queryset")
if some_queryset.exists():
print("There is at least one object in some_queryset")
update(**kwargs)
对指定的字段执行批量更新操作,并返回匹配的行数(如果某些行已具有新值,则可能不等于已更新的行数)。
例如,要对2010年发布的所有博客条目启用评论,可以执行以下操作:
>>> 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)
1
# 返回影响到的row number
使用update()还可以防止在加载对象和调用save()之间的短时间内数据库中某些内容可能发生变化的竞争条件。
最后,意识到update()在SQL级别进行更新,因此不会在模型上调用任何save()方法,也不会发出pre_save或post_save信号(这是调用Model.save的结果)))。 如果要为具有自定义save()方法的模型更新一组记录,请循环遍历它们并调用save()
e = Entry.objects.get(id=10)
e.comments_on = False
e.save()
Entry.objects.filter(id=10).update(comments_on=False) # 所以下面的方式优于上面
delete()
批量删除QuerySet中的所有对象,并返回删除的对象个数和每个对象类型的删除次数的字典。
delete()动作是立即执行的。
不能在QuerySet上调用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})
默认情况下,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})
delete()方法执行批量删除,不会在模型上调用任何delete()方法。 但是,它会为所有已删除的对象(包括级联删除)发出pre_delete和post_delete信号。
Django需要将对象提取到内存中以发送信号和处理级联。 但是,如果没有级联和没有信号,那么Django可能会采用快速路径并删除对象而不会进入内存。 对于大型删除,这可能会导致内存使用量大幅减少。 执行查询的数量也可以减少。
设置为on_delete DO_NOTHING的ForeignKeys不会阻止删除快速路径。
请注意,在对象删除中生成的查询是一个可能会更改的实现细节。
as_manager()
一个类方法,返回Manager的实例与QuerySet的方法的副本。
explain(format=None, **options)
>>> print(Blog.objects.filter(title='My Blog').explain())
Seq Scan on blog (cost=0.00..35.50 rows=10 width=12)
数据库之间的输出显着不同。
除了Oracle之外,所有内置数据库后端都支持explain(),因为那里的实现并不简单。
format参数更改数据库默认的输出格式,通常是基于文本的。 PostgreSQL支持’TEXT’,‘JSON’,‘YAML’和’XML’。 MySQL支持’TEXT’(也称为’TRADITIONAL’)和’JSON’。
某些数据库接受可以返回有关查询的更多信息的标志。 将这些标志作为关键字参数传递 例如,使用PostgreSQL时:
>>> print(Blog.objects.filter(title='My Blog').explain(verbose=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
在某些数据库上,标志可能导致执行查询,这可能会对您的数据库产生负面影响。 例如,如果存在触发器或调用函数,PostgreSQL的ANALYZE标志可能导致数据更改,即使对于SELECT查询也是如此。
字段筛选条件(Field lookups)
字段筛选条件决定了你如何构造 SQL 语句中的 WHERE 从句。它们被指定为 QuerySet 中 filter(),exclude() 和 get() 方法的关键字参数。
exact
精确匹配。如果指定的值是 None,就会翻译成 SQL 中的 NULL (详情请查看 isnull )。
Entry.objects.get(id__exact=14)
Entry.objects.get(id__exact=None)
SELECT ... WHERE id = 14;
SELECT ... WHERE id IS NULL;
在MySQL中,数据库表的“排序规则”设置确定精确比较是否区分大小写。 这是一个数据库设置,而不是Django设置。 可以将MySQL表配置为使用区分大小写的比较,但需要进行一些权衡。 有关此内容的详细信息,请参阅数据库文档中的排序规则部分。
https://docs.djangoproject.com/zh-hans/2.1/ref/databases/#mysql-collation
iexact
忽略大小写的匹配。
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;
SQLite 用户要注意
在使用 SQLite 作为数据库,并且应用 Unicode (non-ASCII) 字符串时,请先查看 database note 中关于字符串比对那一节内容。SQLite 对 Unicode 字符串,无法做忽略大小写的匹配。
contains
大小写敏感的包含匹配。
Entry.objects.get(headline__contains='Lennon')
# 等价于 SQL :
SELECT ... WHERE headline LIKE '%Lennon%';
要注意,上述语句将匹配大标题 ‘Today Lennon honored’ ,但不能匹配 ‘today lennon honored’。
SQLite 不支持大小写敏感的 LIKE 语句;所以对 SQLite 使用 contains 时就和使用 icontains 一样。
icontains
忽略大小写的包含匹配。
Entry.objects.get(headline__icontains='Lennon')
# 等价于 SQL:
SELECT ... WHERE headline ILIKE '%Lennon%';
SQLite 用户请注意
使用 SQLite 数据库并应用 Unicode (non-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');
你也可以把查询(queryset)结果当做动态的列表,从而代替固定的列表:
inner_qs = Blog.objects.filter(name__contains='Cheddar')
entries = Entry.objects.filter(blog__in=inner_qs)
# 做动态列表的 queryset 运行时就会被做为一个子查询,如下:
SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
# 如果你传递了一个 ValuesQuerySet 或 ValuesListQuerySet (它们是调用查询集上 values() 和 values_list() 方法的返回结果) 做为 __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))
gt
大于。
Entry.objects.filter(id__gt=4)
SELECT ... WHERE id > 4;
gte
大于等于。
lt
小于。
lte
小于等于。
startswith
大小写敏感的以…开头。
Entry.objects.filter(headline__startswith='Lennon')
SELECT ... WHERE headline LIKE 'Lennon%';
istartswith
忽略大小写的以…开头。
endswith
大小写敏感的以…结尾。
iendswith
忽略大小写的以…结尾。
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';
你可以把 range 当成 SQL 中的 BETWEEN 来用,比如日期,数字,甚至是字符。
date
对于datetime字段,将值转换为日期。允许链接其他字段查找。采用日期值。
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';
当USE_TZ为True时,字段将在过滤之前转换为当前时区。
month
对日期/时间字段精确匹配月分,用整数表示月分,比如 1 表示一月,12 表示十二月。
Entry.objects.filter(pub_date__month=12)
Entry.objects.filter(pub_date__month__gte=6)
SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12';
SELECT ... WHERE EXTRACT('month' FROM pub_date) >= '6';
确切的SQL语法因每个数据库引擎而异
当USE_TZ为True时,日期时间字段将在过滤之前转换为当前时区。这需要数据库中的时区定义。
day
对于日期和日期时间字段,确切的一天匹配。允许链接其他字段查找。整整一天。
Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3)
SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3';
SELECT ... WHERE EXTRACT('day' FROM pub_date) >= '3';
确切的SQL语法因每个数据库引擎而异
当USE_TZ为True时,日期时间字段将在过滤之前转换为当前时区。这需要数据库中的时区定义。
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)
weekday
对于日期和日期时间字段,“星期几”匹配。允许链接其他字段查找。 采用表示从1(星期日)到7(星期六)的星期几的整数值。
Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)
要注意的是,这段代码将得到 pub_date 字段是星期一的所有记录 (西方习惯于将星期一看做一周的第二天),与它的年月信息无关。星期以星期天做为第一天,以星期六做为最后一天。
quarter
对于日期和日期时间字段,“一年中的四分之一”匹配。 允许链接其他字段查找。 取1到4之间的整数值,表示一年中的四分之一。
在第二季度(4月1日至6月30日)检索条目的示例:
Entry.objects.filter(pub_date__quarter=2)
time
对于datetime字段,将值转换为时间。允许链接其他字段查找。采用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)))
hour
Event.objects.filter(timestamp__hour=23)
Event.objects.filter(time__hour=5)
Event.objects.filter(timestamp__hour__gte=12)
SELECT ... WHERE EXTRACT('hour' FROM timestamp) = '23';
SELECT ... WHERE EXTRACT('hour' FROM time) = '5';
SELECT ... WHERE EXTRACT('hour' FROM timestamp) >= '12';
minute
Event.objects.filter(timestamp__minute=29)
Event.objects.filter(time__minute=46)
Event.objects.filter(timestamp__minute__gte=29)
SELECT ... WHERE EXTRACT('minute' FROM timestamp) = '29';
SELECT ... WHERE EXTRACT('minute' FROM time) = '46';
SELECT ... WHERE EXTRACT('minute' FROM timestamp) >= '29';
second
Event.objects.filter(timestamp__second=31)
Event.objects.filter(time__second=2)
Event.objects.filter(timestamp__second__gte=31)
SELECT ... WHERE EXTRACT('second' FROM timestamp) = '31';
SELECT ... WHERE EXTRACT('second' FROM time) = '2';
SELECT ... WHERE EXTRACT('second' FROM timestamp) >= '31';
isnull
根据 SQL 查询是空 IS NULL 还是非空 IS NOT NULL,返回相应的 True 或 False。
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) +')
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
iregex
Entry.objects.get(title__iregex=r'^(an?|the) +')
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
Aggregation functions(聚合方法)
expression
引用模型字段的一个字符串,或者一个query expression。
output_field
用来表示返回值的model field,一个可选的参数。
**extra
关键字参数可以给聚合函数生成的SQL提供额外的信息。
Avg
返回所给字段的平均值。
默认别名:__avg
返回类型: float
Count
根据所给的关联字段返回被关联 model 的数量。
默认别名: __count
返回类型: int
它有一个可选参数:
distinct
如果 distinct=True,那么只返回不重复的实例数量,相当于 SQL 中的 COUNT(DISTINCT field)。默认值是 False。
Max
默认别名: __max
返回类型: 与所给字段值相同
Min
返回所给字段的最小值。
默认别名: __min
返回类型: 与所给字段相同
StdDev
返回所给字段值的标准差。
默认别名: __stddev
返回类型: float
它有一个可选参数:
sample
默认情况下, StdDev 返回一个总体偏差值,但是如果 sample=True,则返回一个样本偏差值。
SQLite 本身并不提供 StdDev 支持,可以使用 SQLite 的外置模块实现这个功能。详情请查看相应的 SQLite 文档,了解如何获得和安装扩展。
Sum
计算所给字段值的总和
默认别名: __sum
返回类型: 与所给字段相同
Variance
返回所给字段值的标准方差。
默认别名: __variance
返回类型: float
它有一个可选参数:
sample
默认情况下, Variance 返回的是总体方差;如果 sample=True,返回的则是样式方差。