查询表达式描述了一个值或一个计算,它可以作为更新、创建、过滤、排序、注解或聚合的一部分。当一个表达式输出一个布尔值时,它可以直接用于过滤器中。有许多内置的表达式(在下面的文档中)可以用来帮助你编写查询。表达式可以组合,或者在某些情况下嵌套,以形成更复杂的计算。
Django 支持负、加、减、乘、除、模数运算,以及对查询表达式的幂运算符,使用 Python 常量、变量,甚至其他表达式。
Many of the expressions documented in this section support an optional
output_field
parameter. If given, Django will load the value into that
field after retrieving it from the database.
output_field
takes a model field instance, like IntegerField()
or
BooleanField()
. Usually, the field doesn't need any arguments, like
max_length
, since field arguments relate to data validation which will not
be performed on the expression's output value.
output_field
is only required when Django is unable to automatically
determine the result's field type, such as complex expressions that mix field
types. For example, adding a DecimalField()
and a FloatField()
requires
an output field, like output_field=FloatField()
.
>>> 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"))
If the fields that you're combining are of different types you'll need to tell
Django what kind of field will be returned. Most expressions support
output_field for this case, but since F()
does not, you
will need to wrap the expression with 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 字符串中,而不是作为查询参数传递,数据库驱动程序对其进行转义。
The function
, template
, and arg_joiner
keywords can be used to
replace the attributes of the same name without having to define your own
class. output_field can be used to define the expected
return type.
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
.
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 值转换为相应的数据库类型。
If no output_field is specified, it will be inferred from
the type of the provided value
for many common types. For example, passing
an instance of datetime.datetime
as value
defaults
output_field
to DateTimeField
.
ExpressionWrapper()
表达式¶ExpressionWrapper
(expression, output_field)¶ExpressionWrapper
surrounds another expression and provides access to
properties, such as output_field, that may not be
available on other expressions. ExpressionWrapper
is necessary when using
arithmetic on F()
expressions with different types as described in
与注解一起使用 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
¶Defaults to %(expression)s OVER (%(window)s)
. If only the
expression
argument is provided, the window clause will be blank.
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.
The output_field is specified either as an argument or by the expression.
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
参数指定在计算中应该使用哪些其他行。详见 帧。
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.
Among Django's built-in database backends, MySQL, PostgreSQL, and Oracle
support window expressions. Support for different window expression features
varies among the different databases. For example, the options in
asc()
and
desc()
may not be supported. Consult the
documentation for your database as needed.
对于一个窗口帧,你可以选择基于范围的行序列或普通的行序列。
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 movie, this ValueRange
example annotates each movie with the average rating of a movie's peers
released between twelve months before and twelve months after 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
¶allowed_default
¶Tells Django that this expression can be used in
Field.db_default
. Defaults to False
.
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() 对空值进行排序 的用法示例。
desc
(nulls_first=None, nulls_last=None)¶返回准备好降序排序的表达式。
nulls_first
和 nulls_last
定义了如何对空值进行排序。参见 使用 F() 对空值进行排序 的用法示例。
你可以编写你自己的查询表达式类,这些类使用并可以与其他查询表达式集成。让我们通过一个例子,在不使用内置的 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
We do some basic validation on the parameters, including requiring at least 2 columns or values, and ensuring they are expressions. We are requiring output_field here so that Django knows what kind of model field to assign the eventual result to.
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()
方法中。
12月 05, 2023