QuerySet
API 参考
该文档描述了 QuerySet
API 的细节。它是建立在 模型 和 数据库查询 指南的材料基础上的,因此,在阅读这篇文档之前,你可能需要阅读和理解这些文档。
在整个参考资料中,我们将使用在 数据库查询指南 中提出的 示例博客模型。
什么时候 QuerySet
被执行
QuerySet
本身可以被构造,过滤,切片,或者复制赋值等,是无需访问数据库的。只有在你需要从数据库取出数据或者,向数据库存入数据时才需要访问数据库。
你可以用以下方式执行一个 QuerySet
:
迭代。 一个
QuerySet
是可迭代的,当你第一次迭代它时,它就会执行其数据库查询。例如,这将打印数据库中所有条目的标题:for e in Entry.objects.all():
print(e.headline)
注意:如果你想做的只是确定至少一个结果是否存在,不要使用这个。使用 exists() 会更有效。
异步迭代。可以使用
async for
来迭代一个QuerySet
:async for e in Entry.objects.all():
results.append(e)
QuerySets 的同步和异步迭代器共享相同的底层缓存。
切片。 正如在 限制 QuerySet 条目数 中所解释的那样,
QuerySet
可以使用 Python 的数组切片语法进行切片。切片一个未执行的QuerySet
通常会返回另一个未执行的QuerySet
,但如果使用切片语法的step
参数,Django 会执行数据库查询,并返回一个列表。切片一个已经执行过的QuerySet
也会返回一个列表。还要注意的是,即使对一个未执行的
QuerySet
进行切片,返回另一个未执行的QuerySet
,也不允许进一步修改它(例如,添加更多的过滤器,或修改排序),因为这不能很好地翻译成 SQL,也没有明确的含义。Pickle 序列化/缓存。 关于 pickling QuerySets 时涉及的细节,请参见下一节。就本节而言,重要的是,结果是从数据库中读取的。
repr()。 当你调用
repr()
时,所在QuerySet
会被执行。这是为了方便 Python 交互式解释器,所以当你交互式使用 API 时,可以立即看到你的结果。len()。 当你调用
len()
时,会执行QuerySet
。正如你所期望的,这将返回结果列表的长度。注意:如果你只需要确定集合中的记录数(而不需要实际的对象),那么使用 SQL 的
SELECT COUNT(*)
在数据库层面上处理计数会更有效率。Django 提供了一个 count() 方法正是为了这个原因。list()。 通过调用
list()
强制执行QuerySet
。例如:entry_list = list(Entry.objects.all())
bool()。 在布尔语境中测试
QuerySet
,如使用bool()
、or
、and
或if
语句,将导致查询被执行。如果至少有一个结果,则QuerySet
为True
,否则为False
。例如:if Entry.objects.filter(headline="Test"):
print("There is at least one Entry with the headline Test")
注意:如果你只想确定至少一个结果是否存在(而不需要实际的对象),使用 exists() 更高效。
Pickle 序列化 QuerySet
如果你 pickle 序列化一个 QuerySet
,这将迫使所有结果在 pickle 序列化之前加载到内存中。Pickle 序列化通常被用作缓存的前奏,当缓存的查询集被重新加载时,你希望结果已经存在并可以使用(从数据库读取可能需要一些时间,这就违背了缓存的目的)。这意味着,当你取消缓存一个 QuerySet
时,它包含的是它被缓存时的结果,而不是当前在数据库中的结果。
如果你只想要将必要的信息存储为 pickle,在以后的时间从数据库中重新创建 QuerySet
,可以 pickle 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
属性是一个不透明的对象。它代表了查询结构的内部结构,不是公共 API 的一部分。但是,如这里所述,可以安全地(并完全支持)pickle 序列化和反序列化该属性的内容。
对 QuerySet.values_list()
的限制
如果使用 pickle 重新创建 QuerySet.values_list(),它将被转换为 QuerySet.values():
>>> import pickle
>>> qs = Blog.objects.values_list("id", "name")
>>> qs
<QuerySet [(1, 'Beatles Blog')]>
>>> reloaded_qs = Blog.objects.all()
>>> reloaded_qs.query = pickle.loads(pickle.dumps(qs.query))
>>> reloaded_qs
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>
你不能在不同版本之间共享 pickle
QuerySets
的 pickle 只对生成它们的 Django 版本有效。如果你用 Django N 版本生成了一个 pickle,就不能保证这个 pickle 在 Django N+1 版本中可以被读取。Pickle 不应该作为长期存档策略的一部分。
由于 pickle 兼容性错误可能很难诊断,比如静默损坏对象,所以当你试图在与序列化 pickle 时不同版本的 Django 中反序列化查询集时,会发出 RuntimeWarning
。
QuerySet
API
这里是 QuerySet
的正式声明:
class QuerySet
(model=None, query=None, using=None, hints=None)[源代码]
通常当你与 QuerySet
交互时,你会通过 链式过滤器 来使用它。为了实现这一目的,大多数 QuerySet
方法都会返回新的查询集。这些方法将在本节后面详细介绍。
QuerySet
类具有以下公共属性,可以用于自省:
ordered
[源代码]True
如果QuerySet
是有序的——即有一个 order_by() 子句或模型上的默认排序。否则为False
。db
[源代码]如果现在执行这个查询,将使用的数据库。
备注
QuerySet 的 query
参数的存在是为了让专门的查询子类能够重建内部查询状态。该参数的值是该查询状态的不透明表示,不是公共 API 的一部分。
返回新 QuerySet
的方法
Django 提供了一系列的 QuerySet
细化方法,这些方法可以修改 QuerySet
返回的结果类型或其 SQL 查询的执行方式。
备注
这些方法不运行数据库查询,因此它们 可以安全地在异步代码中运行,而且没有单独的异步版本。
filter()
filter
(*args, **kwargs)
返回一个新的 QuerySet
,其中包含与给定查找参数相匹配的对象。
查询参数(**kwargs
)的格式应在下文 Field lookups 中描述。多个参数通过底层 SQL 语句中的 AND
连接。
如果你需要执行更复杂的查询(例如,带有 OR
语句的查询),你可以使用 Q 对象 (*args
)。
exclude()
exclude
(*args, **kwargs)
返回一个新的 QuerySet
,其中包含与给定查找参数不匹配的对象。
查询参数(**kwargs
)的格式应在下文 Field lookups 中描述。多个参数通过底层 SQL 语句中的 AND
连接,整个过程用 NOT()
括起来。
这个例子排除了所有 pub_date
晚于 2005-1-3 且 headline
为“Hello”的条目:
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline="Hello")
用 SQL 术语来说,它的值是:
SELECT ...
WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
这个例子不包括所有 pub_date
晚于 2005-1-3 或 headline
为“Hello”的条目:
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline="Hello")
用 SQL 术语来说,它的值是:
SELECT ...
WHERE NOT pub_date > '2005-1-3'
AND NOT headline = 'Hello'
请注意,第二个例子的限制性更强。
如果你需要执行更复杂的查询(例如,带有 OR
语句的查询),你可以使用 Q 对象 (*args
)。
annotate()
annotate
(*args, **kwargs)
用所提供的 查询表达式 列表对 QuerySet
中的每个对象进行注解。表达式可以是一个简单的值,也可以是对模型(或任何相关模型)字段的引用,或者是对与 QuerySet
中的对象相关的对象进行计算的聚合表达式(平均数、总和等)。
annotate()
的每个参数都是一个注解,将被添加到返回的 QuerySet
中的每个对象。
Django 提供的聚合函数在下面的 聚合函数 中介绍。
使用关键字参数指定的注解将使用关键字作为注解的别名。匿名参数将根据聚合函数的名称和被聚合的模型字段为其生成一个别名。只有引用单个字段的聚合表达式才能成为匿名参数。其他一切都必须是关键字参数。
例如,如果你正在操作一个博客列表,你可能想确定每个博客中有多少条条目:
>>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count("entry"))
# The name of the first blog
>>> q[0].name
'Blogasaurus'
# The number of entries on the first blog
>>> q[0].entry__count
42
Blog
模型本身没有定义 entry__count
属性,但通过使用关键字参数来指定聚合函数,你可以控制注释的名称:
>>> q = Blog.objects.annotate(number_of_entries=Count("entry"))
# The number of entries on the first blog, using the name provided
>>> q[0].number_of_entries
42
关于聚合的深入讨论,见 关于聚合的专题指南。
alias()
alias
(*args, **kwargs)
与 annotate() 相同,但不是注解中的 QuerySet
对象,而是保存表达式,以便以后与其他 QuerySet
方法重复使用。当不需要表达式本身的结果,但用于过滤、排序或作为复杂表达式的一部分时,这很有用。不查找未使用的值可以从数据库中移除多余的工作,这应该会带来更好的性能。
例如,如果你想查找具有超过 5 条条目的博客,但不关心具体的条目数量,你可以这样做:
>>> from django.db.models import Count
>>> blogs = Blog.objects.alias(entries=Count("entry")).filter(entries__gt=5)
alias()
可以与 annotate()、exclude()、filter()、order_by() 和 update() 一起使用。要将别名表达式与其他方法(例如 aggregate())一起使用,你必须将其提升为注解:
Blog.objects.alias(entries=Count("entry")).annotate(
entries=F("entries"),
).aggregate(Sum("entries"))
filter() 和 order_by() 可以直接接受表达式,但表达式的构建和使用往往不发生在同一个地方(例如,QuerySet
方法创建表达式,以便以后在视图中使用)。alias()
允许逐步建立复杂的表达式,可能跨越多个方法和模块,用它们的别名指代表达式部分,只用 annotate() 来获得最终结果。
order_by()
order_by
(*fields)
默认情况下,QuerySet
返回的结果是按照模型 Meta
中的 ordering
选项给出的排序元组排序的。你可以通过使用 order_by
方法在每个 QuerySet
的基础上覆盖这一点。
举例:
Entry.objects.filter(pub_date__year=2005).order_by("-pub_date", "headline")
上述结果将按 pub_date
降序排列,然后按 headline
升序排列。"-pub_date"
前面的负号表示 降序。升序是隐含的。要随机排序,使用 "?"
,如:
Entry.objects.order_by("?")
注意:order_by('?')
查询可能会很贵,而且速度很慢,这取决于你使用的数据库后端。
要按不同模型中的字段排序,使用与跨模型关系查询时相同的语法。也就是说,字段的名称,后面是双下划线(__
),再后面是新模型中的字段名称,以此类推,想加入多少模型就加入多少。例如:
Entry.objects.order_by("blog__name", "headline")
如果你试图通过与另一个模型有关系的字段进行排序,Django 将使用相关模型上的默认排序,如果没有指定 Meta.ordering,则通过相关模型的主键进行排序。例如,由于 Blog
模型没有指定默认排序:
Entry.objects.order_by("blog")
…等同于:
Entry.objects.order_by("blog__id")
如果 Blog
有 ordering = ['name']
,那么第一个查询集将与以下内容相同:
Entry.objects.order_by("blog__name")
你也可以通过在表达式上调用 asc() 或 esc()
,按 查询表达式 排序:
Entry.objects.order_by(Coalesce("summary", "headline").desc())
asc() 和 esc()
有参数(nulls_first
和 nulls_last
)来控制如何对空值进行排序。
如果你还使用 distinct(),在按相关模型中的字段排序时要谨慎。请参见 distinct() 中的说明,解释相关模型排序如何改变预期结果。
备注
允许指定一个多值字段来对结果进行排序(例如,一个 ManyToManyField 字段,或者一个 ForeignKey 字段的反向关系)。
考虑到这种情况:
class Event(Model):
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
related_name="children",
)
date = models.DateField()
Event.objects.order_by("children__date")
在这里,每个 Event
可能有多个排序数据;每个 Event
有多个 children
将被多次返回到 order_by()
创建的新 QuerySet
中。换句话说,在 QuerySet
上使用 order_by()
可能会返回比你一开始工作更多的项目——这可能既不是预期的,也不是有用的。
因此,在使用多值字段对结果进行排序时要注意。如果 你能确定你要订购的每个项目只有一个订购数据,这种方法应该不会出现问题。如果不是,请确保结果是你所期望的。
没有办法指定排序是否应该区分大小写。关于大小写敏感,Django 会按照数据库后台的正常排序方式来排序。
你可以用 Lower
将一个字段转换为小写,从而实现大小写一致的排序:
Entry.objects.order_by(Lower("headline").desc())
如果你不想在查询中应用任何排序,甚至是默认的排序,可以不使用参数调用 order_by()。
你可以通过检查 QuerySet.ordered 属性来判断一个查询是否被排序,如果 QuerySet
以任何方式被排序,则该属性为 True
。
每次 order_by()
调用都将清除以前的任何排序。例如,此查询将按 pub_date
而不是 headline
排序:
Entry.objects.order_by("headline").order_by("pub_date")
警告
排序不是一个免费的操作。你添加到排序中的每个字段都会给你的数据库带来成本。你添加的每个外键都会隐式地包含其所有的默认排序。
如果查询没有指定顺序,那么结果将以未指定的顺序从数据库中返回。只有当按一组字段排序时,才能保证特定的排序,这些字段唯一地标识结果中的每个对象。例如,如果 name
字段不是唯一的,那么按它排序就不能保证具有相同名称的对象总是以相同的顺序出现。
reverse()
reverse
()
使用 reverse()
方法来反向返回查询集元素的顺序。第二次调用 reverse()
会将顺序恢复到正常方向。
要检索一个查询集中的“最后”五个项目,你可以这样做:
my_queryset.reverse()[:5]
请注意,这与 Python 中从一个序列的末尾切分不太一样。上面的例子会先返回最后一项,然后返回倒数第二项,以此类推。如果我们有一个 Python 序列,看 seq[-5:]
,我们会先看到倒数第五项。Django 不支持这种访问模式(从末尾切分),因为在 SQL 中不可能有效地做到这一点。
另外,请注意,reverse()
一般只应在有定义顺序的 QuerySet
上调用(例如,当对定义了默认顺序的模型进行查询时,或者当使用 order_by() 时)。如果没有为一个给定的 QuerySet
定义这样的排序,对它调用 reverse()
没有实际效果(在调用 reverse()
之前,排序是未定义的,之后也将保持未定义)。
distinct()
distinct
(*fields)
返回一个新的 QuerySet
,在其 SQL 查询中使用 SELECT DISTINCT
。这将消除查询结果中的重复记录。
默认情况下,QuerySet
不会消除重复的记录。在实践中,这很少是一个问题,因为简单的查询,如 Blog.objects.all()
不会引入重复结果行的可能性。但是,如果你的查询跨越了多个表,当 QuerySet
被执行时,就有可能得到重复的结果。这时就应该使用 distinct()
。
备注
order_by() 调用中使用的任何字段都包含在 SQL SELECT
列中。当与 distinct()
结合使用时,这有时会导致意外的结果。如果按相关模型中的字段排序,这些字段将被添加到选定的列中,它们可能会使原本重复的行看起来是不同的。由于额外的列不会出现在返回的结果中(它们只是为了支持排序),所以有时看起来像是返回了非去重的结果。
同样,如果使用 values() 查询来限制所选的列,任何 order_by() 中使用的列(或默认的模型排序)仍然会被涉及,并可能影响结果的唯一性。
这里的寓意是,如果你使用 distinct()
,要小心按相关模型排序。同样,当同时使用 distinct()
和 values() 时,在按 values() 调用中没有的字段排序时要小心。
仅在 PostgreSQL 上,你可以传递位置参数(*fields
),以指定 DISTINCT
应适用的字段名称。这相当于一个 SELECT DISTINCT ON
的 SQL 查询。这其中的区别是,对于普通的 distinct()
调用,数据库在确定哪些行是不同的时候,会比较每行中的 每个 字段。对于带有指定字段名的 distinct()
调用,数据库将只比较指定的字段名。
备注
当你指定字段名时,你 必须 在 QuerySet
中提供一个 order_by()
,而且 order_by()
中的字段必须以 distinct()
中的字段开始,顺序相同。
例如,SELECT DISTINCT ON (a)
给出了列 a
中每个值的第一行。如果你不指定顺序,你会得到一些任意的行。
示例(在第一个示例之后,只能在 PostgreSQL 上运行):
>>> Author.objects.distinct()
[...]
>>> Entry.objects.order_by("pub_date").distinct("pub_date")
[...]
>>> Entry.objects.order_by("blog").distinct("blog")
[...]
>>> Entry.objects.order_by("author", "pub_date").distinct("author", "pub_date")
[...]
>>> Entry.objects.order_by("blog__name", "mod_date").distinct("blog__name", "mod_date")
[...]
>>> Entry.objects.order_by("author", "pub_date").distinct("author")
[...]
备注
请记住 order_by() 使用任何已经定义的默认相关模型排序。你可能必须明确地按关系 _id
或引用字段排序,以确保 DISTINCT ON
表达式与 ORDER BY
子句开头的表达式匹配。例如,如果 Blog
模型定义了一个通过 name
进行 ordering
:
Entry.objects.order_by("blog").distinct("blog")
…这是不可行的,因为查询将按 blog__name
排序,从而与 DISTINCT ON
表达式不匹配。你必须明确地按关系 _id
字段(本例中为 blog_id
)或被引用的字段(blog__pk
)排序,以确保两个表达式匹配。
values()
values
(*fields, **expressions)
返回一个 QuerySet
,当用作可迭代对象时,返回字典,而不是模型实例。
其中每一个字典都代表一个对象,键与模型对象的属性名相对应。
这个示例比较了 values()
的字典与普通的模型对象:
# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith="Beatles")
<QuerySet [<Blog: Beatles Blog>]>
# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith="Beatles").values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
values()
方法接受可选的位置参数 *fields
,它指定了 SELECT
应该被限制的字段名。如果你指定了字段,每个字典将只包含你指定字段的字段键/值。如果不指定字段,每个字典将包含数据库表中每个字段的键和值。
例如:
>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values("id", "name")
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>
values()
方法还接受可选的关键字参数 **expressions
,这些参数将传递给 annotate():
>>> from django.db.models.functions import Lower
>>> Blog.objects.values(lower_name=Lower("name"))
<QuerySet [{'lower_name': 'beatles blog'}]>
你可以在排序中使用内置和 自定义查找。例如:
>>> from django.db.models import CharField
>>> from django.db.models.functions import Lower
>>> CharField.register_lookup(Lower)
>>> Blog.objects.values("name__lower")
<QuerySet [{'name__lower': 'beatles blog'}]>
在 values()
子句内部的聚合在同一 values()
子句内的其他参数之前应用。如果需要按另一个值进行分组,请将其添加到较早的 values()
子句中。例如:
>>> from django.db.models import Count
>>> Blog.objects.values("entry__authors", entries=Count("entry"))
<QuerySet [{'entry__authors': 1, 'entries': 20}, {'entry__authors': 1, 'entries': 13}]>
>>> Blog.objects.values("entry__authors").annotate(entries=Count("entry"))
<QuerySet [{'entry__authors': 1, 'entries': 33}]>
有几个微妙的地方值得一提:
如果你有一个名为
foo
的字段是一个 ForeignKey,默认的values()
调用将返回一个名为foo_id
的字典键,因为这是存储实际值的隐藏模型属性的名称(foo
属性指的是相关模型)。当你调用values()
并传递字段名时,你可以传递foo
或foo_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() 调用将忽略其额外选择的字段。组合转换和聚合需要使用两个 annotate() 调用,可以显式地使用它们,也可以作为 values() 的关键字参数。与上面一样,如果转换已经在相关字段类型上注册,第一个 annotate() 可以省略,因此以下示例是等效的:
>>> from django.db.models import CharField, Count
>>> from django.db.models.functions import Lower
>>> CharField.register_lookup(Lower)
>>> Blog.objects.values("entry__authors__name__lower").annotate(entries=Count("entry"))
<QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
>>> Blog.objects.values(entry__authors__name__lower=Lower("entry__authors__name")).annotate(
... entries=Count("entry")
... )
<QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
>>> Blog.objects.annotate(entry__authors__name__lower=Lower("entry__authors__name")).values(
... "entry__authors__name__lower"
... ).annotate(entries=Count("entry"))
<QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
当你知道你只需要一小部分可用字段的值,而且你不需要模型实例对象的功能时,它就很有用。只选择你需要使用的字段会更高效。
最后要注意的是,你可以在调用 values()
之后调用 filter()
、order_by()
等,也就是说这两个调用是相同的:
Blog.objects.values().order_by("id")
Blog.objects.order_by("id").values()
制作 Django 的人喜欢把所有影响 SQL 的方法放在前面,后面(可选的)是任何影响输出的方法(比如 values()
),但这并不重要。这是你真正炫耀自己个性的机会。
你还可以通过 OneToOneField
、ForeignKey
和 ManyToManyField
属性引用反向关系上的相关模型的字段:
>>> Blog.objects.values("name", "entry__headline")
<QuerySet [{'name': 'My blog', 'entry__headline': 'An entry'},
{'name': 'My blog', 'entry__headline': 'Another entry'}, ...]>
警告
因为 ManyToManyField 属性和逆向关系可以有多条相关的记录,包括这些可以对你的结果集的大小产生倍增效应。如果你在你的 values()
查询中包含了多个这样的字段,这一点会特别明显,在这种情况下,所有可能的组合都会被返回。
SQLite 的 JSONField
具有一些特殊的值
由于 SQLite 上实现了 JSON_EXTRACT
和 JSON_TYPE
SQL 函数的方式以及缺少 BOOLEAN
数据类型,values()
将为 JSONField 键转换返回 True
、False
和 None
,而不是 "true"
、"false"
和 "null"
字符串。
values_list()
values_list
(*fields, flat=False, named=False)
这与 values()
类似,不同之处在于在迭代时,它返回元组而不是字典。每个元组包含传递给 values_list()
调用的相应字段或表达式的值 — 因此第一个项是第一个字段,依此类推。例如:
>>> Entry.objects.values_list("id", "headline")
<QuerySet [(1, 'First entry'), ...]>
>>> from django.db.models.functions import Lower
>>> Entry.objects.values_list("id", Lower("headline"))
<QuerySet [(1, 'first entry'), ...]>
如果只传入一个字段,你还可以传递 flat
参数。如果设置为 True
,则返回的结果将是单个值,而不是 1 元组。一个示例可以更清楚地说明区别:
>>> Entry.objects.values_list("id").order_by("id")
<QuerySet[(1,), (2,), (3,), ...]>
>>> Entry.objects.values_list("id", flat=True).order_by("id")
<QuerySet [1, 2, 3, ...]>
当有多个字段时,传入 flat
是错误的。
你可以传递 named=True
以将结果作为 namedtuple() 返回:
>>> Entry.objects.values_list("id", "headline", named=True)
<QuerySet [Row(id=1, headline='First entry'), ...]>
使用命名元组可能会使使用结果更易读,但代价是将结果转化为命名元组时要付出很小的性能代价。
如果你不向 values_list()
传递任何值,它将按照声明的顺序返回模型中的所有字段。
一个常见的需求是获取特定模型实例的特定字段值。要实现这一点,使用 values_list()
后跟一个 get()
调用:
>>> Entry.objects.values_list("headline", flat=True).get(pk=1)
'First entry'
values()
和 values_list()
都是针对特定用例的优化:检索数据的子集,而不需要创建一个模型实例的开销。当处理多对多和其他多值关系(如反向外键的一对多关系)时,这个隐喻就失效了,因为“一行一对象”的假设不成立。
例如,请注意在查询跨越 ManyToManyField 时的行为:
>>> Author.objects.values_list("name", "entry__headline")
<QuerySet [('Noam Chomsky', 'Impressions of Gaza'),
('George Orwell', 'Why Socialists Do Not Believe in Fun'),
('George Orwell', 'In Defence of English Cooking'),
('Don Quixote', None)]>
有多个条目的作者出现多次,没有任何条目的作者的条目标题为 None
。
类似地,当查询反向外键时,对于没有任何作者的条目,会显示为 None
:
>>> Entry.objects.values_list("authors")
<QuerySet [('Noam Chomsky',), ('George Orwell',), (None,)]>
SQLite 的 JSONField
具有一些特殊的值
由于 SQLite 上实现了 JSON_EXTRACT
和 JSON_TYPE
SQL 函数的方式以及缺少 BOOLEAN
数据类型,values_list()
将为 JSONField 键转换返回 True
、False
和 None
,而不是 "true"
、"false"
和 "null"
字符串。
dates()
dates
(field, kind, order=’ASC’)
返回一个 QuerySet
,它的值是一个 datetime.date 对象的列表,代表 QuerySet
内容中所有可用的特定日期。
field
应该是你的模型的 DateField
的名称。kind
应该是``“year”、
“month”、
“week”`` 或 "day"
。结果列表中的每个 datetime.date 对象都被“截断”为给定的` type`。
"year"
返回字段的所有不同年份值的列表。"month"
返回该字段所有不同年/月值的列表。"week"
返回该字段的所有不同年份/星期值的列表。所有日期都是星期一。"day"
返回该字段的所有不同年/月/日值的列表。
order
,默认为 'ASC'
,应该是 'ASC'
或 'DESC'
。这指定了如何对结果进行排序。
举例:
>>> Entry.objects.dates("pub_date", "year")
[datetime.date(2005, 1, 1)]
>>> Entry.objects.dates("pub_date", "month")
[datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)]
>>> Entry.objects.dates("pub_date", "week")
[datetime.date(2005, 2, 14), datetime.date(2005, 3, 14)]
>>> Entry.objects.dates("pub_date", "day")
[datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)]
>>> Entry.objects.dates("pub_date", "day", order="DESC")
[datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)]
>>> Entry.objects.filter(headline__contains="Lennon").dates("pub_date", "day")
[datetime.date(2005, 3, 20)]
datetimes()
datetimes
(field_name, kind, order=’ASC’, tzinfo=None)
返回一个 QuerySet
,它的值是一个 datetime.datetime 对象的列表,代表 QuerySet
内容中所有可用的特定日期。
field_name
应该是你的模型中 DateTimeField
的名称。
kind
应该是 "year"
、"month"
、"week"
、"day"
、"hour"
、"minute"
或 "second"
。结果列表中的每个 datetime.datetime 对象都被“截断”为给定的 type
。
order
,默认为 'ASC'
,应该是 'ASC'
或 'DESC'
。这指定了如何对结果进行排序。
tzinfo
定义了在截断之前将日期时间转换为的时区。事实上,一个给定的日期时间根据使用的时区有不同的表示方式。这个参数必须是一个 datetime.tzinfo 对象。如果是 None
,Django 会使用 current time zone。当 USE_TZ 为 False
时,它没有效果。
备注
这个函数直接在数据库中执行时区转换。因此,你的数据库必须能够解释 tzinfo.tzname(None)
的值。这就转化为以下要求:
- SQLite:没有要求。转换是在 Python 中进行的。
- PostgreSQL:无要求(见 Time Zones )。
- Oracle:无要求(见 Choosing a Time Zone File )。
- MySQL:用 mysql_tzinfo_to_sql 加载时区表。
none()
none
()
调用 none()
将创建一个永远不会返回任何对象的查询集,在访问结果时将不执行任何查询。qs.none()
查询集是 EmptyQuerySet
的一个实例。
举例:
>>> Entry.objects.none()
<QuerySet []>
>>> from django.db.models.query import EmptyQuerySet
>>> isinstance(Entry.objects.none(), EmptyQuerySet)
True
all()
all
()
返回当前 QuerySet
(或 QuerySet
子类)的 副本。 这在以下情况下很有用:你可能想传入一个模型管理器或一个 QuerySet
,并对结果做进一步过滤。在任何一个对象上调用 all()
后,你肯定会有一个 QuerySet
可以使用。
当一个 QuerySet
被 执行 时,它通常会缓存其结果。如果数据库中的数据可能在 QuerySet
被评估后发生了变化,你可以通过调用 all()
对以前执行过的 QuerySet
进行更新。
union()
union
(*other_qs, all=False)
使用 SQL 的 UNION
操作符来组合两个或多个 QuerySet
的结果。例如:
>>> qs1.union(qs2, qs3)
UNION
操作符默认只选择不同的值。要允许重复的值,使用 all=True
参数。
union()
, intersection()
, 和 difference()
返回第一个 QuerySet
类型的模型实例,即使参数是其他模型的 QuerySet
。只要所有 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()、exists()、order_by() 与 values() / values_list() )允许在结果 QuerySet
中使用。此外,数据库对组合查询中允许的操作也有限制。例如,大多数数据库不允许在组合查询中使用 LIMIT
或 OFFSET
。
intersection()
intersection
(*other_qs)
使用 SQL 的 INTERSECT
操作符来返回两个或多个 QuerySet
的共享元素。例如:
>>> qs1.intersection(qs2, qs3)
一些限制见 union()。
difference()
difference
(*other_qs)
使用 SQL 的 EXCEPT
运算符,只保留在一个 QuerySet
中存在而在其他某些 QuerySet
中不存在的元素。例如:
>>> qs1.difference(qs2, qs3)
一些限制见 union()。
select_related()
select_related
(*fields)
返回一个 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("blog").get(id=5)
# Doesn't hit the database, because e.blog has been prepopulated
# in the previous query.
b = e.blog
你可以使用 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
和 相关的 City
:
# 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()
的字段列表中引用任何 ForeignKey 或 OneToOneField 关系。
你也可以在传递给 select_related
的字段列表中引用一个 OneToOneField 的反方向——也就是说,你可以遍历一个 OneToOneField 回到定义字段的对象上。不指定字段名,而是使用 related_name 作为相关对象上的字段。
在某些情况下,你可能希望调用 select_related()
来处理很多相关对象,或者你不知道所有的关系。在这些情况下,我们可以调用 select_related()
,但不使用参数。这将跟随它能找到的所有非空的外键——必须指定可空的外键。在大多数情况下,不建议这样做,因为这可能会使基础查询变得更加复杂,并返回比实际需要的更多数据。
如果需要清除在过去的 select_related
调用中添加的相关字段列表,可以将 None
作为参数传递:
>>> without_relations = queryset.select_related(None)
链式调用 select_related
的工作方式与其他方法类似,即 select_related('foo', 'bar')
等同于 select_related('foo').select_related('bar')
。
prefetch_related()
prefetch_related
(*lookups)
返回一个 QuerySet
,它将在一个批次中自动检索每个指定查询的相关对象。
这与 select_related
有类似的目的,二者都是为了阻止因访问相关对象而引起的数据库查询潮,但策略却完全不同。
select_related
的工作方式是创建一个 SQL 连接,并在 SELECT
语句中包含相关对象的字段。出于这个原因,select_related
在同一个数据库查询中得到相关对象。然而,为了避免因跨越“many”关系进行连接而产生更大的结果集,select_related
仅限于单值关系——外键和一对一。
另一方面,prefetch_related
为每个关系执行单独的查找,并在 Python 中执行“连接”。这使它能够预取许多对多、对一和 GenericRelation 对象,这是使用 select_related
无法完成的,除了外键和一对一关系外,它还支持 GenericForeignKey 的预取,但是必须在 GenericPrefetch 的 querysets
参数中提供每个 ContentType
的查询集。
Changed in Django 5.0:
已经添加了对使用非同构结果集预取 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)"...
这样做的问题是,每次 Pizza.__str__()
要求 self.toppings.all()
都要查询数据库,所以 Pizza.objects.all()
会在 Toppings 表上对 Pizza QuerySet
中的 每 项进行查询。
我们可以使用 prefetch_related
减少到只有两个查询:
>>> Pizza.objects.prefetch_related("toppings")
这意味着每一个 Pizza
都有一个 self.toppings.all()
;现在每次调用 self.toppings.all()
时,不必再去数据库中寻找这些项目,而是在一次查询中填充的预设 QuerySet
缓存中找到它们。
也就是说,所有相关的顶点都将在一次查询中被获取,并被用来制作 QuerySets
,其中有一个预先填充的相关结果的缓存;然后这些 QuerySets
被用于 self.toppings.all()
的调用。
prefetch_related()
中的附加查询是在 QuerySet
开始执行和主要查询被执行后执行的。
请注意,没有机制可以防止另一个数据库查询在主查询和附加查询之间更改项目,这可能会导致不一致的结果。例如,如果在主查询执行后删除了一份 Pizza
,在附加查询中将不会返回其配料,看起来就像披萨没有配料一样:
>>> Pizza.objects.prefetch_related("toppings")
# "Hawaiian" Pizza was deleted in another shell.
<QuerySet [<Pizza: Hawaiian ()>, <Pizza: Seafood (prawns, smoked salmon)>]>
如果你有一个作为模型实例的可迭代对象,你可以使用 prefetch_related_objects() 函数在这些实例上预取相关属性。
请注意,主 QuerySet
的结果缓存和所有指定的相关对象将被完全加载到内存中。这改变了 QuerySets
的典型行为,它通常试图避免在需要之前将所有对象加载到内存中,即使在数据库中执行了一个查询之后。
备注
请记住,与 QuerySets
一样,任何后续的链式方法,如果意味着不同的数据库查询,将忽略之前缓存的结果,并使用新的数据库查询来检索数据。所以,如果你写了以下内容:
>>> pizzas = Pizza.objects.prefetch_related("toppings")
>>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]
…那么 pizza.toppings.all()
已经被预取的事实对你没有帮助。prefetch_related('toppings')
意味着 pizza.toppings.all()
,但 pizza.toppings.filter()
是一个新的、不同的查询。预设缓存在这里帮不上忙,事实上它损害了性能,因为你做了一个你没有使用过的数据库查询。所以要谨慎使用这个功能!
Also, if you call the database-altering methods add(), create(), remove(), clear() or set(), on related managers, any prefetched cache for the relation will be cleared.
你也可以用普通的 join 语法来做相关字段的相关字段。假设我们在上面的例子中多了一个模型:
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")
这将预取所有属于餐馆的披萨,以及所有属于这些披萨的配料。这将导致总共 3 个数据库查询——一个查询餐厅,一个查询披萨,一个查询配料。
>>> Restaurant.objects.prefetch_related("best_pizza__toppings")
这将为每家餐厅获取最好的比萨饼和最好的比萨饼的所有配料。这将在 3 个数据库查询中完成——一个查询餐厅,一个查询“最佳披萨”,一个查询配料。
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 时间。
虽然 prefetch_related
支持预取 GenericForeignKey
关系,但查询次数将取决于数据。由于一个 GenericForeignKey
可以引用多个表中的数据,所以需要对每个被引用的表进行一次查询,而不是对所有项目进行一次查询。如果还没有获取相关的行,可以对 ContentType
表进行额外的查询。
prefetch_related
在大多数情况下,将使用使用“IN”操作符的 SQL 查询来实现。这意味着对于一个大的 QuerySet
可能会生成一个大的“IN”子句,这取决于数据库,在解析或执行 SQL 查询时可能会有自己的性能问题。一定要针对自己的用例进行剖析!
如果使用 iterator()
运行查询,只有在提供了 chunk_size
的值时,才会观察到 prefetch_related()
调用。
你可以使用 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
参数将预取结果分配给一个自定义属性。结果将直接存储在一个列表中。
这允许用不同的 QuerySet
预取同一关系多次;例如:
>>> 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
创建的查找仍然可以像往常一样被其他查找遍历。
>>> vegetarian_pizzas = Pizza.objects.filter(vegetarian=True)
>>> Restaurant.objects.prefetch_related(
... Prefetch("pizzas", queryset=vegetarian_pizzas, to_attr="vegetarian_menu"),
... "vegetarian_menu__toppings",
... )
在对预取结果进行过滤时,建议使用 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()
自定义预取也适用于单一的相关关系,如前向 ForeignKey
或 OneToOneField
。一般来说,你会希望使用 select_related() 来处理这些关系,但在一些情况下,使用自定义 QuerySet
进行预取是有用的。
你要使用一个
QuerySet
,对相关模型进行进一步的预取。你想只预取相关对象的一个子集。
你要使用性能优化技术,比如 递延字段。
>>> queryset = Pizza.objects.only("name")
>>>
>>> restaurants = Restaurant.objects.prefetch_related(
... Prefetch("best_pizza", queryset=queryset)
... )
在使用多个数据库时,Prefetch
将尊重您的数据库选择。如果内部查询没有指定数据库,它将使用外部查询选择的数据库。以下所有方式都是有效的:
>>> # Both inner and outer queries will use the 'replica' database
>>> Restaurant.objects.prefetch_related("pizzas__toppings").using("replica")
>>> Restaurant.objects.prefetch_related(
... Prefetch("pizzas__toppings"),
... ).using("replica")
>>>
>>> # Inner will use the 'replica' database; outer will use 'default' database
>>> Restaurant.objects.prefetch_related(
... Prefetch("pizzas__toppings", queryset=Toppings.objects.using("replica")),
... )
>>>
>>> # Inner will use 'replica' database; outer will use 'cold-storage' database
>>> Restaurant.objects.prefetch_related(
... Prefetch("pizzas__toppings", queryset=Toppings.objects.using("replica")),
... ).using("cold-storage")
备注
查询的顺序很重要。
下面举例说明:
>>> prefetch_related("pizzas__toppings", "pizzas")
即使它是无序的,这也是可行的,因为 'pizzas__toppings'
已经包含了所有需要的信息,因此第二个参数 'pizzas'
实际上是多余的。
>>> prefetch_related("pizzas__toppings", Prefetch("pizzas", queryset=Pizza.objects.all()))
这将引发一个 ValueError
,因为它试图重新定义一个先前看到的查询的查询集。请注意,一个隐式查询集被创建为遍历 ''pizzas'
作为 ''pizzas__toppings'
查询的一部分。
>>> prefetch_related("pizza_list__toppings", Prefetch("pizzas", to_attr="pizza_list"))
这将触发一个 AttributeError
,因为 'pizza_list'
在处理 'pizza_list__toppings'
时还不存在。
这种考虑不限于使用 Prefetch
对象。一些高级技术可能要求按照特定的顺序进行查找,以避免产生额外的查询;因此,建议总是仔细地安排 prefetch_related
参数的顺序。
extra()
extra
(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
有时候,Django 查询语法本身并不能很容易地表达一个复杂的 WHERE
子句。对于这些边缘情况,Django 提供了 extra()
QuerySet
修饰符——用于将特定的子句注入到由 QuerySet
生成的 SQL 中。
在万不得已的情况下使用这种方法
这是一个老的 API,我们的目标是在未来的某个时间点废弃。只有当你不能使用其他的查询集方法来表达你的查询时才使用它。如果你确实需要使用它,请使用 QuerySet.extra keyword 并和你的用例(请先检查现有的工单列表)一起 file a ticket ,这样我们就可以增强 QuerySet API 以允许删除 extra()
。我们不再改进或修复该方法的错误。
例如,extra()
的这种用法:
>>> qs.extra(
... select={"val": "select col from sometable where othercol = %s"},
... select_params=(someparam,),
... )
相当于:
>>> qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))
使用 RawSQL 的主要好处是,如果需要的话,可以设置 output_field
。主要的缺点是,如果你在原始 SQL 中引用了查询集的某个表的别名,那么 Django 有可能会改变这个别名(例如,当查询集在另一个查询中被用作子查询时)。
警告
每当你使用 extra()
时,你应该非常小心。每次使用它时,你应该使用 params
来转义任何用户可以控制的参数,以防止 SQL 注入攻击。
你也不能在 SQL 字符串中引用占位符。这个例子因为在 %s
周围的引号而容易受到 SQL 注入的影响。
SELECT col FROM sometable WHERE othercol = '%s' # unsafe!
你可以阅读更多关于 Django 的 SQL 注入保护 的工作原理。
根据定义,这些额外的查找可能无法移植到不同的数据库引擎中(因为你明确地编写了 SQL 代码),并且违反了 DRY 原则,所以你应该尽可能地避免它们。
指定 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 将给定的 SQL 片段直接插入到
SELECT
语句中,所以上面例子的 SQL 结果将是这样的。SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent
FROM blog_entry;
下一个例子更高级;它做了一个子查询,给每个结果的
Blog
对象一个entry_count
属性,一个相关Entry
对象的整数:Blog.objects.extra(
select={
"entry_count": "SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id"
},
)
在这个特殊的情况下,我们利用了这样一个事实,即查询在其
FROM
子句中已经包含blog_blog
表。上述例子的 SQL 结果是:
SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count
FROM blog_blog;
需要注意的是,大多数数据库引擎要求在子查询周围加上括号,而 Django 的
select
子句则不需要。还需要注意的是,一些数据库后端,比如一些 MySQL 版本,不支持子查询。在一些罕见的情况下,你可能希望在
extra(select=...)
中给 SQL 片段传递参数。为此,使用select_params
参数。这样做就可以了,比如:
Blog.objects.extra(
select={"a": "%s", "b": "%s"},
select_params=("one", "two"),
)
如果你需要在选择字符串中使用
%s
,请使用序列%%s
。where
/tables
你可以通过使用
where
来定义明确的 SQLWHERE
子句——也许是为了执行非明确的连接。你可以通过使用tables
手动添加表到 SQLFROM
子句中。where
和tables
都采用一个字符串列表。所有where
参数都与任何其他搜索标准“AND”在一起。举例:
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
…翻译成(大致)下面的 SQL:
SELECT * FROM blog_entry WHERE (foo='a' OR bar='a') AND (baz='a')
如果使用
tables
参数时,要注意指定查询中已经使用过的表,当你通过tables
参数添加额外的表时,Django 会认为你希望额外包含该表,如果已经包含的话。当你通过tables
参数添加额外的表时,Django 会认为你希望额外地包含该表,如果它已经被包含了。这就会产生一个问题,因为表名会被赋予一个别名。如果一个表在一条 SQL 语句中多次出现,那么第二次和后续的表必须使用别名,这样数据库才能区分它们。如果你指的是你在额外的where
参数中添加的额外表,这就会造成错误。一般情况下,你只会添加查询中还没有出现的额外表。但是,如果确实出现了上面概述的情况,有几种解决办法。首先,看看是否可以不包含额外的表,而使用已经在查询中出现的表。如果不可能的话,把你的
extra()
调用放在查询集构造的前面,这样你的表就是那个表的第一次使用。最后,如果所有其他方法都失败了,看一下产生的查询,重写你的where
加法,使用给你的额外表的别名。每次以同样的方式构造查询集时,别名都会是一样的,所以你可以信赖别名不会改变。order_by
如果你需要使用你通过
extra()
所包含的一些新字段或表来对结果查询集进行排序,请使用extra()
的order_by
参数,并传入一串字符串。这些字符串应该是模型字段(就像在查询集上的普通 order_by() 方法一样),形式为table_name.column_name
或者是你在extra()
的select
参数中指定的列的别名。例子:
q = Entry.objects.extra(select={"is_recent": "pub_date > '2006-01-01'"})
q = q.extra(order_by=["-is_recent"])
这将把
is_recent
为真的所有项目排在结果集的前面(True
按降序排列在False
之前)。顺便说一下,这表明你可以多次调用
extra()
,它将按照你的期望行事(每次增加新的约束)。params
上面描述的
where
参数可以使用标准的 Python 数据库字符串占位符——'%s'
来表示数据库引擎应该自动引用的参数。params
参数是一个要被替换的额外参数的列表。举例:
Entry.objects.extra(where=["headline=%s"], params=["Lennon"])
始终使用
params
而不是直接将值嵌入where
,因为params
将确保根据你的特定后台正确引用值。例如,引号将被正确转义。不好的:
Entry.objects.extra(where=["headline='Lennon'"])
正确的:
Entry.objects.extra(where=["headline=%s"], params=["Lennon"])
警告
如果你在 MySQL 上执行查询,请注意 MySQL 的静默强制类型转换可能会在混合类型时导致意外的结果。如果你在一个字符串类型的列上查询,但却有一个整数值,MySQL 会在执行比较之前将表中所有值的类型强制转换为整数。例如,如果你的表中包含值 'abc'
、'def'
,而你查询 WHERE mycolumn=0
,这两行都会匹配。为了防止这种情况发生,在查询中使用该值之前,请执行正确的类型转换。
defer()
defer
(*fields)
在一些复杂的数据建模情况下,你的模型可能包含很多字段,其中一些字段可能包含很多数据(例如,文本字段),或者需要昂贵的处理来将它们转换为 Python 对象。如果你在某些情况下使用查询集的结果,在最初获取数据时不知道是否需要这些特定的字段,你可以告诉 Django 不要从数据库中检索这些字段。
通过将不加载的字段名称传递给 defer()
:
Entry.objects.defer("headline", "body")
一个有递延字段的查询集仍然会返回模型实例。如果你访问每个递延字段,将从数据库中检索该字段(一次一个,而不是同时访问所有的递延字段)。
备注
延迟加载的字段不会像这样从异步代码中惰性加载。相反,你将得到一个 SynchronousOnlyOperation
异常。如果你正在编写异步代码,不应尝试访问任何你已经使用 defer()
推迟加载的字段。
你可以多次调用 defer()
。每次调用都会在推迟的集合中增加新的字段:
# Defers both the body and headline fields.
Entry.objects.defer("body").filter(rating=5).defer("headline")
字段被添加到递延集的顺序并不重要。用已经被递延的字段名调用 defer()
是无害的(该字段仍将被递延)。
你可以通过使用标准的双下划线符号来分隔相关的字段,来推迟加载相关模型中的字段(如果相关模型是通过 select_related() 加载的):
Blog.objects.select_related().defer("entry__headline", "entry__body")
如果你想清除一组递延字段,将 None
作为参数传递给 defer()
:
# Load all fields immediately.
my_queryset.defer(None)
模型中的一些字段不会被推迟,即使你要求它们。你永远不能推迟加载主键。如果你使用 select_related() 来检索相关的模型,你不应该推迟从主模型连接到相关模型的字段的加载,这样做会导致一个错误。
类似地,从聚合调用 defer()
(或其对应的 only())包括一个参数是没有意义的(例如使用 annotate() 的结果)。这样做将引发异常。聚合值将始终被提取到生成的查询集中。
备注
defer()
方法(和它的表兄弟 only(),见下面)只适用于进阶使用情况。它们提供了一种优化,当你仔细分析了你的查询,了解了你所需要的 确切 的信息,并且测算出返回你所需要的字段和模型的全部字段集之间的差异会很大。
即使你认为自己处于高级用例情况下,只有在在查询集加载时无法确定是否需要额外字段时才使用 defer()
。如果你经常加载和使用数据的特定子集,你可以做出的最好选择是将模型进行规范化,并将未加载的数据放入一个单独的模型(和数据库表中)。如果出于某种原因列 必须 保留在一个表中,请创建一个具有 Meta.managed = False
(请参阅 managed 属性 文档)的模型,其中仅包含您通常需要加载和使用的字段,然后在可能会调用 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"
# Two equivalent QuerySets:
CommonlyUsedModel.objects.all()
ManagedModel.objects.defer("f2")
如果很多字段需要在非托管模型中重复,最好的办法是创建一个共享字段的抽象模型,然后让非托管模型和托管模型从抽象模型中继承。
备注
当调用 save() 对有延迟字段的实例进行保存时,只有加载的字段会被保存。更多细节请参见 save()。
only()
only
(*fields)
only()
方法实际上是 defer() 的相反。只有传递到此方法中的字段,并且尚未指定为延迟加载的字段,在查询集评估时会立即加载。
如果你有一个几乎所有字段都需要延迟加载的模型,使用 only()
来指定补充的字段集合可以简化代码。
假设你有一个模型,其字段为 name
、age
和 biography
。就递延字段而言,以下两个查询集是相同的:
Person.objects.defer("age", "biography")
Person.objects.only("name")
每当你调用 only()
时,它就会 替换 要立即加载的字段集。该方法的名称是记号式的:仅 那些字段被立即加载;其余的字段被推迟。因此,连续调用 only()
的结果是只考虑最后的字段:
# This will defer all fields except the headline.
Entry.objects.only("body", "rating").only("headline")
由于 defer()
以递增的方式行事(将字段添加到递延列表中),你可以将对 only()
和 defer()
的调用结合起来,事情就会符合逻辑:
# Final result is that everything except "headline" is deferred.
Entry.objects.only("headline", "body").defer("body")
# Final result loads headline immediately.
Entry.objects.defer("body").only("headline", "body")
defer() 文档注释中的所有注意事项也适用于 only()
。谨慎使用,只有在用尽其他选项后才能使用。
使用 only()
并省略使用 select_related() 请求的字段也是错误的。另一方面,如果在没有任何参数的情况下调用 only()
,将返回查询集提取的每个字段(包括注释)。
与 defer()
一样,你不能从异步代码中访问未加载的字段并期望它们加载。相反,你将得到一个 SynchronousOnlyOperation
异常。确保你可能访问的所有字段都在你的 only()
调用中。
备注
当调用 save() 对有延迟字段的实例进行保存时,只有加载的字段会被保存。更多细节请参见 save()。
备注
在在 only()
之后使用 defer() 时,defer
中的字段将覆盖在两者中都列出的字段的 only()
。
using()
using
(alias)
如果你使用多个数据库,该方法用于控制 QuerySet
将针对哪个数据库进行评估。 本方法的唯一参数是数据库的别名,定义在 DATABASES 中。
例如:
# queries the database with the 'default' alias.
>>> Entry.objects.all()
# queries the database with the 'backup' alias
>>> Entry.objects.using("backup")
select_for_update()
select_for_update
(nowait=False, skip_locked=False, of=(), no_key=False)
返回一个查询集,该查询集将锁定行直到事务结束,从而在受支持的数据库上生成 SELECT ... FOR UPDATE
SQL 语句。
例子:
from django.db import transaction
entries = Entry.objects.select_for_update().filter(author=request.user)
with transaction.atomic():
for entry in entries:
...
当查询集被执行时(这里是 for entry in entries
),所有匹配的条目将被锁定,直到事务块结束,这意味着其他事务将被阻止改变或获取它们的锁。
通常情况下,如果另一个事务已经获得了所选行的锁,那么查询将被阻塞,直到锁被释放。如果这不是你想要的行为,调用 select_for_update(nowait=True)
。这将使调用非阻塞。如果一个冲突的锁已经被另一个事务获取,那么当查询集被评估时,将引发 DatabaseError。你也可以通过使用 select_for_update( skip_locked=True)
来忽略锁定的记录。nowait
和 skip_locked
是相互排斥的,在启用这两个选项的情况下调用 select_for_update()
会导致一个 ValueError。
默认情况下,select_for_update()
锁定所有被查询选择的行。例如,在 select_related() 中指定的相关对象的行,除了查询集模型的行之外,也会被锁定。如果不希望这样,可以在 select_for_update(of=(...))
中使用与 select_related() 相同的字段语法指定你要锁定的相关对象。使用 'self'
来表示查询集的模型。
在 select_for_update(of=(...))
中锁定父模型
如果在使用 多表继承 时要锁定父模型,必须在 of
参数中指定父链接字段(默认为 <parent_model_name>_ptr
)。例如:
Restaurant.objects.select_for_update(of=("self", "place_ptr"))
在指定的字段使用 select_for_update(of=(...))
如果你想锁定模型并指定选定的字段,例如使用 values(),你必须从每个模型的 of
参数中至少选择一个字段。没有选定字段的模型将不会被锁定。
仅在 PostgreSQL 上,你可以通过 no_key=True
来获得一个较弱的锁,这仍然允许在锁存在的情况下,创建仅仅引用锁定的行(例如,通过外键)。PostgreSQL 文档中有更多关于 行级锁模式的细节 。
你不能在可空关联上使用 select_for_update()
:
>>> Person.objects.select_related("hometown").select_for_update()
Traceback (most recent call last):
...
django.db.utils.NotSupportedError: FOR UPDATE cannot be applied to the nullable side of an outer join
为了避免这个限制,如果你不关心空对象,可以将它们排除在外:
>>> Person.objects.select_related("hometown").select_for_update().exclude(hometown=None)
<QuerySet [<Person: ...)>, ...]>
postgresql
、oracle
和 mysql
数据库后端支持 select_for_update()
。然而,MariaDB 只支持 nowait
参数,MariaDB 10.6+ 还支持 skip_locked
参数,而 MySQL 支持 nowait
、skip_locked
和 of
参数。no_key
参数仅在 PostgreSQL 上受支持。
在使用不支持这些选项的数据库后端(如 MySQL)向 select_for_update()``传递 ``nowait=True
、skip_locked=True
、no_key=True
或 of
,会产生一个 NotSupportedError。这可以防止代码意外地阻塞。
在支持 SELECT ... FOR UPDATE
的后端上,用 select_for_update()
在自动提交模式下执行一个查询集是一个 TransactionManagementError 错误,因为在这种情况下行没有被锁定。如果允许这样做,这将促进数据损坏,并且很容易通过调用期望在一个事务之外的事务中运行的代码而引起。
在不支持 SELECT ... FOR UPDATE
的后端(比如 SQLite)使用 select_for_update()
不会有任何影响。SELECT ... FOR UPDATE
不会被添加到查询中,如果 select_for_update()
在自动提交模式下使用,也不会出现错误。
警告
虽然 select_for_update()
通常在自动提交模式下会失败,但由于 TestCase 会自动将每个测试封装在一个事务中,因此在一个 TestCase
中调用 select_for_update()
甚至在 atomic()`()
块外调用 select_for_update()
会(也许会出乎意料地)通过而不会引发 TransactionManagementError
。为了正确测试 select_for_update()
,你应该使用 TransactionTestCase。
可能不支持某些表达方式
PostgreSQL 不支持 select_for_update()
与 Window 表达式。
raw()
raw
(raw_query, params=(), translations=None, using=None)
获取一个原始 SQL 查询,执行它,并返回一个 django.db.models.query.RawQuerySet
实例。这个 RawQuerySet
实例可以像普通的 QuerySet
一样进行迭代,提供对象实例。
更多信息请参见 执行原生 SQL 查询。
警告
raw()
总是触发一个新的查询,并且不考虑以前的过滤。因此,它通常应该从 Manager
或从一个新的 QuerySet
实例中调用。
返回新 QuerySet
的操作符
组合的查询集必须使用相同的模型。
AND(&
)
使用 SQL 的 AND
运算符以类似于链接过滤器的方式合并两个 QuerySet
。
以下的都是相同的:
Model.objects.filter(x=1) & Model.objects.filter(y=2)
Model.objects.filter(x=1).filter(y=2)
SQL 等价于:
SELECT ... WHERE x=1 AND y=2
OR(|
)
使用 SQL OR
操作符将两个 QuerySet
组合起来。
以下的都是相同的:
Model.objects.filter(x=1) | Model.objects.filter(y=2)
from django.db.models import Q
Model.objects.filter(Q(x=1) | Q(y=2))
SQL 等价于:
SELECT ... WHERE x=1 OR y=2
|
不是一个换元运算,因为可能会产生不同的(虽然是等价的)查询。
XOR (^
)
使用 SQL 的 XOR
运算符合并两个 QuerySet
。XOR
表达式匹配由奇数个操作数匹配的行。
以下的都是相同的:
Model.objects.filter(x=1) ^ Model.objects.filter(y=2)
from django.db.models import Q
Model.objects.filter(Q(x=1) ^ Q(y=2))
SQL 等价于:
SELECT ... WHERE x=1 XOR y=2
备注
XOR
在 MariaDB 和 MySQL 上受到原生支持。在其他数据库上,x ^ y ^ ... ^ z
会被转换为等效的形式:
(x OR y OR ... OR z) AND
1=MOD(
(CASE WHEN x THEN 1 ELSE 0 END) +
(CASE WHEN y THEN 1 ELSE 0 END) +
...
(CASE WHEN z THEN 1 ELSE 0 END),
2
)
Changed in Django 5.0:
在旧版本中,在没有本地支持 SQL XOR
运算符的数据库上,XOR
返回由一个操作数精确匹配的行。先前的行为与 MySQL、MariaDB 和 Python 的行为不一致。
不返回 QuerySet
的方法
以下 QuerySet
方法执行 QuerySet
,并返回 QuerySet
以外的东西。
这些方法不使用缓存(参见 缓存和 QuerySet)。相反,它们每次被调用时都会查询数据库。
因为这些方法评估了 QuerySet,它们是阻塞调用,因此它们的主要(同步)版本不能从异步代码中调用。因此,每个方法都有一个带有 a
前缀的相应异步版本 - 例如,你可以使用 await aget(…)
而不是 get(…)
。
通常,除了它们的异步性质之外,它们的行为没有太大区别,但任何差异都在下面的方法旁边进行了说明。
get()
get
(*args, **kwargs)
aget
(*args, **kwargs)
异步版本: aget()
返回与给定的查找参数相匹配的对象,其格式应该在 Field lookups 中描述。你应该使用保证唯一的查询,比如主键或唯一约束中的字段。例如:
Entry.objects.get(id=1)
Entry.objects.get(Q(blog=blog) & Q(entry_number=1))
如果你希望一个查询集已经返回一条记录,你可以在没有任何参数的情况下使用 get()
来返回该行的对象:
Entry.objects.filter(pk=1).get()
如果 get()
没有找到任何对象,它会引发一个 Model.DoesNotExist 异常:
Entry.objects.get(id=-999) # raises Entry.DoesNotExist
如果 get()
发现多个对象,会引发一个 Model.MultipleObjectsReturned 异常:
Entry.objects.get(name="A Duplicated Name") # raises Entry.MultipleObjectsReturned
这两个异常类都是模型类的属性,并且特定于该模型。如果你想对不同模型的多个 get()
调用处理这样的异常,可以使用它们的通用基类。例如,你可以使用 django.core.exceptions.ObjectDoesNotExist 来处理 DoesNotExist
来自多个模型的异常:
from django.core.exceptions import ObjectDoesNotExist
try:
blog = Blog.objects.get(id=1)
entry = Entry.objects.get(blog=blog, entry_number=1)
except ObjectDoesNotExist:
print("Either the blog or entry doesn't exist.")
create()
create
(**kwargs)
acreate
(**kwargs)
异步版本: acreate()
一种方便的方法,用于创建一个对象并一步到位地保存。 因此:
p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
和:
p = Person(first_name="Bruce", last_name="Springsteen")
p.save(force_insert=True)
是等效的。
force_insert 参数在其他地方有说明,但它的意思是总是会创建一个新的对象。通常情况下,你不需要担心这个问题。但是,如果你的模型中包含了一个你设置的手动主键值,而且如果这个值已经存在于数据库中,那么对 create()
的调用就会以一个 IntegrityError 失败,因为主键必须是唯一的。如果使用手动主键,要做好处理异常的准备。
get_or_create()
get_or_create
(defaults=None, **kwargs)
aget_or_create
(defaults=None, **kwargs)
异步版本: aget_or_create()
一个方便的方法,用于查找具有给定 kwargs
的对象(如果你的模型对所有字段都有默认值,则可能为空),必要时创建一个对象。
返回 (object, created)
的元组,其中 object
是检索或创建的对象,created
是指定是否创建新对象的布尔值。
这是为了防止在并行进行请求时创建重复的对象,并作为样板代码的快捷方式。 例如:
try:
obj = Person.objects.get(first_name="John", last_name="Lennon")
except Person.DoesNotExist:
obj = Person(first_name="John", last_name="Lennon", birthday=date(1940, 10, 9))
obj.save()
在这里,如果是并发请求,可能会多次尝试用相同的参数保存一个 Person
。为了避免这种竞争条件,可以使用 get_or_create()
重写上面的例子,比如:
obj, created = Person.objects.get_or_create(
first_name="John",
last_name="Lennon",
defaults={"birthday": date(1940, 10, 9)},
)
任何传递给 get_or_create()
的关键字参数—— 除了 一个叫 defaults
的可选参数——都将在 get() 调用中使用。如果找到了一个对象,get_or_create()
返回该对象的元组和 False
。
警告
假设数据库强制执行关键字参数的唯一性(参见 unique 或 unique_together),这个方法是原子性的。如果关键字参数中使用的字段没有唯一性约束,那么对该方法的并发调用可能会导致插入具有相同参数的多条记录。
你可以通过将 get_or_create()
和 filter()
串联起来,并使用 Q 对象 为检索对象指定更复杂的条件。例如,如果 Robert 或 Bob Marley 存在,则检索 Robert 或 Bob Marley,否则创建后者:
from django.db.models import Q
obj, created = Person.objects.filter(
Q(first_name="Bob") | Q(first_name="Robert"),
).get_or_create(last_name="Marley", defaults={"first_name": "Bob"})
如果找到多个对象,get_or_create()
会引发 MultipleObjectsReturned。如果没有找到对象,get_or_create()
将实例化并保存一个新对象,返回一个新对象的元组和 True
。新对象将大致按照以下算法创建:
params = {k: v for k, v in kwargs.items() if "__" not in k}
params.update({k: v() if callable(v) else v for k, v in defaults.items()})
obj = self.model(**params)
obj.save()
在英语中,这意味着从任何不包含双下划线的非 'defaults'
关键字参数开始(这将表明一个非精确的查找)。然后添加 defaults
的内容,必要时覆盖任何键,并将结果作为模型类的关键字参数。如果 defaults
中存在任何可调用对象,则对其进行评估。正如上面所提示的,这是对所使用算法的简化,但它包含了所有相关的细节。内部实现有比这更多的错误检查,并处理一些额外的边缘条件;如果你感兴趣,请阅读代码。
如果你有一个名为 defaults
的字段,并且想在 get_or_create()
中使用它作为精确查询,使用 'defaults__exact'
,像这样:
Foo.objects.get_or_create(defaults__exact="bar", defaults={"defaults": "baz"})
当你使用手动指定的主键时,get_or_create()
方法的错误行为与 create() 类似。如果需要创建一个对象,而该键已经存在于数据库中,则会引发一个 IntegrityError。
最后,关于在 Django 视图中使用 get_or_create()
的一些建议。请确保只在 POST
请求中使用它,除非你有充分的理由不这样做。GET
请求不应该对数据产生任何影响。相反,只有当对页面的请求对数据产生副作用时,才使用 POST
。有关更多信息,请参阅 HTTP 规范中的 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 的 chapters 字段使用 get_or_create()
,但它只在该书的上下文中获取:
>>> book = Book.objects.create(title="Ulysses")
>>> book.chapters.get_or_create(title="Telemachus")
(<Chapter: Telemachus>, True)
>>> book.chapters.get_or_create(title="Telemachus")
(<Chapter: Telemachus>, False)
>>> Chapter.objects.create(title="Chapter 1")
<Chapter: Chapter 1>
>>> book.chapters.get_or_create(title="Chapter 1")
# Raises IntegrityError
出现这种情况是因为它试图通过“Ulysses”这本书获取或创建“Chapter 1”,但它不能做任何事情:关系不能获取该章,因为它与该书无关,但它也不能创建它,因为 title
字段应该是唯一的。
update_or_create()
update_or_create
(defaults=None, create_defaults=None, **kwargs)
aupdate_or_create
(defaults=None, create_defaults=None, **kwargs)
异步版本: aupdate_or_create()
一个方便的方法,用于使用给定的 kwargs
更新对象,如果需要的话创建一个新对象。create_defaults
和 defaults
都是 (字段, 值) 对的字典。create_defaults
和 defaults
中的值可以是可调用的。defaults
用于更新对象,而 create_defaults
用于创建操作。如果未提供 create_defaults
,则将使用 defaults
进行创建操作。
返回 (object, created)
的元组,其中 object
是创建或更新的对象,created
是一个布尔值,指定是否创建了一个新对象。
update_or_create
方法根据给定的 kwargs
尝试从数据库中获取一个对象。如果找到了匹配的对象,它就会更新 defaults
字典中传递的字段。
这是作为一个快捷方式来处理样板代码。例如:
defaults = {"first_name": "Bob"}
create_defaults = {"first_name": "Bob", "birthday": date(1940, 10, 9)}
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(create_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"},
create_defaults={"first_name": "Bob", "birthday": date(1940, 10, 9)},
)
关于如何解决在 kwargs
中传递名字的详细描述,见 get_or_create()。
如上文 get_or_create() 中所述,这种方法容易出现竞争条件,如果不在数据库层面强制执行唯一性,就会导致同时插入多条记录。
就像 get_or_create() 和 create() 一样,如果你使用的是手动指定的主键,需要创建一个对象,但该键已经存在于数据库中,就会引发 IntegrityError。
Changed in Django 5.0:
已添加了 create_defaults
参数。
bulk_create()
bulk_create
(objs, batch_size=None, ignore_conflicts=False, update_conflicts=False, update_fields=None, unique_fields=None)
abulk_create
(objs, batch_size=None, ignore_conflicts=False, update_conflicts=False, update_fields=None, unique_fields=None)
异步版本: abulk_create()
这个方法以高效的方式将提供的对象列表插入数据库中(通常只有 1 个查询,无论有多少对象),并以与提供的顺序相同的顺序返回已创建的对象列表:
>>> objs = Entry.objects.bulk_create(
... [
... Entry(headline="This is a test"),
... Entry(headline="This is only a test"),
... ]
... )
不过这有一些注意事项:
模型的
save()
方法将不会被调用,pre_save
和post_save
信号将不会被发送。在多表继承的情况下,它不能与子模型一起工作。
If the model’s primary key is an AutoField and
ignore_conflicts
is False, the primary key attribute can only be retrieved on certain databases (currently PostgreSQL, MariaDB, and SQLite 3.35+). On other databases, it will not be set.对于多对多的关系,它是行不通的。
它将
objs
转换为一个列表,如果objs
是一个生成器,则完全执行objs
。这种转换允许检查所有对象,因此任何具有手动设置主键的对象都可以首先插入。如果你想分批插入对象,而不一次性执行整个生成器,你可以使用这种技术,只要对象没有任何手动设置的主键:from itertools import islice
batch_size = 100
objs = (Entry(headline="Test %s" % i) for i in range(1000))
while True:
batch = list(islice(objs, batch_size))
if not batch:
break
Entry.objects.bulk_create(batch, batch_size)
batch_size
参数控制在一次查询中创建多少对象。默认情况是在一个批次中创建所有对象,但 SQLite 除外,默认情况是每个查询最多使用 999 个变量。
在支持的数据库上(除了 Oracle),将 ignore_conflicts
参数设置为 True
告诉数据库忽略插入失败的行,例如违反唯一值约束的行。
在支持它的数据库上(除了 Oracle),将 update_conflicts
参数设置为 True
,告诉数据库在冲突时更新 update_fields
。在 PostgreSQL 和 SQLite 上,除了 update_fields
,还必须提供可能冲突的 unique_fields
列表。
启用 ignore_conflicts
参数会禁用在每个模型实例上设置主键(如果数据库通常支持它)。
Changed in Django 5.0:
在旧版本中,启用 update_conflicts
参数会阻止设置每个模型实例的主键。
警告
在 MySQL 和 MariaDB 上,将 ignore_conflicts
参数设置为 True
将某些类型的错误,除了重复键之外,变成警告。即使在严格模式下也是如此。例如:无效值或不可空值违规。更多细节请参见 MySQL documentation 和 MariaDB documentation 。
bulk_update()
bulk_update
(objs, fields, batch_size=None)
abulk_update
(objs, fields, batch_size=None)
异步版本: abulk_update()
这个方法高效地更新提供的模型实例上的给定字段,通常只需一个查询,并返回更新的对象数量:
>>> objs = [
... Entry.objects.create(headline="Entry 1"),
... Entry.objects.create(headline="Entry 2"),
... ]
>>> objs[0].headline = "This is entry 1"
>>> objs[1].headline = "This is entry 2"
>>> Entry.objects.bulk_update(objs, ["headline"])
2
QuerySet.update() 用于保存更改,所以这比遍历模型列表并对每个模型调用 save()
更有效,但它有一些注意事项:
- 你不能更新模型的主键。
- 每个模型的
save()
方法没有被调用,而且 pre_save 和 post_save 信号没有被发送。 - 如果更新大量行中的大量列,生成的 SQL 可能非常大。通过指定一个合适的
batch_size
来避免这种情况。 - 更新定义在多表继承祖先上的字段将给每个祖先带来额外的查询。
- 当一个单独的批次包含重复的内容时,只有该批次的第一个实例会导致更新。
- 该函数返回的更新对象的数量可能少于传入的对象的数量。这可能是由于传入的对象重复,在同一批次中被更新,或者是竞赛条件,如对象在数据库中不再存在。
batch_size
参数控制一次查询中保存多少对象。默认值是在一个批次中更新所有对象,但 SQLite 和 Oracle 除外,它们对查询中使用的变量数量有限制。
count()
count
()
acount
()
异步版本: acount()
返回一个整数,表示数据库中与 QuerySet
匹配的对象数量。
举例:
# Returns the total number of entries in the database.
Entry.objects.count()
# Returns the number of entries whose headline contains 'Lennon'
Entry.objects.filter(headline__contains="Lennon").count()
count()
调用在幕后执行 SELECT COUNT(*)
,所以你应该总是使用 count()
而不是将所有的记录加载到 Python 对象中,然后在结果上调用 len()
(除非你需要将对象加载到内存中,在这种情况下 len()
会更快)。
请注意,如果你想知道 QuerySet
中的项数,并且也要从它中检索模型实例(例如,通过迭代它),那么使用 len(queryset)
可能更有效,因为它不会像 count()
那样引起额外的数据库查询。
如果查询集已经被完全检索到,count()
将使用该长度,而不是执行额外的数据库查询。
in_bulk()
in_bulk
(id_list=None, *, field_name=’pk’)
ain_bulk
(id_list=None, *, field_name=’pk’)
异步版本: ain_bulk()
接收一个字段值的列表(id_list
)和这些值的 field_name
,并返回一个字典,将每个值映射到具有给定字段值的对象实例。in_bulk
不会引发任何 django.core.exceptions.ObjectDoesNotExist 异常;也就是说,任何不匹配任何实例的 id_list
值将被简单地忽略掉。如果没有提供 id_list
,将返回查询集的所有对象。field_name
必须是一个唯一字段或一个独立的字段(如果只有一个字段在 distinct() 中指定)。field_name
默认为主键。
例如:
>>> Blog.objects.in_bulk([1])
{1: <Blog: Beatles Blog>}
>>> Blog.objects.in_bulk([1, 2])
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
>>> Blog.objects.in_bulk([])
{}
>>> Blog.objects.in_bulk()
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>}
>>> Blog.objects.in_bulk(["beatles_blog"], field_name="slug")
{'beatles_blog': <Blog: Beatles Blog>}
>>> Blog.objects.distinct("name").in_bulk(field_name="name")
{'Beatles Blog': <Blog: Beatles Blog>, 'Cheddar Talk': <Blog: Cheddar Talk>, 'Django Weblog': <Blog: Django Weblog>}
如果你传递 in_bulk()
一个空列表,你将得到一个空字典。
iterator()
iterator
(chunk_size=None)
aiterator
(chunk_size=None)
异步版本: aiterator()
评估 QuerySet
(通过执行查询)并返回结果的迭代器,如果调用其异步版本 aiterator
,则返回异步迭代器。
通常,QuerySet
会在内部缓存其结果,以便重复评估不会导致额外的查询。相比之下,iterator()
将直接读取结果,而不会在 QuerySet
级别进行任何缓存(在内部,默认迭代器调用 iterator()
并缓存返回值)。对于返回大量对象且您只需访问一次的 QuerySet
,这可以提高性能并显著减少内存使用。
请注意,在已经被执行的 QuerySet
上使用 iterator()
会迫使它再次执行,重复查询。
iterator()
与之前对 prefetch_related()
的调用兼容,只要提供了 chunk_size
。较大的值将需要更少的查询来完成预取操作,但代价是更多的内存使用。
Changed in Django 5.0:
已添加对先前调用 prefetch_related()
的支持,以支持 aiterator()
。
在某些数据库(例如 Oracle,SQLite)中,SQL 中的 IN
子句中的最大项数可能会受到限制。因此,应使用低于此限制的值。特别是在跨两个或多个关系进行预取的情况下,chunk_size
应该足够小,以便每个预取关系的预期结果数量仍然保持在限制以下。
只要 QuerySet 不预取任何相关对象,不为 chunk_size
提供值,Django 将使用默认的隐式值 2000。
根据数据库后端,查询结果将被一次性加载或使用服务器端的游标从数据库中流转。
使用服务器端游标
Oracle 和 PostgreSQL 使用服务器端的游标从数据库流式传输结果,而不需要将整个结果集加载到内存中。
Oracle 数据库驱动程序总是使用服务器端的游标。
对于服务器端的游标,chunk_size
参数指定了要在数据库驱动层缓存的结果数量。获取更大的块数会减少数据库驱动和数据库之间的往返次数,但会牺牲内存。
在 PostgreSQL 上,只有当 DISABLE_SERVER_SIDE_CURSORS 设置为 False
时,才会使用服务器端游标。如果你使用的是配置在事务池模式下的连接池器,请阅读 事务池和服务器端游标。当禁用服务器端游标时,其行为与不支持服务器端游标的数据库相同。
没有服务器端游标
MySQL 不支持流式结果,因此 Python 数据库驱动将整个结果集加载到内存中。然后数据库适配器使用 PEP 249 中定义的 fetchmany()
方法将结果集转化为 Python 行对象。
SQLite 可以使用 fetchmany()
分批获取结果,但由于 SQLite 不提供连接内查询之间的隔离,所以在向被迭代的表写入时要小心。参见 使用 QuerySet.iterator() 时的隔离 了解更多信息。
chunk_size
参数控制 Django 从数据库驱动中获取的批次大小。批量越大,就会减少与数据库驱动通信的开销,但代价是略微增加内存消耗。
只要查询集不预取任何相关对象,不提供 chunk_size
的值将导致 Django 使用隐式默认值 2000,这个值是从 psycopg 邮件列表上的计算 推导出的。
假设行数为 10-20 列,文字数据和数字数据混合,2000 要取不到 100KB 的数据,这似乎是一个很好的折中方案,在传输的行数和提前退出循环时丢弃的数据之间。
latest()
latest
(*fields)
alatest
(*fields)
异步版本: alatest()
根据给定的字段,返回表中最新的对象。
这个例子根据 pub_date
字段返回表中最新的 Entry
:
Entry.objects.latest("pub_date")
你也可以根据几个字段选择最新的。例如,当两个条目具有相同的 pub_date
时,要选择最早的 expire_date
条目:
Entry.objects.latest("pub_date", "-expire_date")
'-expire_date'
中的负号表示按 降 序排列 expire_date
。由于 latest()
得到的是最后一个结果,所以选择了最早的 expire_date
的 Entry
。
如果你模型的 Meta 指定了 get_latest_by,你可以省略 earliest()
或 latest()
的任何参数。get_latest_by 中指定的字段将被默认使用。
像 get()、earliest()
和 latest()
,如果没有给定参数的对象,就会引发 DoesNotExist。
请注意,earliest()
和 latest()
的存在纯粹是为了方便和可读性。
earliest()
和 latest()
可能返回日期为空的实例。
由于排序是委托给数据库的,如果使用不同的数据库,允许空值的字段上的结果可能会有不同的排序。例如,PostgreSQL 和 MySQL 将空值排序为高于非空值,而 SQLite 则相反。
你可能想过滤掉空值:
Entry.objects.filter(pub_date__isnull=False).latest("pub_date")
earliest()
earliest
(*fields)
aearliest
(*fields)
异步版本: aearliest()
除了方向改变外,其他工作方式都像 last()。
first()
first
()
afirst
()
异步版本: afirst()
返回查询集匹配的第一个对象,如果没有匹配的对象,则返回 None
。如果 QuerySet
没有定义排序,那么查询集自动按主键排序。这可能会影响聚合结果,如 Interaction with order_by() 中所述。
举例:
p = Article.objects.order_by("title", "pub_date").first()
请注意,first()
是一个方便的方法,下面的代码示例相当于上面的例子:
try:
p = Article.objects.order_by("title", "pub_date")[0]
except IndexError:
p = None
last()
last
()
alast
()
异步版本: alast()
与 first() 工作原理相同,但返回的是查询集中的最后一个对象。
aggregate()
aggregate
(*args, **kwargs)
aaggregate
(*args, **kwargs)
异步版本: aaggregate()
返回对 QuerySet
计算的聚合值(平均值、总和等)的字典。aggregate()
的每个参数都指定了一个将被包含在返回的字典中的值。
Django 提供的聚合函数在下面的 Aggregation Functions 中介绍。由于聚合函数也是 查询表达式,所以你可以将聚合函数与其他聚合函数或值结合起来,创建复杂的聚合函数。
使用关键字参数指定的聚合将使用关键字作为注解的名称。匿名参数将根据聚合函数的名称和被聚合的模型字段为其生成一个名称。复杂的聚合不能使用匿名参数,必须指定一个关键字参数作为别名。
例如,当你处理博客条目时,你可能想要知道有多少位作者贡献了博客条目:
>>> from django.db.models import Count
>>> Blog.objects.aggregate(Count("entry"))
{'entry__count': 16}
通过使用关键字参数来指定聚合函数,你可以控制返回的聚合值的名称:
>>> Blog.objects.aggregate(number_of_entries=Count("entry"))
{'number_of_entries': 16}
关于聚合的深入讨论,见 关于聚合的专题指南。
exists()
exists
()
aexists
()
异步版本: aexists()
如果 QuerySet 包含任何结果,则返回 True
,如果不包含,则返回 False
。该函数试图以最简单、最快速的方式执行查询,但它 执行 的查询与普通的 QuerySet 查询几乎相同。
existence()
对于与 QuerySet 中任何对象的存在有关的搜索很有用,特别是在一个大的 QuerySet 的背景下。
要查找一个查询集是否包含任何项目:
if some_queryset.exists():
print("There is at least one object in some_queryset")
这种方法比下面的方法更快:
if some_queryset:
print("There is at least one object in some_queryset")
…但程度不高(因此需要大量的查询集才能获得效率提升)。
此外,如果一个 some_queryset
还没有被执行,但你知道它会在某个时候被执行,那么使用 some_queryset.exences()
比使用 bool(some_queryset)
会做更多的总体工作(一个存在性检查的查询加上一个额外的查询,以便以后检索结果),后者检索结果,然后检查是否有任何返回。
contains()
contains
(obj)
acontains
(obj)
异步版本: acontains()
如果 QuerySet 包含 obj
,返回 True
,如果不包含 False
。这试图以最简单和最快的方式执行查询。
contains() 对于检查一个对象在 QuerySet 中的成员资格很有用,特别是在一个大的 QuerySet 的情况下。
要检查一个查询集是否包含一个特定的项目:
if some_queryset.contains(obj):
print("Entry contained in queryset")
这将比以下需要执行和迭代整个查询集的方法更快:
if obj in some_queryset:
print("Entry contained in queryset")
像 exists() 一样,如果 some_queryset
还没有被执行,但你知道它将在某个时候被执行,那么使用 some_queryset.contains(obj)
将进行额外的数据库查询,通常会导致整体性能变慢。
update()
update
(**kwargs)
aupdate
(**kwargs)
异步版本: aupdate()
对指定的字段执行 SQL 更新查询,并返回匹配的行数(如果有些行已经有了新的值,则可能不等于更新的行数)。
例如,要关闭所有在 2010 年发布的博客条目的评论,你可以这样做:
>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
(假定你的 Entry
模型有 pub_date
和 comments_on
。)
你可以更新多个字段,没有限制。例如,这里我们更新了 comments_on
和 headline
字段:
>>> Entry.objects.filter(pub_date__year=2010).update(
... comments_on=False, headline="This is old"
... )
update()
方法会立即应用,并且被更新的 QuerySet 的唯一限制是它只能更新模型主表中的列,不能更新相关模型上的列。例如,你不能这样做:
>>> Entry.objects.update(blog__name="foo") # Won't work!
基于相关字段的过滤仍然是可能的:
>>> Entry.objects.filter(blog__id=1).update(comments_on=True)
你不能在一个 QuerySet 上调用 update()
,因为它已经被取走了一个片断或者不能再被过滤。
update()
方法返回受影响的行数:
>>> Entry.objects.filter(id=64).update(comments_on=True)
1
>>> Entry.objects.filter(slug="nonexistent-slug").update(comments_on=True)
0
>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
132
如果你只是更新一条记录,不需要对模型对象做任何事情,最有效的方法是调用 update()
,而不是将模型对象加载到内存中。例如,不要这样做:
e = Entry.objects.get(id=10)
e.comments_on = False
e.save()
…要这样做:
Entry.objects.filter(id=10).update(comments_on=False)
使用 update()
还可以防止在加载对象和调用 save()
之间的短暂时间内数据库中的某些东西可能发生变化的竞争条件。
最后,要知道,update()
是在 SQL 级别上进行更新,因此,它不会在模型上调用任何 save()
方法,也不会发出 pre_save
或 post_save 信号(这是调用 Model.save() 的结果)。如果你想为一个有自定义 save() 方法的模型更新一堆记录,在它们上面循环并调用 save(),像这样:
for e in Entry.objects.filter(pub_date__year=2010):
e.comments_on = False
e.save()
有序的查询集
将 order_by()
与 update()
串联起来,只在 MariaDB 和 MySQL 上支持,对于不同的数据库会被忽略。这对于按照指定的顺序更新一个唯一的字段是很有用的,没有冲突。例如:
Entry.objects.order_by("-number").update(number=F("number") + 1)
备注
order_by()
子句如果包含注释、继承字段或跨越关系的查找,将被忽略。
delete()
delete
()
adelete
()
异步版本: adelete()
对 QuerySet 中的所有行执行 SQL 删除查询,并返回删除的对象数量和每个对象类型的删除数量的字典。
delete()
是即时应用的。你不能对已经被取走一个片断或不能再被过滤的 QuerySet 调用 delete()
。
例如,要删除特定博客中的所有条目:
>>> b = Blog.objects.get(pk=1)
# Delete all the entries belonging to this Blog.
>>> Entry.objects.filter(blog=b).delete()
(4, {'blog.Entry': 2, 'blog.Entry_authors': 2})
默认情况下,Django 的 ForeignKey 模拟了 SQL 约束 ON DELETE CASCADE
— 也就是说,任何具有外键指向将要被删除的对象的对象将会随之一起被删除。例如:
>>> blogs = Blog.objects.all()
# This will delete all Blogs and all of their Entry objects.
>>> blogs.delete()
(5, {'blog.Blog': 1, 'blog.Entry': 2, 'blog.Entry_authors': 2})
这种级联行为通过 ForeignKey 的 on_delete 参数定义。
delete()
方法进行批量删除,并不调用模型上的任何 delete()
方法。但是,它确实为所有被删除的对象(包括级联删除)发出 pre_delete 和 post_delete 信号。
Django 需要将对象获取到内存中来发送信号和处理级联。但是,如果没有级联和信号,那么 Django 可能会采取快速路径删除对象,而不需要将其获取到内存中。对于大面积的删除,这可以使内存使用量大大降低。也可以减少执行查询的数量。
设置为 on_delete DO_NOTHING
的外键不会阻止在删除时采取快速路径。
需要注意的是,对象删除中产生的查询是一个实现细节,可能会发生变化。
as_manager()
classmethod as_manager
()
类方法,该方法返回一个 Manager 的实例,其中包含 QuerySet
的方法的副本。详见 创建带有 QuerySet 方法的管理器 。
请注意,与本节中的其他条目不同,这个条目没有异步变体,因为它不执行查询。
explain()
explain
(format=None, **options)
aexplain
(format=None, **options)
异步版本: aexplain()
返回一个 QuerySet
的执行计划的字符串,它详细说明了数据库将如何执行查询,包括将使用的任何索引或联接。了解这些细节可以帮助你提高慢速查询的性能。
例如,在使用 PostgreSQL 时:
>>> print(Blog.objects.filter(title="My Blog").explain())
Seq Scan on blog (cost=0.00..35.50 rows=10 width=12)
Filter: (title = 'My Blog'::bpchar)
不同数据库之间的输出有很大的不同。
explain()
得到了所有内置数据库后端的支持,但 Oracle 除外,因为在那里的实现并不直接。
format
参数改变数据库默认的输出格式,通常是基于文本的。PostgreSQL 支持 ''TEXT'
、'JSON'
、'YAML'
和 'XML'
格式。MariaDB 和 MySQL 支持 ''TEXT'
(也叫 'TRADITIONAL'
)和 'JSON'
格式。MySQL 8.0.16+ 还支持改进的 'TREE'
格式,它类似于 PostgreSQL 的 'TEXT'
输出,如果支持的话,默认使用。
一些数据库接受可以返回有关查询的更多信息的标志。将这些标志作为关键字参数传递。例如,在使用 PostgreSQL 时:
>>> print(Blog.objects.filter(title="My Blog").explain(verbose=True, analyze=True))
Seq Scan on public.blog (cost=0.00..35.50 rows=10 width=12) (actual time=0.004..0.004 rows=10 loops=1)
Output: id, title
Filter: (blog.title = 'My Blog'::bpchar)
Planning time: 0.064 ms
Execution time: 0.058 ms
有些数据库接受的标志可以返回更多 在一些数据库上,标志可能会导致查询被执行,这可能会对你的数据库产生不利影响。 例如,MariaDB、MySQL 8.0.18+ 和 PostgreSQL 支持的 ANALYZE
标志,如果有触发器或调用函数,即使是 SELECT
查询,也可能导致数据的改变。
Changed in Django 5.1:
Support for the generic_plan
option on PostgreSQL 16+ was added.
Field
查找
字段查找是指定 SQL WHERE
子句的方法。它们被指定为 QuerySet
方法 filter()、exclude() 和 get() 的关键字参数。
介绍见 模型和数据库查询文档。
Django 的内置查找功能如下。也可以为模型字段写 自定义查找。
为方便起见,当没有提供查找类型时(如 Entry.objects.get(id=14)
),查找类型被假定为 exact。
exact
完全匹配。如果提供的比较值是 None
,它将被解释为 SQL NULL
(详见 isnull)。
举例:
Entry.objects.get(id__exact=14)
Entry.objects.get(id__exact=None)
SQL 等价于:
SELECT ... WHERE id = 14;
SELECT ... WHERE id IS NULL;
MySQL 比较
在 MySQL 中,数据库表的“字符序”设置决定了 act
比较是否区分大小写。这是一个数据库设置,而 不是 Django 设置。可以配置你的 MySQL 表来使用区分大小写的比较,但是会有一些折衷。关于这方面的更多信息,请参阅 数据库 文档中的 字符序部分。
iexact
不区分大小写的完全匹配。如果提供的比较值是 None
,它将被解释为 SQL NULL
(详见 isnull)。
举例:
Blog.objects.get(name__iexact="beatles blog")
Blog.objects.get(name__iexact=None)
SQL 等价于:
SELECT ... WHERE name ILIKE 'beatles blog';
SELECT ... WHERE name IS NULL;
注意第一个查询会匹配 'Beatles Blog'
、'beatles blog'
、'BeAtLes BLoG'
等。
SQLite 用户
当使用 SQLite 后台和非 ASCII 字符串时,请记住 database note 中关于字符串比较的内容。SQLite 对非 ASCII 字符串不进行区分大小写的匹配。
contains
区分大小写的包含测试。
举例:
Entry.objects.get(headline__contains="Lennon")
SQL 等价于:
SELECT ... WHERE headline LIKE '%Lennon%';
请注意,这将与标题 'Lennon honored today'
相匹配,而不是 'lennon honored today'
。
SQLite 用户
SQLite 不支持区分大小写的 LIKE
语句;contains
的作用就像 SQLite 的 icontains
。更多信息请参见 database note。
icontains
不区分大小写的包含测试。
举例:
Entry.objects.get(headline__icontains="Lennon")
SQL 等价于:
SELECT ... WHERE headline ILIKE '%Lennon%';
SQLite 用户
当使用 SQLite 后端和非 ASCII 字符串时,请记住 database note 中关于字符串比较的内容。
in
在一个给定的可迭代对象中;通常是一个列表、元组或查询集。这不是一个常见的用例,但字符串(可迭代)是可以接受的。
举例:
Entry.objects.filter(id__in=[1, 3, 4])
Entry.objects.filter(headline__in="abc")
SQL 等价于:
SELECT ... WHERE id IN (1, 3, 4);
SELECT ... WHERE headline IN ('a', 'b', 'c');
你也可以使用一个查询集来动态计算值列表,而不是提供一个字面值列表:
inner_qs = Blog.objects.filter(name__contains="Cheddar")
entries = Entry.objects.filter(blog__in=inner_qs)
该查询集将作为子选择语句执行:
SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
如果你把一个由 values()
或 values_list()
产生的 QuerySet
作为值传递给 __in
查找,你需要确保你只提取结果中的一个字段。例如,这样做就可以了(过滤博客名):
inner_qs = Blog.objects.filter(name__contains="Ch").values("name")
entries = Entry.objects.filter(blog__name__in=inner_qs)
这个例子会引发一个异常,因为内部查询正试图提取两个字段值,而预期只有一个:
# Bad code! Will raise a TypeError.
inner_qs = Blog.objects.filter(name__contains="Ch").values("name", "id")
entries = Entry.objects.filter(blog__name__in=inner_qs)
性能考量
谨慎使用嵌套查询,了解你的数据库服务器的性能特点(如果有疑问,请做基准测试!)。一些数据库后端,最主要的是 MySQL,并不能很好地优化嵌套查询。在这些情况下,提取一个值的列表,然后将其传递到第二个查询中会更有效率。也就是说,执行两个查询而不是一个查询:
values = Blog.objects.filter(name__contains="Cheddar").values_list("pk", flat=True)
entries = Entry.objects.filter(blog__in=list(values))
注意 Blog QuerySet
周围的 list()
调用,以强制执行第一个查询。如果没有它,一个嵌套查询就会被执行,因为 QuerySet 是惰性的。
gt
大于。
举例:
Entry.objects.filter(id__gt=4)
SQL 等价于:
SELECT ... WHERE id > 4;
gte
大于等于。
lt
小于。
lte
小于等于
startswith
区分大小写的开头为。
举例:
Entry.objects.filter(headline__startswith="Lennon")
SQL 等价于:
SELECT ... WHERE headline LIKE 'Lennon%';
SQLite 不支持区分大小写的 LIKE
语句;startswith
的作用就像 SQLite 的 istartswith
。
istartswith
不区分大小写的开头为。
举例:
Entry.objects.filter(headline__istartswith="Lennon")
SQL 等价于:
SELECT ... WHERE headline ILIKE 'Lennon%';
SQLite 用户
当使用 SQLite 后端和非 ASCII 字符串时,请记住 database note 中关于字符串比较的内容。
endswith
区分大小写的结尾为。
举例:
Entry.objects.filter(headline__endswith="Lennon")
SQL 等价于:
SELECT ... WHERE headline LIKE '%Lennon';
SQLite 用户
SQLite 不支持区分大小写的 LIKE
语句;endswith
的作用类似于 SQLite 的 iendswith
。更多内容请参考 database note 文档。
iendswith
不区分大小写的结尾为。
举例:
Entry.objects.filter(headline__iendswith="Lennon")
SQL 等价于:
SELECT ... WHERE headline ILIKE '%Lennon'
SQLite 用户
当使用 SQLite 后端和非 ASCII 字符串时,请记住 database note 中关于字符串比较的内容。
range
范围测试(含)。
举例:
import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))
SQL 等价于:
SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';
在 SQL 中,你可以在任何你可以使用 BETWEEN
的地方使用 range
——用于日期、数字甚至字符。
警告
用日期过滤 DateTimeField
不会包括最后一天的项目,因为界限被解释为“给定日期的 0 点”。如果 pub_date
是一个 DateTimeField
,上面的表达式就会变成这个 SQL:
SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00';
一般来说,你不能把日期和日期时间混在一起。
date
对于日期时间字段,将值投射为日期。允许链接其他字段的查找。取一个日期值。
举例:
Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))
(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。
当 USE_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
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
iso_year
对于日期和日期时间字段,精确的 ISO 8601 周号年份匹配。允许链接其他字段的查询。取整数年。
举例:
Entry.objects.filter(pub_date__iso_year=2005)
Entry.objects.filter(pub_date__iso_year__gte=2005)
(确切的 SQL 语法因每个数据库引擎而异)。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
month
对于日期和日期时间字段,精确的月份匹配。允许链接其他字段的查询。取整数 1(1 月)到 12(12 月)。
举例:
Entry.objects.filter(pub_date__month=12)
Entry.objects.filter(pub_date__month__gte=6)
SQL 等价于:
SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12';
SELECT ... WHERE EXTRACT('month' FROM pub_date) >= '6';
(确切的 SQL 语法因每个数据库引擎而异)。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
day
对于日期和日期时间字段,精确匹配日期。允许链接其他字段的查询。取整数日。
举例:
Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3)
SQL 等价于:
SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3';
SELECT ... WHERE EXTRACT('day' FROM pub_date) >= '3';
(确切的 SQL 语法因每个数据库引擎而异)。
请注意,这将匹配任何带有 pub_date 的月份第三天的记录,如 1 月 3 日,7 月 3 日等。
当 USE_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)
(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
week_day
对于日期和日期时间字段,“星期几”匹配。允许链接其他字段的查询。
从 1(星期日)到 7(星期六)取一个整数值,代表一周的一天。
举例:
Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)
(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。
请注意,这将匹配任何带有 pub_date
的记录,这些记录都是在星期一(一周的第 2 天)发生的,不管它发生在哪一年哪一月。周日的索引,第 1 天是周日,第 7 天是周六。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
iso_week_day
对于日期和日期时间字段,精确匹配 ISO 8601 星期几。允许链接其他字段的查询。
取一个整数值,代表一周的 1(星期一)到 7(星期日)。
举例:
Entry.objects.filter(pub_date__iso_week_day=1)
Entry.objects.filter(pub_date__iso_week_day__gte=1)
(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。
请注意,这将匹配任何带有 pub_date
的记录,这些记录都是在星期一(一周的第 1 天)发生的,不管它发生在哪个月或哪个年。周日的索引是第 1 天是星期一,第 7 天是星期天。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
quarter
对于日期和日期时间字段,“一年的四分之一”匹配。允许链接额外的字段查找。取 1 到 4 之间的整数值,代表一年中的季度。
检索第二季度(4 月 1 日至 6 月 30 日)的条目示例:
Entry.objects.filter(pub_date__quarter=2)
(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
time
对于日期时间字段,将其值强制转换为时间。允许链式附加字段查找。取一个 datetime.time 的值。
举例:
Entry.objects.filter(pub_date__time=datetime.time(14, 30))
Entry.objects.filter(pub_date__time__range=(datetime.time(8), datetime.time(17)))
(由于不同的数据库引擎对相关查询的实现各不相同,因此本次查询不包含等效的 SQL 代码片段)。
当 USE_TZ 为 True
时,字段在过滤前会被转换为当前时区。这需要 数据库中的时区定义。
hour
对于日期时间和时间字段,精确的小时匹配。允许链式查找其他字段。取 0 到 23 之间的整数。
举例:
Event.objects.filter(timestamp__hour=23)
Event.objects.filter(time__hour=5)
Event.objects.filter(timestamp__hour__gte=12)
SQL 等价于:
SELECT ... WHERE EXTRACT('hour' FROM timestamp) = '23';
SELECT ... WHERE EXTRACT('hour' FROM time) = '5';
SELECT ... WHERE EXTRACT('hour' FROM timestamp) >= '12';
(确切的 SQL 语法因每个数据库引擎而异)。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
minute
对于日期时间和时间字段,精确的分钟匹配。允许链式查找其他字段。取 0 到 59 之间的整数。
举例:
Event.objects.filter(timestamp__minute=29)
Event.objects.filter(time__minute=46)
Event.objects.filter(timestamp__minute__gte=29)
SQL 等价于:
SELECT ... WHERE EXTRACT('minute' FROM timestamp) = '29';
SELECT ... WHERE EXTRACT('minute' FROM time) = '46';
SELECT ... WHERE EXTRACT('minute' FROM timestamp) >= '29';
(确切的 SQL 语法因每个数据库引擎而异)。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
second
对于日期时间和时间字段,完全秒配。允许链式查找其他字段。取 0 到 59 之间的整数。
举例:
Event.objects.filter(timestamp__second=31)
Event.objects.filter(time__second=2)
Event.objects.filter(timestamp__second__gte=31)
SQL 等价于:
SELECT ... WHERE EXTRACT('second' FROM timestamp) = '31';
SELECT ... WHERE EXTRACT('second' FROM time) = '2';
SELECT ... WHERE EXTRACT('second' FROM timestamp) >= '31';
(确切的 SQL 语法因每个数据库引擎而异)。
当 USE_TZ 为 True
时,日期时间字段会在过滤前转换为当前时区。这需要 数据库中的时区定义。
isnull
取 True
或 False
,分别对应 IS NULL
和 IS NOT NULL
的 SQL 查询。
举例:
Entry.objects.filter(pub_date__isnull=True)
SQL 等价于:
SELECT ... WHERE pub_date IS NULL;
regex
区分大小写的正则表达式匹配。
正则表达式语法是使用中的数据库后端的语法。对于没有内置正则表达式支持的 SQLite 来说,这个功能是由(Python)用户定义的 REGEXP 函数提供的,因此正则表达式语法是 Python 的 re
模块的语法。
举例:
Entry.objects.get(title__regex=r"^(An?|The) +")
SQL 等价于:
SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; -- MySQL
SELECT ... WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c'); -- Oracle
SELECT ... WHERE title ~ '^(An?|The) +'; -- PostgreSQL
SELECT ... WHERE title REGEXP '^(An?|The) +'; -- SQLite
建议使用原始字符串(例如,用 r'foo'
代替 'foo'
)来传递正则表达式语法。
iregex
不区分大小写的正则表达式匹配。
举例:
Entry.objects.get(title__iregex=r"^(an?|the) +")
SQL 等价于:
SELECT ... WHERE title REGEXP '^(an?|the) +'; -- MySQL
SELECT ... WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i'); -- Oracle
SELECT ... WHERE title ~* '^(an?|the) +'; -- PostgreSQL
SELECT ... WHERE title REGEXP '(?i)^(an?|the) +'; -- SQLite
聚合函数
Django 在 django.db.models
模块中提供了以下聚合函数。关于如何使用这些聚合函数的细节,请参见 关于聚合的主题指南。参见 Aggregate 文档,了解如何创建你的聚合函数。
警告
SQLite 无法处理日期/时间字段的聚合。这是因为 SQLite 中没有原生的日期/时间字段,而 Django 目前使用文本字段来模拟这些功能。试图在 SQLite 中使用日期/时间字段上的聚合将引发 NotSupportedError
。
空的查询集或分组
聚合函数在与空的 QuerySet
或分组一起使用时会返回 None
。例如,如果 QuerySet
不包含任何条目或在非空的 QuerySet
中有任何空分组时,Sum
聚合函数将返回 None
而不是 0
。要返回另一个值,可以定义 default
参数。 Count
是这种行为的一个例外;如果 QuerySet
为空,它将返回 0
,因为 Count
不支持 default
参数。
所有聚合体都有以下共同的参数:
expressions
引用模型上的字段的字符串,字段的转换,或 查询表达式。
output_field
一个可选的参数,表示返回值的 模型字段
备注
当组合多个字段类型时,Django 只能在所有字段类型相同的情况下确定 output_field
。否则,你必须自己提供 output_field
。
filter
一个可选的 Q 对象,用于过滤被聚合的行。
default
一个可选的参数,允许指定一个值作为默认值,当查询集(或分组)不包含条目时使用。
**extra
关键字参数,可以为聚合生成的 SQL 提供额外的上下文。
Avg
class Avg
(expression, output_field=None, distinct=False, filter=None, default=None, **extra)[源代码]
返回给定表达式的平均值,除非指定不同的 output_field
,否则必须是数值。
- 默认别名:
<field>__avg
返回类型:如果输入是
int
,则返回float
,否则返回与输入字段相同的类型,或者如果提供了output_field
,则返回它。如果查询集或分组为空,将返回default
。distinct
可选。如果
distinct=True
,则Avg
返回唯一值的平均值。这相当于 SQL 中的AVG(DISTINCT <field>)
。默认值是False
。
Count
class Count
(expression, distinct=False, filter=None, **extra)[源代码]
返回通过提供的表达式相关联的对象数量。 Count('*')
等同于 SQL 的 COUNT(*)
表达式。
- 默认别名:
<field>__count
返回类型:
int
distinct
可选。如果
distinct=True
,计数将仅包括唯一的实例。这相当于 SQL 中的COUNT(DISTINCT <field>)
。默认值是False
。
备注
不支持 default
参数。
Max
class Max
(expression, output_field=None, filter=None, default=None, **extra)[源代码]
返回给定表达式的最大值。
- 默认别名:
<field>__max
- 返回类型:与输入字段相同,或者如果提供了
output_field
,则返回它。如果查询集或分组为空,将返回default
。
Min
class Min
(expression, output_field=None, filter=None, default=None, **extra)[源代码]
返回给定表达式的最小值。
- 默认别名:
<field>__min
- 返回类型:与输入字段相同,或者如果提供了
output_field
,则返回它。如果查询集或分组为空,将返回default
。
StdDev
class StdDev
(expression, output_field=None, sample=False, filter=None, default=None, **extra)[源代码]
返回给定表达式中数据的标准差。
- 默认别名:
<field>__stddev
返回类型:如果输入是
int
,则返回float
,否则返回与输入字段相同的类型,或者如果提供了output_field
,则返回它。如果查询集或分组为空,将返回default
。sample
可选。默认情况下,
StdDev
返回总体标准差。然而,如果sample=True
,返回值将是样本标准差。
Sum
class Sum
(expression, output_field=None, distinct=False, filter=None, default=None, **extra)[源代码]
计算给定表达式的所有值的总和。
- 默认别名:
<field>__sum
返回类型:与输入字段相同,或者如果提供了
output_field
,则返回它。如果查询集或分组为空,将返回default
。distinct
可选。如果
distinct=True
,Sum
返回唯一值的总和。这相当于 SQL 中的SUM(DISTINCT <field>)
。默认值是False
。
Variance
class Variance
(expression, output_field=None, sample=False, filter=None, default=None, **extra)[源代码]
返回给定表达式中数据的方差。
- 默认别名:
<field>__variance
返回类型:如果输入是
int
,则返回float
,否则返回与输入字段相同的类型,或者如果提供了output_field
,则返回它。如果查询集或分组为空,将返回default
。sample
可选。默认情况下,
Variance
返回总体方差。然而,如果sample=True
,返回值将是样本方差。
查询相关工具
本节提供了与查询相关的工具的参考资料,其他地方没有记载。
Q()
对象
class Q
[源代码]
Q()
对象表示可用于数据库相关操作的 SQL 条件。 它类似于 F() <django.db.models.F>` 对象表示模型字段或注释的值的方式。 它们使得定义和重用条件成为可能。 这些可以使用“~”(“NOT”)运算符进行否定,并使用“|”(“OR”)、“&”(“AND”)等运算符进行组合 ) 和``^`` (XOR
)。 请参阅:ref:complex-lookups-with-q。
Prefetch()
对象
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"))
<QuerySet [<Question: What's up?>]>
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
[<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
实例中。
prefetch_related_objects()
prefetch_related_objects
(model_instances, *related_lookups)[源代码]
aprefetch_related_objects
(model_instances, *related_lookups)
异步版本:aprefetch_related_objects()
在作为模型实例的可迭代对象上预取给定的查找。这在接收模型实例列表而不是 QuerySet
的代码中很有用;例如,从缓存中获取模型或手动实例化它们时。
传递一个模型实例的可迭代对象(必须都是同一类),以及你想要预取的查找或 Prefetch 对象。例如:
>>> from django.db.models import prefetch_related_objects
>>> restaurants = fetch_top_restaurants_from_cache() # A list of Restaurants
>>> prefetch_related_objects(restaurants, "pizzas__toppings")
当使用 prefetch_related_objects
的多个数据库时,预取查询将使用与模型实例相关的数据库。这可以通过在相关查找中使用自定义查询集来覆盖。
Changed in Django 5.0:
aprefetch_related_objects()
函数已添加。
FilteredRelation()
对象
class FilteredRelation
(relation_name, *, condition=Q())[源代码]
relation_name
你想过滤关系的字段名。
condition
一个 Q 对象来控制过滤。
FilteredRelation
与 annotate() 一起使用,在执行 JOIN
时创建一个 ON
子句。它不作用于默认的关系,而是作用于注解名称(下面例子中的 pizzas_vegetarian
)。
例如,要查找具有在名称中包含 'mozzarella'
的素食比萨的餐馆:
>>> from django.db.models import FilteredRelation, Q
>>> Restaurant.objects.annotate(
... pizzas_vegetarian=FilteredRelation(
... "pizzas",
... condition=Q(pizzas__vegetarian=True),
... ),
... ).filter(pizzas_vegetarian__name__icontains="mozzarella")
如果有大量的比萨,这个查询集的性能要优于:
>>> Restaurant.objects.filter(
... pizzas__vegetarian=True,
... pizzas__name__icontains="mozzarella",
... )
因为第一个查询集的 WHERE
子句中的过滤只对素食披萨进行操作。
FilteredRelation
不支持:
- QuerySet.only() 和 prefetch_related()。
- 一个从父模型继承的 GenericForeignKey。