查询表达式描述可以用作更新、创建、筛选、排序依据、批注或聚合的一部分的值或计算。当表达式输出布尔值时,它可以直接用于过滤器。有许多内置表达式(如下所述)可用于帮助您编写查询。可以组合表达式,或者在某些情况下嵌套表达式,以形成更复杂的计算。
Django支持使用python常量、变量甚至其他表达式对查询表达式执行求反、加法、减法、乘法、除法、模算术和幂运算符。
本节中介绍的许多表达式都支持可选的 output_field
参数。如果给定,Django将在从数据库检索该值后将其加载到该字段中。
output_field
获取一个模型字段实例,如 IntegerField()
或 BooleanField()
。通常,该字段不需要任何参数,例如 max_length
,因为字段参数与不会对表达式的输出值执行的数据验证相关。
output_field
仅当Django无法自动确定结果的字段类型时才需要,例如混合了字段类型的复杂表达式。例如,添加一个 DecimalField()
以及一个 FloatField()
需要输出字段,如 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, OuterRef
>>> 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()
对象表示模型字段的值、模型字段的转换值或带批注的列。它使得引用模型字段值并使用它们执行数据库操作成为可能,而不必实际将它们从数据库中提取到Python内存中。
相反,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永远不会知道它——它完全由数据库处理。所有的 Python 都是通过Django F()
类,是创建用于引用字段和描述操作的SQL语法。
要访问以这种方式保存的新值,必须重新加载对象::
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
以及在上述单个实例上的操作中使用, F()
可用于 QuerySets
对象实例的,使用 update()
. 这减少了我们在上面使用的两个查询 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()
因此,可以通过以下方式提供性能优势:
让数据库而不是python完成工作
减少某些操作所需的查询数
F()
¶另一个有益的好处是 F()
是否让数据库(而不是python)更新字段的值可以避免 竞争条件 .
如果两个Python线程执行上面第一个示例中的代码,那么一个线程可以在另一个线程从数据库中检索字段值之后检索、增加和保存该字段的值。第二个线程保存的值将基于原始值;第一个线程的工作将丢失。
如果数据库负责更新字段,则该过程更为健壮:当 save()
或 update()
执行,而不是基于检索实例时的值。
F()
assignments persist after 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()
也是非常有用的 QuerySet
过滤器,它们可以根据一组对象的字段值(而不是python值)根据条件过滤它们。
F()
带批注¶F()
可用于通过将不同字段与算术组合在一起在模型上创建动态字段::
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
如果要组合的字段类型不同,则需要告诉Django将返回哪种类型的字段。大多数表达式都支持 output_field 在这种情况下,但因为 F()
,则需要用 ExpressionWrapper
**
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F("active_at") + F("duration"), output_field=DateTimeField()
)
)
在引用关系字段时,如 ForeignKey
, F()
返回主键值,而不是模型实例:
>>> car = Company.objects.annotate(built_by=F("manufacturer"))[0]
>>> car.manufacturer
<Manufacturer: Toyota>
>>> car.built_by
3
F()
对空值排序¶使用 F()
以及 nulls_first
或 nulls_last
关键字参数 Expression.asc()
或 desc()
控制字段空值的顺序。默认情况下,排序取决于数据库。
例如,对尚未联系的公司进行排序 (last_contacted
为空)在已联系的公司之后:
from django.db.models import F
Company.objects.order_by(F("last_contacted").desc(nulls_last=True))
F()
使用逻辑运算¶F()
输出以下内容的表达式 BooleanField
可以使用逆运算符在逻辑上求反 ~F()
。例如,要交换公司的激活状态:
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如下:
类属性,作为格式字符串,用于描述为此函数生成的SQL。默认为 '%(function)s(%(expressions)s)'
.
如果您正在构建类似SQL的 strftime('%W', 'date')
需要文字 %
查询中的字符,四倍 (%%%%
在 template
属性,因为字符串被插入两次:在模板插入期间插入一次 as_sql()
并在SQL中插入一次与数据库光标中的查询参数。
表示用于加入列表的字符的类属性。 expressions
一起。默认为 ', '
.
表示函数接受的参数数目的类属性。如果设置了此属性,并且使用不同数量的表达式调用了函数, TypeError
将被提升。默认为 None
.
为数据库函数生成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
must not contain untrusted user input 因为这些值是插入到SQL字符串中的,而不是作为查询参数传递的,所以数据库驱动程序将在其中对它们进行转义。
这个 *expressions
参数是将应用函数的位置表达式的列表。表达式将转换为字符串,并与 arg_joiner
然后插入到 template
作为 expressions
占位符。
位置参数可以是表达式或python值。字符串被假定为列引用,并将被包装在 F()
表达式,而其他值将包装在 Value()
表达。
这个 **extra
克瓦格斯群岛 key=value
可以插入到 template
属性。为了避免SQL注入漏洞, extra
must not contain untrusted user input 因为这些值是插入到SQL字符串中的,而不是作为查询参数传递的,所以数据库驱动程序将在其中对它们进行转义。
这个 function
, template
,以及 arg_joiner
可以使用关键字替换同名的属性,而不必定义您自己的类。 output_field 可用于定义预期的返回类型。
Aggregate()
表达¶聚合表达式是 Func() expression 它通知查询 GROUP BY
子句是必需的。所有的 aggregate functions ,像 Sum()
和 Count()
继承 Aggregate()
.
自从 Aggregate
s是表达式和包装表达式,可以表示一些复杂的计算:
from django.db.models import Count
Company.objects.annotate(
managers_required=(Count("num_employees") / 4) + Count("num_managers")
)
这个 Aggregate
API如下:
类属性,作为格式字符串,用于描述为此聚合生成的SQL。默认为 '%(function)s(%(distinct)s%(expressions)s)'
.
一个类属性,用于确定此聚合函数是否允许传递 distinct
关键字参数。如果设置为 False
(默认) TypeError
如果 distinct=True
通过。
默认为 None
因为大多数聚合函数都会导致 NULL
当应用于空结果集时。
这个 expressions
位置参数可以包括表达式、模型字段的转换或模型字段的名称。它们将被转换为字符串并用作 expressions
控件中的占位符 template
。
这个 distinct
参数确定是否应为的每个不同值调用聚合函数 expressions
(或一组值,用于多个 expressions
)只有具有 allow_distinct
设置为 True
.
这个 filter
参数采用 Q object
用于筛选聚合的行。见 条件聚合 和 对批注进行筛选 例如用法。
这个 default
参数接受一个值,该值将与聚合一起传递到 Coalesce
。这对于指定要返回的值而不是 None
当查询集(或分组)不包含任何条目时。
这个 **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()
表达¶A Value()
对象表示表达式中可能的最小组件:简单值。当需要在表达式中表示整数、布尔值或字符串的值时,可以将该值包装在 Value()
.
你很少需要使用 Value()
直接。当你写表达式时 F('field') + 1
,Django隐式包装 1
在一个 Value()
,允许在更复杂的表达式中使用简单值。你需要使用 Value()
当您要将字符串传递给表达式时。大多数表达式将字符串参数解释为字段名,例如 Lower('name')
.
这个 value
参数描述要包含在表达式中的值,例如 1
, True
或 None
. Django知道如何将这些python值转换为相应的数据库类型。
如果没有 output_field 是指定的,则将从提供的 value
对于许多常见类型。例如,传递 datetime.datetime
AS value
默认设置 output_field
至 DateTimeField
。
ExpressionWrapper()
表达¶ExpressionWrapper
将另一个表达式括起来并提供对属性的访问,例如 output_field ,这在其他表达式中可能不可用。 ExpressionWrapper
在使用算术运算时是必需的 F()
不同类型的表达式,如中所述 使用 F() 带批注 。
条件表达式允许您使用 if
… elif
… else
查询中的逻辑。Django本机支持SQL CASE
表达。有关详细信息,请参阅 条件表达式 .
Subquery()
表达¶可以将显式子查询添加到 QuerySet
使用 Subquery
表达式。
例如,要使用该帖子上最新评论的作者的电子邮件地址为该帖子添加注释:
>>> 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
属性中的查询集 Subquery
需要引用外部查询或其转换中的字段。它的行为就像一个 F
表达式,除非在解析外部查询集之前不会检查它是否引用有效字段。
实例 OuterRef
可以与嵌套的 Subquery
引用不是直接父级的包含查询集。例如,此查询集需要位于嵌套的 Subquery
要正确解析的实例:
>>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
有时必须从 Subquery
例如,要使用 Subquery
作为一个 __in
查一查。要返回前一天发布的帖子的所有评论,请执行以下操作:
>>> 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()
只返回一列:日志的主键。
若要防止子查询返回多行,请使用切片 ([:1]
使用了查询集的):
>>> subquery = Subquery(newest.values("email")[:1])
>>> Post.objects.annotate(newest_commenter_email=subquery)
在这种情况下,子查询只能返回一列 and 一行:最近创建的评论的电子邮件地址。
(使用) get()
而不是切片会失败,因为 OuterRef
只有在 Subquery
)
Exists()
子查询¶Exists
是一个 Subquery
使用SQL的子类 EXISTS
语句。在许多情况下,它比子查询执行得更好,因为当找到第一个匹配行时,数据库能够停止对子查询的计算。
例如,要用前一天内是否有评论来注释每个帖子:
>>> 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
子查询只会降低性能,它会自动删除。
您可以使用 NOT EXISTS
具有 ~Exists()
.
Subquery()
或 Exists()
表达¶Subquery()
它返回布尔值,并且 Exists()
可以用作 condition
在……里面 When
表达式,或直接过滤查询集:
>>> recent_comments = Comment.objects.filter(...) # From above
>>> Post.objects.filter(Exists(recent_comments))
这将确保不会将子查询添加到 SELECT
列的性能可能会更好。
Subquery
表达¶集料可用于 Subquery
但它们需要 filter()
, values()
和 annotate()
以使子查询分组正确。
假设两个型号都有一个 length
字段,以查找帖子长度大于所有综合评论的总长的帖子:
>>> 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()
删除默认值 ordering
(如果有的话) Comment
模型。 values('post')
聚合注释依据 Post
. 最后, annotate(...)
执行聚合。这些查询集方法的应用顺序很重要。在这种情况下,由于子查询必须限于一列, values('total')
是必需的。
这是在 Subquery
作为使用 aggregate()
尝试评估查询集(如果存在 OuterRef
,这将无法解决)。
有时数据库表达式不能很容易地表达复杂的 WHERE
第。条。在这些边缘情况下,使用 RawSQL
表情。例如:
>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (param,)))
这些额外的查找可能无法移植到不同的数据库引擎(因为您显式地编写了SQL代码),并且违反了DRY原则,因此如果可能的话,应该避免它们。
RawSQL
表达式还可以用作 __in
过滤器:
>>> queryset.filter(id__in=RawSQL("select id from sometable where col = %s", (param,)))
警告
防止 SQL injection attacks ,必须转义用户可以使用 params
. params
是强制您确认您没有使用用户提供的数据插入SQL所必需的参数。
您也不能将SQL字符串中的占位符引起来。由于前后有引号,此示例容易受到SQL注入的攻击 %s
:
RawSQL("select col from sometable where othercol = '%s'") # unsafe!
你可以阅读更多关于 Django 的 SQL injection protection 作品。
窗口函数提供了一种在分区上应用函数的方法。与普通的聚合函数(它为group by定义的每个集合计算最终结果)不同,窗口函数对 frames 并计算每行的结果。
您可以在同一查询中指定多个窗口,在django orm中,这相当于在 QuerySet.annotate() 调用。ORM不使用命名窗口,而是作为所选列的一部分。
默认为 %(expression)s OVER (%(window)s)
。如果只有 expression
参数,则Window子句将为空。
这个 Window
类是 OVER
条款。
这个 expression
参数不是 window function ,一个 aggregate function 或与window子句兼容的表达式。
这个 partition_by
参数接受一个表达式或一个表达式序列(列名应该用 F
-对象),它控制行的分区。分区缩小了用于计算结果集的行数。
这个 output_field 指定为参数或由表达式指定。
这个 order_by
参数接受可在其上调用的表达式 asc()
和 desc()
,字段名的字符串(带有可选的 "-"
指示降序的前缀),或字符串和/或表达式的元组或列表。顺序控制应用表达式的顺序。例如,如果对分区中的行求和,第一个结果是第一行的值,第二个结果是第一行和第二行的总和。
这个 frame
参数指定应在计算中使用的其他行。见 框架 有关详细信息。
例如,要用同一制片厂对同一类型和发行年份的电影的平均评分来注释每部电影:
>>> 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",
... ),
... )
这允许您检查一部电影的评级是否比它的同类电影好还是差。
您可能希望在同一窗口上应用多个表达式,即相同的分区和框架。例如,通过在同一查询中使用三个窗口函数,您可以修改前面的示例,使其也包括每个电影组(相同的工作室、流派和发行年份)中的最佳和最差评级。上例中的划分和排序被提取到字典中,以减少重复:
>>> 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,
... ),
... )
只要查找不是析取的(不使用 OR
或 XOR
作为连接器),并且针对执行聚集的查询集。
例如,依赖于聚合的查询具有 OR
不支持针对窗口函数和字段的-ed过滤器。在聚合后应用组合谓词可能会导致将通常从组中排除的行包括在内:
>>> 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.
在Django的内置数据库后端中,MySQL、PostgreSQL和Oracle支持窗口表达式。对不同窗口表达功能的支持因不同的数据库而异。例如,中的选项 asc()
和 desc()
可能不受支持。根据需要参考您的数据库的文档。
对于窗口框架,可以选择基于范围的行序列或普通的行序列。
此属性设置为 'RANGE'
.
PostgreSQL对 ValueRange
并且只支持使用标准的起点和终点,例如 CURRENT ROW
和 UNBOUNDED FOLLOWING
.
这个 exclusion
添加了参数。
此属性设置为 'ROWS'
.
这个 exclusion
添加了参数。
这两个类都返回带有模板的SQL:
%(frame_type)s BETWEEN %(start)s AND %(end)s
这个 exclusion
参数允许排除行 (CURRENT_ROW
)、群组 (GROUP
),和领带 (TIES
)从支持的数据库上的窗口框架:
%(frame_type)s BETWEEN %(start)s AND %(end)s EXCLUDE %(exclusion)s
帧缩小了用于计算结果的行。它们从某个起点移动到某个特定的终点。帧可以与分区一起使用,也可以不与分区一起使用,但通常最好指定窗口的顺序以确保确定的结果。在帧中,帧中的对等点是具有等效值的行,如果没有排序子句,则是所有行。
帧的默认起始点是 UNBOUNDED PRECEDING
这是分区的第一行。端点总是显式包含在ORM生成的SQL中,并且在默认情况下是 UNBOUNDED FOLLOWING
. 默认框架包括从分区到集合中最后一行的所有行。
属性的接受值 start
和 end
论据是 None
、整数或零。的负整数 start
在.中的结果 N PRECEDING
,而 None
收益率 UNBOUNDED PRECEDING
。在……里面 ROWS
模式下,正整数可用于 `start
导致 N FOLLOWING
。正整数可用于 end
并导致 N FOLLOWING
。在……里面 ROWS
模式下,负整数可用于 `end
导致 N PRECEDING
。对两个人都是 start
和 end
,零将返回 CURRENT ROW
。
在什么方面有区别 CURRENT ROW
包括。当在中指定时 ROWS
模式,帧以当前行开始或结束。当在中指定时 RANGE
模式,帧根据排序子句在第一个或最后一个对等点开始或结束。因此, RANGE CURRENT ROW
对具有由排序指定的相同值的行计算表达式。因为模板包括 start
和 end
点,这可以表示为:
ValueRange(start=0, end=0)
如果一部电影的“同行”被描述为由同一制片厂在同一年以同一类型发行的电影,这 RowRange
示例使用一部电影的前两个同级和后两个同级的平均评分来注释每部电影:
>>> 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),
... ),
... )
如果数据库支持,则可以根据分区中的表达式的值指定起点和终点。如果 released
中的字段 Movie
Model存储每部电影的上映月份,这是 ValueRange
示例使用在每部电影之前12个月和之后12个月之间上映的电影同行的平均评分来注释每部电影:
>>> 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),
... ),
... )
支持正整数 start
和负整数 end
已为以下对象添加 RowRange
。
在下面,您将发现可能对库作者有用的技术实现细节。下面的技术API和示例将有助于创建通用查询表达式,以扩展Django提供的内置功能。
查询表达式实现 query expression API ,但也会公开下面列出的许多额外方法和属性。所有查询表达式都必须继承自 Expression()
或相关子类。
当查询表达式包装另一个表达式时,它负责对包装的表达式调用适当的方法。
告诉Django这个表达式可以用在 Field.db_default
。默认为 False
。
告诉Django此表达式包含聚合,并且 GROUP BY
需要将子句添加到查询中。
告诉Django可以在 QuerySet.filter()
.默认为 True
.
告诉Django在使用表达式对空结果集应用函数时应返回哪个值。默认为 NotImplemented
这将强制在数据库上计算表达式。
提供在将表达式添加到查询之前对其执行任何预处理或验证的机会。 resolve_expression()
也必须在任何嵌套表达式上调用。一个 copy()
的 self
应随任何必要的转换一起返回。
query
是后端查询实现。
allow_joins
是允许或拒绝在查询中使用联接的布尔值。
reuse
是一组用于多连接方案的可重用连接。
summarize
是一个布尔值,当 True
,表示正在计算的查询是终端聚合查询。
for_save
是一个布尔值,当 True
,表示正在执行的查询正在执行创建或更新。
返回内部表达式的有序列表。例如:
>>> Sum(F("foo")).get_source_expressions()
[F('foo')]
获取表达式列表并将其存储为 get_source_expressions()
可以退货。
返回的复制(副本) 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
允许表达式强制 value
更合适的类型。
expression
是一样的 self
.
负责返回此表达式引用的列的列表。 get_group_by_cols()
应在任何嵌套表达式上调用。 F()
具体而言,对象包含对列的引用。
返回可按升序排序的表达式。
nulls_first
和 nulls_last
定义空值的排序方式。见 使用 F() 对空值排序 例如用法。
返回准备按降序排序的表达式。
nulls_first
和 nulls_last
定义空值的排序方式。见 使用 F() 对空值排序 例如用法。
您可以编写自己的查询表达式类,这些类使用并可以与其他查询表达式集成。让我们通过编写 COALESCE
SQL函数,不使用内置的 Func() expressions .
这个 COALESCE
SQL函数被定义为获取列或值的列表。它将返回第一列或不是 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就知道应该将最终结果分配给哪种模型场。
现在,我们实现了数据的预处理和验证。由于此时我们没有任何自己的验证,因此我们委托嵌套的表达式::
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()
自定义的关键字参数优于可变参数 self
在内部 as_vendorname()
方法在不同的数据库后端运行时可能会导致错误。如果类依赖类属性来定义数据,请考虑在 as_sql()
方法。
我们为每个 expressions
通过使用 compiler.compile()
方法,并用逗号将结果联接在一起。然后用我们的数据填充模板,并返回SQL和参数。
我们还定义了一个特定于Oracle后端的自定义实现。这个 as_oracle()
将调用函数而不是 as_sql()
如果Oracle后端正在使用中。
最后,我们实现了其他方法,这些方法允许我们的查询表达式与其他查询表达式一起使用:
def get_source_expressions(self):
return self.expressions
def set_source_expressions(self, expressions):
self.expressions = expressions
让我们看看它是如何工作的:
>>> 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
自从A 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的Microsoft SQL Server编写后端 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
您还可以使用 template
参数 as_sql()
.
我们使用 as_sqlserver()
因为 django.db.connection.vendor
收益率 sqlserver
对于后端。
第三方后端可以在顶级注册其功能 __init__.py
后端包的文件或顶级文件 expressions.py
从顶层导入的文件(或包) __init__.py
.
对于希望修补其正在使用的后端的用户项目,此代码应位于 AppConfig.ready()
方法。
12月 18, 2023