QuerySet API 参考
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() 调用将忽略其额外选择的字段。 -
在
values()之后调用 only() 和 defer() 是没有意义的,这样做会引发TypeError。 -
组合转换和聚合需要使用两个 annotate() 调用,可以显式地使用它们,也可以作为 values() 的关键字参数。与上面一样,如果转换已经在相关字段类型上注册,第一个 annotate() 可以省略,因此以下示例是等效的:
>>> from django.db.models import CharField, Count
>>> from django.db.models.functions import Lower
>>> CharField.register_lookup(Lower)
>>> Blog.objects.values("entry__authors__name__lower").annotate(entries=Count("entry"))
<QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
>>> Blog.objects.values(entry__authors__name__lower=Lower("entry__authors__name")).annotate(
... entries=Count("entry")
... )
<QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
>>> Blog.objects.annotate(entry__authors__name__lower=Lower("entry__authors__name")).values(
... "entry__authors__name__lower"
... ).annotate(entries=Count("entry"))
<QuerySet [{'entry__authors__name__lower': 'test author', 'entries': 33}]>
当你知道你只需要一小部分可用字段的值,而且你不需要模型实例对象的功能时,它就很有用。只选择你需要使用的字段会更高效。
最后要注意的是,你可以在调用 values() 之后调用 filter()、order_by() 等,也就是说这两个调用是相同的:
Blog.objects.values().order_by("id")
Blog.objects.order_by("id").values()
制作 Django 的人喜欢把所有影响 SQL 的方法放在前面,后面(可选的)是任何影响输出的方法(比如 values()),但这并不重要。这是你真正炫耀自己个性的机会。
你还可以通过 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 来定义明确的 SQL WHERE 子句——也许是为了执行非明确的连接。你可以通过使用 tables 手动添加表到 SQL FROM 子句中。
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()
(本文超出站点单文档体积上限,已按小节截断)
