查询表达式 — Django 文档

来自菜鸟教程
Django/docs/3.2.x/ref/models/expressions
跳转至:导航、​搜索

查询表达式

查询表达式描述可用作更新、创建、过滤、排序依据、注释或聚合的一部分的值或计算。 当表达式输出布尔值时,可以直接在过滤器中使用。 有许多内置表达式(记录如下)可用于帮助您编写查询。 表达式可以组合,或在某些情况下嵌套,以形成更复杂的计算。

支持的算术

Django 支持否定、加法、减法、乘法、除法、模运算和查询表达式的幂运算符,使用 Python 常量、变量,甚至其他表达式。


一些例子

from django.db.models import Count, F, Value
from django.db.models.functions import Length, Upper

# 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))
)

内置表达式

笔记

这些表达式在 django.db.models.expressionsdjango.db.models.aggregates 中定义,但为了方便起见,它们可用并且通常从 django.db.models 导入。


F() 表达式

class 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 永远不会知道它——它完全由数据库处理。 通过 Django 的 F() 类,Python 所做的一切就是创建 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.all().update(stories_filed=F('stories_filed') + 1)

F() 因此可以通过以下方式提供性能优势:

  • 让数据库而不是 Python 来做工作
  • 减少某些操作所需的查询数量

3.2 版更改: 增加了对字段转换的支持。


使用 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()QuerySet 过滤器中也非常有用,它们可以根据字段值而不是 Python 值根据条件过滤一组对象。

这在 中使用查询 中的 F() 表达式进行了记录。


将 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()))

引用 ForeignKey 等关系字段时,F() 返回主键值而不是模型实例:

>> car = Company.objects.annotate(built_by=F('manufacturer'))[0]
>> car.manufacturer
<Manufacturer: Toyota>
>> car.built_by
3

使用 F() 对空值进行排序

使用 F()nulls_firstnulls_last 关键字参数到 Expression.asc()desc() 来控制排序字段的空值。 默认情况下,排序取决于您的数据库。

例如,要在联系过的公司之后对未联系过的公司进行排序(last_contacted 为空):

from django.db.models import F
Company.objects.order_by(F('last_contacted').desc(nulls_last=True))

Func() 表达式

Func() 表达式是所有涉及数据库函数(如 COALESCELOWER)或聚合(如 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 如下:

class Func(*expressions, **extra)
function

描述将生成的函数的类属性。 具体来说,function 将作为 模板 内的 function 占位符进行插值。 默认为 None

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() 方法应使用 functiontemplatearg_joiner 和任何其他 **extra_context 参数来根据需要自定义 SQL。 例如:

django/db/models/functions.py

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 连接在一起,然后作为 expressions 占位符插入到 template 中。

位置参数可以是表达式或 Python 值。 字符串被假定为列引用并将被包装在 F() 表达式中,而其他值将被包装在 Value() 表达式中。

**extra kwargs 是 key=value 对,可以插入到 template 属性中。 为避免 SQL 注入漏洞,extra 不得包含不受信任的用户输入 ,因为这些值被插入到 SQL 字符串中,而不是作为查询参数传递,数据库驱动程序会在其中对它们进行转义。

functiontemplatearg_joiner 关键字可用于替换同名的属性,而无需定义您自己的类。 output_field 可用于定义预期的返回类型。


Aggregate() 表达式

聚合表达式是 Func() 表达式 的一个特例,它通知查询需要一个 GROUP BY 子句。 所有 聚合函数 ,如 Sum()Count(),都继承自 Aggregate()

由于 Aggregates 是表达式和包装表达式,您可以表示一些复杂的计算:

from django.db.models import Count

Company.objects.annotate(
    managers_required=(Count('num_employees') / 4) + Count('num_managers'))

Aggregate API 如下:

class Aggregate(*expressions, output_field=None, distinct=False, filter=None, **extra)
template

一个类属性,作为格式字符串,描述为此聚合生成的 SQL。 默认为 '%(function)s(%(distinct)s%(expressions)s)'

function

描述将生成的聚合函数的类属性。 具体来说,function 将作为 模板 内的 function 占位符进行插值。 默认为 None

window_compatible

默认为 True,因为大多数聚合函数都可以用作 Window 中的源表达式。

allow_distinct

确定此聚合函数是否允许传递 distinct 关键字参数的类属性。 如果设置为 False(默认),如果通过 distinct=True,则会引发 TypeError

expressions 位置参数可以包括表达式、模型字段的转换或模型字段的名称。 它们将被转换为字符串并用作 template 中的 expressions 占位符。

output_field 参数需要一个模型字段实例,如 IntegerField()BooleanField(),Django 将在从数据库中检索值后将其加载到其中。 通常在实例化模型字段时不需要参数,因为任何与数据验证相关的参数(max_lengthmax_digits 等)都不会在表达式的输出值上强制执行。

注意 output_field 仅在 Django 无法确定结果应该是什么字段类型时才需要。 混合字段类型的复杂表达式应定义所需的 output_field。 例如,将一个 IntegerField() 和一个 FloatField() 加在一起应该可能定义了 output_field=FloatField()

distinct 参数确定是否应为 expressions 的每个不同值(或多个 expressions 的值集)调用聚合函数。 该参数仅在 allow_distinct 设置为 True 的聚合上受支持。

filter 参数采用 Q 对象 ,用于过滤聚合的行。 有关示例用法,请参阅 条件聚合注释过滤

**extra kwargs 是 key=value 对,可以插入到 template 属性中。

3.2 版更改: 增加了对字段转换的支持。


创建您自己的聚合函数

您也可以创建自己的聚合函数。 至少,您需要定义 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() 表达式

class Value(value, output_field=None)

Value() 对象表示表达式的最小可能组件:一个简单的值。 当您需要在表达式中表示整数、布尔值或字符串的值时,您可以将该值包装在 Value() 中。

您很少需要直接使用 Value()。 当您编写表达式 F('field') + 1 时,Django 将 1 隐式包装在 Value() 中,从而允许在更复杂的表达式中使用简单的值。 当您想将字符串传递给表达式时,您需要使用 Value()。 大多数表达式将字符串参数解释为字段的名称,例如 Lower('name')

value 参数描述要包含在表达式中的值,例如 1TrueNone。 Django 知道如何将这些 Python 值转换为它们对应的数据库类型。

output_field 参数应该是一个模型字段实例,如 IntegerField()BooleanField(),Django 将在从数据库中检索值后将其加载到其中。 通常在实例化模型字段时不需要参数,因为任何与数据验证相关的参数(max_lengthmax_digits 等)都不会在表达式的输出值上强制执行。 如果没有指定 output_field,如果可能,它将从提供的 valuetype 中暂时推断出来。 例如,将 datetime.datetime 的实例作为 value 传递将默认 output_fieldDateTimeField

3.2 版更改: 支持从 value 的类型推断默认 output_field


ExpressionWrapper() 表达式

class ExpressionWrapper(expression, output_field)

ExpressionWrapper 包围另一个表达式并提供对其他表达式可能不可用的属性的访问,例如 output_fieldExpressionWrapper 在对具有不同类型的 F() 表达式使用算术时是必需的,如 使用带注释的 F() 中所述。


条件表达式

条件表达式允许您在查询中使用 if ... elif ... else 逻辑。 Django 本身支持 SQL CASE 表达式。 有关更多详细信息,请参阅 条件表达式


Subquery() 表达式

class Subquery(queryset, output_field=None)

您可以使用 Subquery 表达式向 QuerySet 添加显式子查询。

例如,要使用该帖子的最新评论的作者的电子邮件地址来注释每个帖子:

>>> 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 执行子查询。 在某些情况下,可以编写一个等效的查询集来更清晰或更高效地执行相同的任务。


从外部查询集中引用列

class OuterRef(field)

Subquery 中的查询集需要引用外部查询或其转换中的字段时,请使用 OuterRef。 它的作用类似于 F 表达式,只是在解析外部查询集之前不会检查它是否引用了有效字段。

OuterRef 的实例可以与 Subquery 的嵌套实例结合使用,以引用不是直接父级的包含查询集。 例如,这个查询集需要在一对嵌套的 Subquery 实例中才能正确解析:

>>> Book.objects.filter(author=OuterRef(OuterRef('pk')))

3.2 版更改: 增加了对字段转换的支持。


将子查询限制为单列

有时必须从 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)

在这种情况下,子查询必须只返回单列 和单行 :最近创建的评论的电子邮件地址。

(使用 get() 而不是切片会失败,因为在 Subquery 中使用查询集之前,无法解析 OuterRef。)


Exists() 子查询

class Exists(queryset)

Exists 是使用 SQL EXISTS 语句的 Subquery 子类。 在许多情况下,它会比子查询执行得更好,因为当找到第一个匹配行时,数据库能够停止对子查询的评估。

例如,要注释每个帖子是否有最后一天的评论:

>>> 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() 可以用作 When 表达式中的 condition,或直接过滤查询集:

>>> 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() 删除 Comment 模型上的默认 排序 (如果有)。 values('post') 汇总了 Post 的评论。 最后,annotate(...) 执行聚合。 应用这些查询集方法的顺序很重要。 在这种情况下,由于子查询必须限制为单个列,因此需要 values('total')

这是在 Subquery 中执行聚合的唯一方法,因为使用 aggregate() 尝试评估查询集(如果有 OuterRef,这不会可以解决)。


原始 SQL 表达式

class RawSQL(sql, params, output_field=None)

有时数据库表达式不能轻易表达复杂的 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 注入攻击 ,您必须转义用户可以使用 params 控制的任何参数。 params 是强制您承认您没有使用用户提供的数据插入 SQL 的必需参数。

您也不得在 SQL 字符串中引用占位符。 由于 %s 周围的引号,此示例容易受到 SQL 注入攻击:

RawSQL("select col from sometable where othercol = '%s'")  # unsafe!

您可以阅读有关 Django SQL 注入保护 工作原理的更多信息。


窗口函数

窗口函数提供了一种在分区上应用函数的方法。 与为 group by 定义的每个集合计算最终结果的普通聚合函数不同,窗口函数对 和分区进行操作,并计算每一行的结果。

您可以在同一个查询中指定多个窗口,这在 Django ORM 中相当于在 QuerySet.annotate() 调用中包含多个表达式。 ORM 不使用命名窗口,而是它们是选定列的一部分。

class Window(expression, partition_by=None, order_by=None, frame=None, output_field=None)
filterable

默认为 False。 SQL 标准不允许在 WHERE 子句中引用窗口函数,并且 Django 在构造一个 QuerySet 时会引发异常。

template

默认为 %(expression)s OVER (%(window)s)'。 如果仅提供 expression 参数,则 window 子句将为空白。

Window 类是 OVER 子句的主要表达式。

expression 参数是 窗口函数聚合函数 或与窗口子句兼容的表达式。

partition_by 参数接受控制行分区的表达式或表达式序列(列名应包含在 F 对象中)。 分区缩小了用于计算结果集的行。

output_field 被指定为参数或表达式。

order_by 参数接受一个表达式或一个表达式序列,您可以在其上调用 asc()desc()。 排序控制应用表达式的顺序。 例如,如果对分区中的行求和,第一个结果是第一行的值,第二个结果是第一行和第二行的总和。

frame 参数指定应在计算中使用的其他行。 有关详细信息,请参阅

例如,要使用同一工作室、同一类型和发行年份的电影的平均评分来注释每部电影:

>>> from django.db.models import Avg, F, Window
>>> from django.db.models.functions import ExtractYear
>>> Movie.objects.annotate(
>>>     avg_rating=Window(
>>>         expression=Avg('rating'),
>>>         partition_by=[F('studio'), F('genre')],
>>>         order_by=ExtractYear('released').asc(),
>>>     ),
>>> )

这使您可以检查一部电影的评分是否高于或低于同行。

您可能希望在同一个窗口(即同一个分区和框架)上应用多个表达式。 例如,您可以修改前面的示例,通过在同一查询中使用三个窗口函数,还包括每个电影组(相同的工作室、流派和发行年份)中的最佳和最差评级。 将上一个示例中的分区和排序提取到字典中以减少重复:

>>> from django.db.models import Avg, F, Max, Min, Window
>>> from django.db.models.functions import ExtractYear
>>> window = {
>>>    'partition_by': [F('studio'), F('genre')],
>>>    'order_by': ExtractYear('released').asc(),
>>> }
>>> Movie.objects.annotate(
>>>     avg_rating=Window(
>>>         expression=Avg('rating'), **window,
>>>     ),
>>>     best=Window(
>>>         expression=Max('rating'), **window,
>>>     ),
>>>     worst=Window(
>>>         expression=Min('rating'), **window,
>>>     ),
>>> )

在 Django 的内置数据库后端中,MySQL 8.0.2+、PostgreSQL 和 Oracle 支持窗口表达式。 对不同窗口表达式功能的支持因数据库而异。 例如,可能不支持 asc()desc() 中的选项。 根据需要查阅您的数据库的文档。

框架

对于窗口框架,您可以选择基于范围的行序列或普通的行序列。

class ValueRange(start=None, end=None)
frame_type

此属性设置为 'RANGE'

PostgreSQL 对 ValueRange 的支持有限,只支持使用标准的起点和终点,例如 CURRENT ROWUNBOUNDED FOLLOWING

class RowRange(start=None, end=None)
;; frame_type
此属性设置为 'ROWS'

这两个类都返回带有模板的 SQL:

%(frame_type)s BETWEEN %(start)s AND %(end)s

框架缩小了用于计算结果的行。 它们从某个起点转移到某个指定的终点。 框架可以使用和不使用分区,但指定窗口的顺序以确保确定性结果通常是个好主意。 在框架中,框架中的对等点是具有等效值的行,或者如果不存在排序子句,则是所有行。

帧的默认起点是 UNBOUNDED PRECEDING,它是分区的第一行。 终点始终明确包含在 ORM 生成的 SQL 中,默认情况下为 UNBOUNDED FOLLOWING。 默认框架包括从分区到集合中最后一行的所有行。

startend 参数的可接受值为 None、整数或零。 start 的负整数产生 N preceding,而 None 产生 UNBOUNDED PRECEDING。 对于 startend,零将返回 CURRENT ROWend 接受正整数。

CURRENT ROW 包含的内容有所不同。 在 ROWS 模式下指定时,帧以当前行开始或结束。 当在 RANGE 模式下指定时,帧根据排序条款在第一个或最后一个对等点开始或结束。 因此,RANGE CURRENT ROW 为具有由排序指定的相同值的行计算表达式。 因为模板同时包含 startend 点,这可以表示为:

ValueRange(start=0, end=0)

如果一部电影的“同行”被描述为同一年同一类型的同一工作室发行的电影,这个 RowRange 示例用电影的两个前两个同行和两个后续同行的平均评分来注释每部电影:

>>> from django.db.models import Avg, F, RowRange, Window
>>> from django.db.models.functions import ExtractYear
>>> Movie.objects.annotate(
>>>     avg_rating=Window(
>>>         expression=Avg('rating'),
>>>         partition_by=[F('studio'), F('genre')],
>>>         order_by=ExtractYear('released').asc(),
>>>         frame=RowRange(start=-2, end=2),
>>>     ),
>>> )

如果数据库支持,您可以根据分区中表达式的值指定起点和终点。 如果 Movie 模型的 released 字段存储每部电影的发行月份,则此 ValueRange 示例使用在前 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=F('released').asc(),
>>>         frame=ValueRange(start=-12, end=12),
>>>     ),
>>> )

技术资料

您将在下面找到可能对库作者有用的技术实现细节。 下面的技术 API 和示例将有助于创建可以扩展 Django 提供的内置功能的通用查询表达式。

表达式接口

查询表达式实现了 查询表达式 API,但也公开了下面列出的许多额外方法和属性。 所有查询表达式都必须从 Expression() 或相关子类继承。

当查询表达式包装另一个表达式时,它负责对包装的表达式调用适当的方法。

class Expression
contains_aggregate

告诉 Django 这个表达式包含一个聚合并且需要将 GROUP BY 子句添加到查询中。

contains_over_clause

告诉 Django 这个表达式包含一个 Window 表达式。 例如,它用于在修改数据的查询中禁止窗口函数表达式。

filterable

告诉 Django 这个表达式可以在 QuerySet.filter() 中引用。 默认为 True

window_compatible

告诉 Django 这个表达式可以用作 Window 中的源表达式。 默认为 False

resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False)

提供在将表达式添加到查询之前对其进行任何预处理或验证的机会。 resolve_expression() 也必须在任何嵌套表达式上调用。 selfcopy() 应与任何必要的转换一起返回。

query 是后端查询实现。

allow_joins 是一个布尔值,允许或拒绝在查询中使用连接。

reuse 是一组用于多连接场景的可重用连接。

summarize 是一个布尔值,当 True 表示正在计算的查询是终端聚合查询时。

for_save 是一个布尔值,当 True 时,表示正在执行的查询正在执行创建或更新。

get_source_expressions()

返回内部表达式的有序列表。 例如:

>>> 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 强制转换为更合适的类型。

expressionself 相同。

get_group_by_cols(alias=None)

负责通过此表达式返回列引用列表。 get_group_by_cols() 应该在任何嵌套表达式上调用。 F() 对象,特别是,持有对列的引用。 alias 参数将为 None 除非表达式已被注释并用于分组。

asc(nulls_first=False, nulls_last=False)

返回准备好按升序排序的表达式。

nulls_firstnulls_last 定义空值的排序方式。 请参阅 使用 F() 对空值进行排序 以获取示例用法。

desc(nulls_first=False, nulls_last=False)

返回准备好按降序排序的表达式。

nulls_firstnulls_last 定义空值的排序方式。 请参阅 使用 F() 对空值进行排序 以获取示例用法。

reverse_ordering()

返回 self 以及在 order_by 调用中反转排序顺序所需的任何修改。 例如,实现 NULLS LAST 的表达式会将其值更改为 NULLS FIRST。 只有实现排序顺序的表达式(如 OrderBy)才需要修改。 当在查询集上调用 reverse() 时调用此方法。


编写自己的查询表达式

您可以编写自己的查询表达式类,这些类使用并可以与其他查询表达式集成。 让我们通过编写 COALESCE SQL 函数的实现来逐步完成一个示例,而不使用内置的 Func() 表达式

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() 关键字参数进行自定义比在 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

让我们看看它是如何工作的:

>>> 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

避免 SQL 注入

由于 __init__() (**extra) 和 as_sql() (**extra_context) 的 Func 的关键字参数被插入到 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 语法,您可以通过将新方法添加到函数的类上来添加对它的支持。

假设我们正在为 Microsoft 的 SQL Server 编写后端,它使用 SQL LEN 而不是 Length 函数的 LENGTH。 我们将在 Length 类上添加一个名为 as_sqlserver() 的新方法:

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文件或从顶级[导入的顶级expressions.py文件(或包)中注册其功能X191X]。

对于希望修补他们正在使用的后端的用户项目,此代码应位于 AppConfig.ready() 方法中。