查询表达式

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

支持的算术

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.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永远不会知道它——它完全由数据库处理。所有的 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值)根据条件过滤它们。

记录在 using F() expressions in queries .

使用 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()
    )
)

在引用关系字段时,如 ForeignKeyF() 返回主键值,而不是模型实例:

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

vbl.使用 F() 使用逻辑运算

F() 输出以下内容的表达式 BooleanField 可以使用逆运算符在逻辑上求反 ~F() 。例如,要交换公司的激活状态:

from django.db.models import F

Company.objects.update(is_active=~F("is_active"))

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 中的占位符 template .默认为 None .

template

类属性,作为格式字符串,用于描述为此函数生成的SQL。默认为 '%(function)s(%(expressions)s)' .

如果您正在构建类似SQL的 strftime('%W', 'date') 需要文字 % 查询中的字符,四倍 (%%%%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 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字符串中的,而不是作为查询参数传递的,所以数据库驱动程序将在其中对它们进行转义。

这个 functiontemplate ,以及 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如下:

class Aggregate(*expressions, output_field=None, distinct=False, filter=None, default=None, **extra)[源代码]
template

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

function

描述将生成的聚合函数的类属性。具体来说, function 将被插入为 function 中的占位符 template .默认为 None .

window_compatible

默认为 True 因为大多数聚合函数都可以用作 Window .

allow_distinct

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

empty_result_set_value

默认为 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() 表达

class Value(value, output_field=None)[源代码]

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

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

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

如果没有 output_field 是指定的,则将从提供的 value 对于许多常见类型。例如,传递 datetime.datetime AS value 默认设置 output_fieldDateTimeField

ExpressionWrapper() 表达

class ExpressionWrapper(expression, output_field)[源代码]

ExpressionWrapper 将另一个表达式括起来并提供对属性的访问,例如 output_field ,这在其他表达式中可能不可用。 ExpressionWrapper 在使用算术运算时是必需的 F() 不同类型的表达式,如中所述 使用 F() 带批注

条件表达式

条件表达式允许您使用 ifelifelse 查询中的逻辑。Django本机支持SQL CASE 表达。有关详细信息,请参阅 条件表达式 .

Subquery() 表达

class Subquery(queryset, output_field=None)[源代码]

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

引用外部查询集中的列

class OuterRef(field)[源代码]

使用 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() 子查询

class Exists(queryset)[源代码]

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

A上的滤波 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 ,这将无法解决)。

原始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 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不使用命名窗口,而是作为所选列的一部分。

class Window(expression, partition_by=None, order_by=None, frame=None, output_field=None)[源代码]
template

默认为 %(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,
...     ),
... )

只要查找不是析取的(不使用 ORXOR 作为连接器),并且针对执行聚集的查询集。

例如,依赖于聚合的查询具有 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() 可能不受支持。根据需要参考您的数据库的文档。

框架

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

class ValueRange(start=None, end=None, exclusion=None)[源代码]
frame_type

此属性设置为 'RANGE' .

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

Changed in Django Development version:

这个 exclusion 添加了参数。

class RowRange(start=None, end=None, exclusion=None)[源代码]
frame_type

此属性设置为 'ROWS' .

Changed in Django Development version:

这个 exclusion 添加了参数。

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

%(frame_type)s BETWEEN %(start)s AND %(end)s
class WindowFrameExclusion[源代码]
New in Django Development version.
CURRENT_ROW
GROUP
TIES
NO_OTHERS

这个 exclusion 参数允许排除行 (CURRENT_ROW )、群组 (GROUP ),和领带 (TIES )从支持的数据库上的窗口框架:

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

帧缩小了用于计算结果的行。它们从某个起点移动到某个特定的终点。帧可以与分区一起使用,也可以不与分区一起使用,但通常最好指定窗口的顺序以确保确定的结果。在帧中,帧中的对等点是具有等效值的行,如果没有排序子句,则是所有行。

帧的默认起始点是 UNBOUNDED PRECEDING 这是分区的第一行。端点总是显式包含在ORM生成的SQL中,并且在默认情况下是 UNBOUNDED FOLLOWING . 默认框架包括从分区到集合中最后一行的所有行。

属性的接受值 startend 论据是 None 、整数或零。的负整数 start 在.中的结果 N PRECEDING ,而 None 收益率 UNBOUNDED PRECEDING 。在……里面 ROWS 模式下,正整数可用于 `start 导致 N FOLLOWING 。正整数可用于 end 并导致 N FOLLOWING 。在……里面 ROWS 模式下,负整数可用于 `end 导致 N PRECEDING 。对两个人都是 startend ,零将返回 CURRENT ROW

在什么方面有区别 CURRENT ROW 包括。当在中指定时 ROWS 模式,帧以当前行开始或结束。当在中指定时 RANGE 模式,帧根据排序子句在第一个或最后一个对等点开始或结束。因此, RANGE CURRENT ROW 对具有由排序指定的相同值的行计算表达式。因为模板包括 startend 点,这可以表示为:

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),
...     ),
... )
Changed in Django Development version:

支持正整数 start 和负整数 end 已为以下对象添加 RowRange

技术信息

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

表达式API

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

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

class Expression[源代码]
allowed_default
New in Django 5.0.

告诉Django这个表达式可以用在 Field.db_default 。默认为 False

contains_aggregate

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

contains_over_clause

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

filterable

告诉Django可以在 QuerySet.filter() .默认为 True .

window_compatible

告诉Django此表达式可以用作中的源表达式 Window .默认为 False .

empty_result_set_value

告诉Django在使用表达式对空结果集应用函数时应返回哪个值。默认为 NotImplemented 这将强制在数据库上计算表达式。

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

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

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 更合适的类型。

expression 是一样的 self .

get_group_by_cols()

负责返回此表达式引用的列的列表。 get_group_by_cols() 应在任何嵌套表达式上调用。 F() 具体而言,对象包含对列的引用。

asc(nulls_first=None, nulls_last=None)

返回可按升序排序的表达式。

nulls_firstnulls_last 定义空值的排序方式。见 使用 F() 对空值排序 例如用法。

desc(nulls_first=None, nulls_last=None)

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

nulls_firstnulls_last 定义空值的排序方式。见 使用 F() 对空值排序 例如用法。

reverse_ordering()

返回 selforder_by 调用。例如,表达式实现 NULLS LAST 将其值更改为 NULLS FIRST . 只有实现类似于 OrderBy . 当 reverse() 对查询集调用。

编写自己的查询表达式

您可以编写自己的查询表达式类,这些类使用并可以与其他查询表达式集成。让我们通过编写 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

避免SQL注入

自从A Func 的关键字参数 __init__() (**extraas_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() 方法。