查询表达式描述了一个值或一个计算,它可以作为更新、创建、过滤、排序、注解或聚合的一部分。当一个表达式输出一个布尔值时,它可以直接用于过滤器中。有许多内置的表达式(在下面的文档中)可以用来帮助你编写查询。表达式可以组合,或者在某些情况下嵌套,以形成更复杂的计算。
Django 支持负、加、减、乘、除、模数运算,以及对查询表达式的幂运算符,使用 Python 常量、变量,甚至其他表达式。
>>> from django.db.models import Count, F, Value
>>> from django.db.models.functions import Length, Upper
>>> from django.db.models.lookups import GreaterThan
# Find companies that have more employees than chairs.
>>> Company.objects.filter(num_employees__gt=F("num_chairs"))
# Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent.
>>> Company.objects.filter(num_employees__gt=F("num_chairs") * 2)
>>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs"))
# How many chairs are needed for each company to seat all employees?
>>> company = (
... Company.objects.filter(num_employees__gt=F("num_chairs"))
... .annotate(chairs_needed=F("num_employees") - F("num_chairs"))
... .first()
... )
>>> company.num_employees
120
>>> company.num_chairs
50
>>> company.chairs_needed
70
# Create a new company using expressions.
>>> company = Company.objects.create(name="Google", ticker=Upper(Value("goog")))
# Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db()
>>> company.ticker
'GOOG'
# Annotate models with an aggregated value. Both forms
# below are equivalent.
Company.objects.annotate(num_products=Count('products'))
Company.objects.annotate(num_products=Count(F('products')))
# Aggregates can contain complex computations also
Company.objects.annotate(num_offerings=Count(F('products') + F('services')))
# Expressions can also be used in order_by(), either directly
Company.objects.order_by(Length('name').asc())
Company.objects.order_by(Length('name').desc())
# or using the double underscore lookup syntax.
from django.db.models import CharField
from django.db.models.functions import Length
CharField.register_lookup(Length)
Company.objects.order_by('name__length')
# Boolean expression can be used directly in filters.
from django.db.models import Exists
Company.objects.filter(
Exists(Employee.objects.filter(company=OuterRef('pk'), salary__gt=10))
)
# Lookup expressions can also be used directly in filters
Company.objects.filter(GreaterThan(F('num_employees'), F('num_chairs')))
# or annotations.
Company.objects.annotate(
need_chairs=GreaterThan(F('num_employees'), F('num_chairs')),
)
备注
这些表达式在 django.db.models.expressions
和 django.db.models.aggregates
中定义,但为了方便起见,通常从 django.db.models
中导入使用。
F()
表达式¶F
¶An F()
object represents the value of a model field, transformed value of a
model field, or annotated column. It makes it possible to refer to model field
values and perform database operations using them without actually having to
pull them out of the database into Python memory.
取而代之的是,Django 使用 F()
对象来生成一个 SQL 表达式,在数据库层面描述所需的操作。
我们举个例子试试。通常情况下,我们可以这样做:
# Tintin filed a news story!
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed += 1
reporter.save()
这里,我们从数据库中提取了 reporter.stories_filed
的值到内存中,并使用熟悉的 Python 操作符对其进行操作,然后将对象保存回数据库。但我们也可以这样做:
from django.db.models import F
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
虽然 reporter.stories_filed = F('stories_filed') + 1
看起来像一个普通的 Python 赋值给一个实例属性,但实际上它是一个描述数据库操作的 SQL 结构。
当 Django 遇到 F()
的实例时,它会覆盖标准的 Python 运算符来创建一个封装的 SQL 表达式;在本例中,它指示数据库递增由 reporter.stories_filed
表示的数据库字段。
无论 reporter.stories_filed
上的值是多少,Python 永远不会知道它——它完全由数据库处理。通过 Django 的 F()
类,Python 所做的就是创建 SQL 语法来引用这个字段并描述操作。
要访问这样保存的新值,必须重新加载对象:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
F()
除了用于上述对单个实例的操作外,F()
还可以与 update()
一起用于对象实例的 QuerySets
。这就把我们上面使用的两个查询——get()
和 save()
减少到只有一个:
reporter = Reporters.objects.filter(name="Tintin")
reporter.update(stories_filed=F("stories_filed") + 1)
我们还可以使用 update()
来递增多个对象上的字段值——这可能比从数据库中把它们全部拉到 Python 中,在它们身上循环,递增每个对象的字段值,然后把每个对象保存回数据库要快得多:
Reporter.objects.update(stories_filed=F("stories_filed") + 1)
因此,F()
可以通过以下方式提供性能优势:
F()
避免竞争条件¶F()
的另一个有用的好处是,让数据库——而不是 Python——更新一个字段的值,避免了 竞争条件。
如果两个 Python 线程执行上面第一个例子中的代码,一个线程可以在另一个线程从数据库中获取一个字段的值后,检索、递增并保存它。第二个线程保存的值将基于原始值,第一个线程的工作将丢失。
如果数据库负责更新字段,那么这个过程就比较稳健:它只会在执行 save()
或 update()
时,根据数据库中字段的值来更新字段,而不是根据检索实例时的值来更新。
F()
赋值在 Model.save()
之后持续存在¶F()
分配给模型字段的对象在保存模型实例后会持续存在,并将应用于每个 save()
。例如:
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
reporter.name = "Tintin Jr."
reporter.save()
在这种情况下,stories_filed
将被更新两次。如果最初是 1
,最终值将是 3
。这种持久性可以通过在保存模型对象后重新加载来避免,例如,使用 refresh_from_db()
。
F()
¶F()
可用于通过将不同的字段与算术相结合,在你的模型上创建动态字段:
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
如果你要组合的字段是不同类型的,你需要告诉 Django 将返回什么样的字段。由于 F()
不直接支持 output_field
,你需要用 ExpressionWrapper
来包装表达式:
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F("active_at") + F("duration"), output_field=DateTimeField()
)
)
When referencing relational fields such as ForeignKey
, F()
returns the
primary key value rather than a model instance:
>> car = Company.objects.annotate(built_by=F('manufacturer'))[0]
>> car.manufacturer
<Manufacturer: Toyota>
>> car.built_by
3
F()
对空值进行排序¶使用 F()
和 Expression.asc()
或 esc()
的关键词参数 nulls_first
或 nulls_last
来控制字段的空值的排序。默认情况下,排序取决于你的数据库。
例如,在已经联系过的公司之后,对尚未联系过的公司进行排序(last_contacted
为空):
from django.db.models import F
Company.objects.order_by(F("last_contacted").desc(nulls_last=True))
F()
with logical operations¶F()
expressions that output BooleanField
can be logically negated with
the inversion operator ~F()
. For example, to swap the activation status of
companies:
from django.db.models import F
Company.objects.update(is_active=~F("is_active"))
Func()
表达式¶Func()
表达式是所有涉及 COALESCE
和 LOWER
等数据库函数或 SUM
等集合的表达式的基本类型。它们可以直接使用:
from django.db.models import F, Func
queryset.annotate(field_lower=Func(F("field"), function="LOWER"))
或者可以用它们来建立一个数据库函数库:
class Lower(Func):
function = "LOWER"
queryset.annotate(field_lower=Lower("field"))
但这两种情况都会产生一个查询集,其中每个模型都有一个额外的属性 field_lower
来注释,大致由以下 SQL 产生:
SELECT
...
LOWER("db_table"."field") as "field_lower"
参见 数据库函数 获取内置的数据库函数列表。
Func
API 如下:
Func
(*expressions, **extra)¶template
¶一个类属性,作为格式字符串,描述为该函数生成的SQL。默认值为 '%(function)s(%(expressions)s)'
。
如果你正在构造像 strftime('%W', 'date')
这样的 SQL,并且需要在查询中使用 %
字符,那么在 template
属性中把它四倍(%%%%
),因为这个字符串会被插值两次:一次是在 as_sql()
中的模板插值中,另一次是在数据库游标中的查询参数的 SQL 插值中。
arg_joiner
¶一个类属性,表示用于将 expressions
列表连接在一起的字符。默认值为 ', '
。
arity
¶一个类属性,表示函数接受的参数数。如果设置了这个属性,并且用不同数量的表达式调用函数,将引发 TypeError
。默认值为 None
。
as_sql
(compiler, connection, function=None, template=None, arg_joiner=None, **extra_context)¶生成数据库函数的 SQL 片段。返回一个元组 (sql, params)
,其中 sql
是 SQL 字符串,params
是查询参数的列表或元组。
as_vendor()
方法应该使用 function
、template
、arg_joiner
以及其他 **extra_context
参数来根据需要定制 SQL。例如:
class ConcatPair(Func):
...
function = "CONCAT"
...
def as_mysql(self, compiler, connection, **extra_context):
return super().as_sql(
compiler,
connection,
function="CONCAT_WS",
template="%(function)s('', %(expressions)s)",
**extra_context
)
为了避免 SQL 注入漏洞,extra_context
不得包含不受信任的用户输入,因为这些值会被内插到 SQL 字符串中,而不是作为查询参数传递,数据库驱动对其进行转义。
*expressions
参数是一个位置表达式的列表,该函数将应用于此。表达式将被转换为字符串,用 arg_joiner
连接在一起,然后插入到 template
中作为 expressions
占位符。
位置参数可以是表达式或 Python 值。字符串被认为是列引用,将被包装在 F()
表达式中,而其他值将被包装在 Value()
表达式中。
**extra
关键字是 key=value
对,可以内插到 template
属性中。为了避免 SQL 注入漏洞,extra
不能包含不受信任的用户输入,因为这些值会被内插到 SQL 字符串中,而不是作为查询参数传递,数据库驱动程序对其进行转义。
function
、template
和 arg_joiner
关键字可以用来替换同名的属性,而不必定义自己的类。output_field
可以用来定义预期的返回类型。
Aggregate()
表达式¶聚合表达式是 Func() 表达式 的一个特例,它通知查询需要一个 GROUP BY
子句。所有的 聚合函数,如 Sum()
和 Count()
,都继承自 Aggregate()
。
由于 Aggregate
是表达式和封装表达式,你可以表示一些复杂的计算:
from django.db.models import Count
Company.objects.annotate(
managers_required=(Count("num_employees") / 4) + Count("num_managers")
)
Aggregate
的 API 如下:
Aggregate
(*expressions, output_field=None, distinct=False, filter=None, default=None, **extra)¶template
¶作为格式字符串的类属性,描述为该集合生成的 SQL。默认值为 '%(function)s(%(distinct)s%(expressions)s)'
。
allow_distinct
¶一个类属性,决定该聚合函数是否允许传递 distinct
关键字参数。如果设置为 False
(默认),如果传递了 distinct=True
,会引发 TypeError
。
empty_result_set_value
¶Defaults to None
since most aggregate functions result in NULL
when applied to an empty result set.
The expressions
positional arguments can include expressions, transforms of
the model field, or the names of model fields. They will be converted to a
string and used as the expressions
placeholder within the template
.
output_field
参数需要一个模型字段实例,比如 IntegerField()
或 BooleanField()
,Django 会在从数据库中获取值后将其载入其中。通常在实例化模型字段时,不需要任何参数,因为任何与数据验证有关的参数(max_length
、max_digits
等)都不会在表达式的输出值上执行。
请注意,只有当 Django 无法确定结果的字段类型时,才需要使用 output_field
。混合字段类型的复杂表达式应该定义所需的 output_field
。例如,将一个 IntegerField()
和一个 FloatField()
加在一起,可能应该定义 output_field=FloatField()
。
distinct
参数决定是否应该为 expressions
的每一个不同的值(或一组值,对于多个 expressions
)调用聚合函数。该参数仅在 allow_distinct
设置为 True
的聚合函数中得到支持。
filter
参数取一个 Q 对象
,用于过滤被聚合的行。参见 条件聚合 和 过滤注解 的用法示例。
The default
argument takes a value that will be passed along with the
aggregate to Coalesce
. This is useful for
specifying a value to be returned other than None
when the queryset (or
grouping) contains no entries.
**extra
关键字是 key=value
对,可以内插到 template
属性中。
你也可以创建自己的聚合函数。至少,你需要定义 function
,但你也可以完全自定义生成的 SQL。下面是一个简单的例子:
from django.db.models import Aggregate
class Sum(Aggregate):
# Supports SUM(ALL field).
function = "SUM"
template = "%(function)s(%(all_values)s%(expressions)s)"
allow_distinct = False
def __init__(self, expression, all_values=False, **extra):
super().__init__(expression, all_values="ALL " if all_values else "", **extra)
Value()
表达式¶Value
(value, output_field=None)¶Value()
对象表示一个表达式中最小的成分:一个简单的值。当你需要在一个表达式中表示一个整数、布尔值或字符串的值时,你可以将该值包裹在一个 Value()
中。
你很少会需要直接使用 Value()
。当你写下表达式 F('field') + 1
时,Django 会隐式地将 1
包裹在 Value()
中,允许简单的值被用于更复杂的表达式中。当你想把一个字符串传递给一个表达式时,你需要使用 Value()
。大多数表达式将字符串参数解释为字段的名称,如 Lower('name')
。
value
参数描述了要包含在表达式中的值,比如 1
、True
或 None
。Django 知道如何将这些 Python 值转换为相应的数据库类型。
The output_field
argument should be a model field instance, like
IntegerField()
or BooleanField()
, into which Django will load the value
after it's retrieved from the database. Usually no arguments are needed when
instantiating the model field as any arguments relating to data validation
(max_length
, max_digits
, etc.) will not be enforced on the expression's
output value. If no output_field
is specified it will be tentatively
inferred from the type
of the provided value
, if possible. For
example, passing an instance of datetime.datetime
as value
would default output_field
to DateTimeField
.
ExpressionWrapper()
表达式¶ExpressionWrapper
(expression, output_field)¶ExpressionWrapper
包裹另一个表达式,并提供对 output_field
等属性的访问,这些属性在其他表达式上可能无法使用。ExpressionWrapper
在对 F()
具有不同类型的表达式使用算术时是必要的,如 与注解一起使用 F() 中所述。
Subquery()
表达式¶Subquery
(queryset, output_field=None)¶你可以使用 Subquery
表达式向 QuerySet
添加一个显式子查询。
For example, to annotate each post with the email address of the author of the newest comment on that post:
>>> from django.db.models import OuterRef, Subquery
>>> newest = Comment.objects.filter(post=OuterRef("pk")).order_by("-created_at")
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values("email")[:1]))
在 PostgreSQL 上,SQL 看起来像:
SELECT "post"."id", (
SELECT U0."email"
FROM "comment" U0
WHERE U0."post_id" = ("post"."id")
ORDER BY U0."created_at" DESC LIMIT 1
) AS "newest_commenter_email" FROM "post"
备注
本节中的例子是为了展示如何强制 Django 执行一个子查询。在某些情况下,可以写一个等价的查询集,更清楚或更有效地执行同样的任务。
OuterRef
(field)¶Use OuterRef
when a queryset in a Subquery
needs to refer to a field
from the outer query or its transform. It acts like an F
expression
except that the check to see if it refers to a valid field isn't made until the
outer queryset is resolved.
Instances of OuterRef
may be used in conjunction with nested instances
of Subquery
to refer to a containing queryset that isn't the immediate
parent. For example, this queryset would need to be within a nested pair of
Subquery
instances to resolve correctly:
>>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
There are times when a single column must be returned from a Subquery
, for
instance, to use a Subquery
as the target of an __in
lookup. To return
all comments for posts published within the last day:
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> posts = Post.objects.filter(published_at__gte=one_day_ago)
>>> Comment.objects.filter(post__in=Subquery(posts.values("pk")))
在这种情况下,子查询必须使用 values()
只返回一列:该文章的主键。
To prevent a subquery from returning multiple rows, a slice ([:1]
) of the
queryset is used:
>>> subquery = Subquery(newest.values("email")[:1])
>>> Post.objects.annotate(newest_commenter_email=subquery)
在这种情况下,子查询必须只返回一列 和 一行:最近创建的评论的电子邮件地址。
(使用 get()
而不是分片会导致失败,因为 OuterRef
在 Subquery
中使用查询集之前无法解析。)
Exists()
子查询¶Exists
(queryset)¶Exists
是一个 Subquery
子类,它使用 SQL EXISTS
语句。在许多情况下,它的性能比子查询更好,因为当找到第一条匹配的记录时,数据库能够停止对子查询的执行。
For example, to annotate each post with whether or not it has a comment from within the last day:
>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef("pk"),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
在 PostgreSQL 上,SQL 看起来像:
SELECT "post"."id", "post"."published_at", EXISTS(
SELECT (1) as "a"
FROM "comment" U0
WHERE (
U0."created_at" >= YYYY-MM-DD HH:MM:SS AND
U0."post_id" = "post"."id"
)
LIMIT 1
) AS "recent_comment" FROM "post"
没有必要强制 Exists
指向单一列,因为列会被丢弃,并返回一个布尔结果。同样,由于在 SQL EXISTS
子查询中,排序并不重要,只会降低性能,所以会自动删除。
可以用 `~Exists()
来查询 NOT EXISTS
。
Subquery()
或 Exists()
表达式进行过滤。¶Subquery()
that returns a boolean value and Exists()
may be used as a
condition
in When
expressions, or to
directly filter a queryset:
>>> recent_comments = Comment.objects.filter(...) # From above
>>> Post.objects.filter(Exists(recent_comments))
这将确保子查询不会被添加到 SELECT
列中,这可能会带来更好的性能。
Subquery
表达式中使用集合。¶聚合可以在 Subquery
中使用,但需要将 filter()
、values()
和 annotate()
进行特定的组合,才能使子查询分组正确。
Assuming both models have a length
field, to find posts where the post
length is greater than the total length of all combined comments:
>>> from django.db.models import OuterRef, Subquery, Sum
>>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post")
>>> total_comments = comments.annotate(total=Sum("length")).values("total")
>>> Post.objects.filter(length__gt=Subquery(total_comments))
初始的 filter(...)
将子查询限制在相关参数上。order_by()
删除 Comment
模型上的默认 Order
(如果有的话)。values('post')
按 Post
聚合评论。最后,annotate(...)
执行聚合。这些查询集方法的应用顺序很重要。在这种情况下,由于子查询必须限于一列,所以需要使用 values('total')
。
这是在 Subquery
内进行聚合的唯一方法,因为使用 aggregary()
试图执行查询集(如果有 OuterRef
,将无法解决)。
RawSQL
(sql, params, output_field=None)¶Sometimes database expressions can't easily express a complex WHERE
clause.
In these edge cases, use the RawSQL
expression. For example:
>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (param,)))
这些额外的查找可能无法移植到不同的数据库引擎中(因为你是显式地编写 SQL 代码),并且违反了 DRY 原则,所以你应该尽可能地避免它们。
RawSQL
expressions can also be used as the target of __in
filters:
>>> queryset.filter(id__in=RawSQL("select id from sometable where col = %s", (param,)))
窗口函数提供了一种在分区上应用函数的方法。与一般的聚合函数不同,窗口函数对 帧 和分区进行操作,并计算每行的结果。
你可以在同一个查询中指定多个窗口,这在 Django ORM 中相当于在一个 QuerySet.annotate() 调用中包含多个表达式。ORM 并没有利用命名窗口,而是将其作为所选列的一部分。
Window
(expression, partition_by=None, order_by=None, frame=None, output_field=None)¶template
¶默认为 %(expression)sOVER (%(window)s)'
。如果只提供 expression
参数,窗口子句将是空白的。
Window
类是 OVER
子句的主要表达式。
expression
参数是一个 窗口函数,一个 聚合函数,或者一个与窗口子句兼容的表达式。
The partition_by
argument accepts an expression or a sequence of
expressions (column names should be wrapped in an F
-object) that control
the partitioning of the rows. Partitioning narrows which rows are used to
compute the result set.
output_field
是作为参数或通过表达式指定的。
The order_by
argument accepts an expression on which you can call
asc()
and
desc()
, a string of a field name (with an
optional "-"
prefix which indicates descending order), or a tuple or list
of strings and/or expressions. The ordering controls the order in which the
expression is applied. For example, if you sum over the rows in a partition,
the first result is the value of the first row, the second is the sum of first
and second row.
frame
参数指定在计算中应该使用哪些其他行。详见 帧。
添加了对按字段名称引用的 order_by
的支持。
For example, to annotate each movie with the average rating for the movies by the same studio in the same genre and release year:
>>> from django.db.models import Avg, F, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... ),
... )
这可以让你检查一部电影的评分是好是坏,与它的同行相比。
You may want to apply multiple expressions over the same window, i.e., the same partition and frame. For example, you could modify the previous example to also include the best and worst rating in each movie's group (same studio, genre, and release year) by using three window functions in the same query. The partition and ordering from the previous example is extracted into a dictionary to reduce repetition:
>>> from django.db.models import Avg, F, Max, Min, Window
>>> window = {
... "partition_by": [F("studio"), F("genre")],
... "order_by": "released__year",
... }
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... **window,
... ),
... best=Window(
... expression=Max("rating"),
... **window,
... ),
... worst=Window(
... expression=Min("rating"),
... **window,
... ),
... )
Filtering against window functions is supported as long as lookups are not
disjunctive (not using OR
or XOR
as a connector) and against a queryset
performing aggregation.
For example, a query that relies on aggregation and has an OR
-ed filter
against a window function and a field is not supported. Applying combined
predicates post-aggregation could cause rows that would normally be excluded
from groups to be included:
>>> qs = Movie.objects.annotate(
... category_rank=Window(Rank(), partition_by="category", order_by="-rating"),
... scenes_count=Count("actors"),
... ).filter(Q(category_rank__lte=3) | Q(title__contains="Batman"))
>>> list(qs)
NotImplementedError: Heterogeneous disjunctive predicates against window functions
are not implemented when performing conditional aggregation.
Support for filtering against window functions was added.
在 Django 内置的数据库后端中,MySQL 8.0.2+、PostgreSQL、Oracle 都支持窗口表达式。不同的数据库对不同窗口表达式功能的支持也不同。例如, asc()
和 esc()
中的选项可能不被支持。根据需要,请查阅你的数据库的文档。
对于一个窗口帧,你可以选择基于范围的行序列或普通的行序列。
ValueRange
(start=None, end=None)¶frame_type
¶该属性被设置为 'RANGE'
。
PostgreSQL 对 ValueRange
的支持有限,只支持使用标准的开始和结束点,如 CURRENT ROW
和 UNBOUNDED FOLLOWING
。
Both classes return SQL with the template:
%(frame_type)s BETWEEN %(start)s AND %(end)s
帧缩小了用于计算结果的行。它们从某个起点移动到某个指定的终点。帧可以不分区使用,但通常情况下,指定窗口的排序以确保结果的确定性是个好主意。在帧中,帧中的对等值是指具有等值的行,如果不存在排序子句,则是指所有行。
一个帧的默认起点是 UNBOUNDED PRECEDING
,即分区的第一行。终点总是显式地包含在 ORM 生成的 SQL 中,默认为 UNBOUNDED FOLLOWING
。默认帧包括从分区到集合中最后一行的所有行。
start
和 end
参数的可接受值是 None
、一个整数或零。start
的负整数会导致 N preceding
,而 None
会产生 UNBOUNDED PRECEDING
。对于 start
和 end
,0 将返回 CURRENT ROW
。end
接受正整数。
CURRENT ROW
包括的内容有区别。当在 ROWS
模式下指定时,帧以当前行开始或结束。当在 RANGE
模式下指定时,根据排序子句,帧以第一个或最后一个对等值开始或结束。因此,RANGE CURRENT ROW
对具有由排序指定的相同值的行执行表达式。因为模板包括 start
和 end
点,所以可以用:
ValueRange(start=0, end=0)
If a movie's "peers" are described as movies released by the same studio in the
same genre in the same year, this RowRange
example annotates each movie
with the average rating of a movie's two prior and two following peers:
>>> from django.db.models import Avg, F, RowRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=RowRange(start=-2, end=2),
... ),
... )
If the database supports it, you can specify the start and end points based on
values of an expression in the partition. If the released
field of the
Movie
model stores the release month of each movies, this ValueRange
example annotates each movie with the average rating of a movie's peers
released between twelve months before and twelve months after the each movie:
>>> from django.db.models import Avg, F, ValueRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=ValueRange(start=-12, end=12),
... ),
... )
下面你会发现对库作者可能有用的技术实现细节。下面的技术 API 和示例将有助于创建通用的查询表达式,可以扩展 Django 提供的内置功能。
查询表达式实现了 查询表达式 API,但也暴露了下面列出的一些额外的方法和属性。所有查询表达式必须继承于 Expression()
或相关子类。
当一个查询表达式包装另一个表达式时,它负责在被包装的表达式上调用相应的方法。
Expression
¶contains_aggregate
¶告诉 Django 这个表达式包含一个集合,需要在查询中添加一个 GROUP BY
子句。
filterable
¶告诉 Django 这个表达式可以在 QuerySet.filter()
中引用。默认值为 True
。
empty_result_set_value
¶Tells Django which value should be returned when the expression is used
to apply a function over an empty result set. Defaults to
NotImplemented
which forces the expression to be computed on
the database.
resolve_expression
(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False)¶Provides the chance to do any preprocessing or validation of
the expression before it's added to the query. resolve_expression()
must also be called on any nested expressions. A copy()
of self
should be returned with any necessary transformations.
query
是后端查询的实现。
allow_joins
是一个允许或拒绝在查询中使用连接的布尔值。
reuse
是一组可重用的多连接方案的连接。
summarize
是一个布尔值,当 True
时,表示正在计算的查询是一个终端聚合查询。
for_save
是一个布尔值,当 True
时,表示正在执行的查询正在进行创建或更新。
get_source_expressions
()¶Returns an ordered list of inner expressions. For example:
>>> Sum(F("foo")).get_source_expressions()
[F('foo')]
set_source_expressions
(expressions)¶获取一个表达式列表,并将其存储起来,使 get_source_expressions()
能够返回它们。
relabeled_clone
(change_map)¶返回 self`
的克隆(副本),并重新标明所有列别名。当创建子查询时,列别名会被重新命名。relabeled_clone()
也应该对任何嵌套的表达式进行调用并分配给克隆。
change_map
是一个将旧别名映射到新别名的字典。
举例:
def relabeled_clone(self, change_map):
clone = copy.copy(self)
clone.expression = self.expression.relabeled_clone(change_map)
return clone
convert_value
(value, expression, connection)¶一个钩子,允许表达式将 value`
强制转换成一个更合适的类型。
expression
与 self
相同。
get_group_by_cols
()¶Responsible for returning the list of columns references by
this expression. get_group_by_cols()
should be called on any
nested expressions. F()
objects, in particular, hold a reference
to a column.
The alias=None
keyword argument was removed.
asc
(nulls_first=None, nulls_last=None)¶返回准备按升序排序的表达式。
nulls_first
和 nulls_last
定义了如何对空值进行排序。参见 使用 F() 对空值进行排序 的用法示例。
In older versions, nulls_first
and nulls_last
defaulted to
False
.
4.1 版后已移除: Passing nulls_first=False
or nulls_last=False
to asc()
is deprecated. Use None
instead.
desc
(nulls_first=None, nulls_last=None)¶返回准备好降序排序的表达式。
nulls_first
和 nulls_last
定义了如何对空值进行排序。参见 使用 F() 对空值进行排序 的用法示例。
In older versions, nulls_first
and nulls_last
defaulted to
False
.
4.1 版后已移除: Passing nulls_first=False
or nulls_last=False
to desc()
is deprecated. Use None
instead.
你可以编写你自己的查询表达式类,这些类使用并可以与其他查询表达式集成。让我们通过一个例子,在不使用内置的 Func() 表达式 的情况下,编写一个 COALESCE
SQL 函数的实现。
COALESCE
SQL 函数被定义为接收一个列或值的列表,它将返回第一个不是 NULL
的列或值。它将返回第一个不是 NULL
的列或值。
我们将首先定义用于生成 SQL 的模板和一个 __init__()
方法来设置一些属性:
import copy
from django.db.models import Expression
class Coalesce(Expression):
template = "COALESCE( %(expressions)s )"
def __init__(self, expressions, output_field):
super().__init__(output_field=output_field)
if len(expressions) < 2:
raise ValueError("expressions must have at least 2 elements")
for expression in expressions:
if not hasattr(expression, "resolve_expression"):
raise TypeError("%r is not an Expression" % expression)
self.expressions = expressions
我们对参数进行一些基本的验证,包括要求至少有 2 个列或值,并确保它们是表达式。我们在这里要求 output_field
,这样 Django 就知道要把最终的结果分配给什么样的模型字段。
Now we implement the preprocessing and validation. Since we do not have any of our own validation at this point, we delegate to the nested expressions:
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):
c = self.copy()
c.is_summary = summarize
for pos, expression in enumerate(self.expressions):
c.expressions[pos] = expression.resolve_expression(
query, allow_joins, reuse, summarize, for_save
)
return c
接下来,我们编写负责生成 SQL 的方法:
def as_sql(self, compiler, connection, template=None):
sql_expressions, sql_params = [], []
for expression in self.expressions:
sql, params = compiler.compile(expression)
sql_expressions.append(sql)
sql_params.extend(params)
template = template or self.template
data = {"expressions": ",".join(sql_expressions)}
return template % data, sql_params
def as_oracle(self, compiler, connection):
"""
Example of vendor specific handling (Oracle in this case).
Let's make the function name lowercase.
"""
return self.as_sql(compiler, connection, template="coalesce( %(expressions)s )")
as_sql()
方法可以支持自定义关键字参数,允许 as_vendorname()
方法覆盖用于生成 SQL 字符串的数据。使用 as_sql()
关键字参数进行自定义最好是在 as_vendorname()
方法中突变 self
,因为后者在不同的数据库后端运行时可能会导致错误。如果你的类依赖于类属性来定义数据,可以考虑在你的 as_sql()
方法中允许覆盖。
我们使用 compiler.compile()
方法为每个 expressions
生成 SQL,并将结果用逗号连接起来。然后在模板中填入我们的数据,并返回 SQL 和参数。
我们还定义了一个专门针对 Oracle 后端的自定义实现。如果使用 Oracle 后端,将调用 as_oracle()
函数,而不是 as_sql()
。
最后,我们实现了其余的方法,使我们的查询表达式能够与其他查询表达式很好地配合:
def get_source_expressions(self):
return self.expressions
def set_source_expressions(self, expressions):
self.expressions = expressions
Let's see how it works:
>>> from django.db.models import F, Value, CharField
>>> qs = Company.objects.annotate(
... tagline=Coalesce(
... [F("motto"), F("ticker_name"), F("description"), Value("No Tagline")],
... output_field=CharField(),
... )
... )
>>> for c in qs:
... print("%s: %s" % (c.name, c.tagline))
...
Google: Do No Evil
Apple: AAPL
Yahoo: Internet Company
Django Software Foundation: No Tagline
由于 Func
的关键字参数 __init__()
(**extra
)和 as_sql()
(**extra_context
)的参数是内插到 SQL 字符串中,而不是作为查询参数传递的(数据库驱动程序会对其进行转义处理),因此它们不能包含不受信任的用户输入。
例如,如果 substring
是用户提供的,这个函数就容易被 SQL 注入:
from django.db.models import Func
class Position(Func):
function = "POSITION"
template = "%(function)s('%(substring)s' in %(expressions)s)"
def __init__(self, expression, substring):
# substring=substring is an SQL injection vulnerability!
super().__init__(expression, substring=substring)
这个函数在没有任何参数的情况下生成一个 SQL 字符串。由于 substring
是作为关键字参数传递给 super().__init__()
的,所以在将查询发送到数据库之前,它就被插入到 SQL 字符串中。
下面是更正后的改写:
class Position(Func):
function = "POSITION"
arg_joiner = " IN "
def __init__(self, expression, substring):
super().__init__(substring, expression)
用 substring
代替作为位置参数传递,它将作为数据库查询的参数传递。
如果你使用的数据库后端对某个函数使用了不同的 SQL 语法,你可以通过在函数的类上打一个新的方法来增加对它的支持。
比方说,我们正在为微软的 SQL Server 编写一个后端,它使用 SQL 的 LEN
而不是 LENGTH
来实现 Length
函数。我们将把一个名为 as_sqlserver()
的新方法移植到 Length
类上:
from django.db.models.functions import Length
def sqlserver_length(self, compiler, connection):
return self.as_sql(compiler, connection, function="LEN")
Length.as_sqlserver = sqlserver_length
你也可以使用 as_sql()
的 template
参数自定义 SQL。
我们使用 as_sqlserver()
,因为 django.db.connection.vendor
返回 sqlserver
作为后端。
第三方后端可以在后端包的顶层 __init__.py
文件或从顶层 __init__.py
导入的顶层 expressions.py
文件(或包)中注册它们的函数。
对于希望给自己正在使用的后端打补丁的用户项目来说,这段代码应该存在于 AppConfig.ready()
方法中。
5月 31, 2023