• 3.2
  • 5.0
  • 6.1
  • Версия документации: 3.1

Выражения запроса

Выражения запроса описывают значение или вычисление, которое можно использовать как часть обновления, создания, фильтрации, упорядочивания, аннотации или агрегирования. Когда выражение выводит логическое значение, его можно использовать непосредственно в фильтрах. Существует ряд встроенных выражений (описанных ниже), которые можно использовать для написания запросов. Выражения можно комбинировать или в некоторых случаях вкладывать в более сложные вычисления.

Поддерживаемая арифметика

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.expressions и django.db.models.aggregates, но для удобства они доступны и обычно импортируются из django.db.models.

Выражения F()

class F

An F() object represents the 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 никогда не узнает об этом - оно полностью обрабатывается базой данных. Все, что Python делает с помощью класса F() в Django, — это создает синтаксис SQL для обращения к полю и описания операции.

Чтобы получить доступ к новому значению, сохраненному таким образом, объект необходимо перезагрузить:

reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()

Помимо использования в операциях с отдельными экземплярами, как указано выше, F() можно использовать с QuerySets экземпляров объекта с помощью update(). Это сокращает два запроса, которые мы использовали выше - get() и :meth:~Model.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, выполнять работу

  • уменьшение количества запросов, которые требуются для некоторых операций

Как избежать состояний гонки с помощью 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'))

If the fields that you’re combining are of different types you’ll need to tell Django what kind of field will be returned. Since F() does not directly support output_field 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() и аргумент ключевого слова nulls_first или nulls_last для Expression.asc() или desc() для управления порядком нулевых значений поля. По умолчанию порядок зависит от вашей базы данных.

Например, чтобы отсортировать компании, с которыми не связались («last_contacted» имеет значение null) после компаний, с которыми связались:

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

Выражения 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"

См. Функции базы данных для получения списка встроенных функций базы данных.

API Func выглядит следующим образом:

class Func(*expressions, **extra)
function

Атрибут класса, описывающий создаваемую функцию. В частности, функция будет интерполирована как заполнитель функции в template. По умолчанию установлено значение «Нет».

template

Атрибут класса в виде строки формата, описывающий SQL, созданный для этой функции. По умолчанию используется '%(function)s(%(expressions)s)'.

Если вы создаете SQL типа strftime('%W', 'date') и вам нужен буквальный символ % в запросе, увеличьте его в четыре раза (%%%%) в атрибуте template, поскольку строка интерполируется дважды: один раз во время интерполяции шаблона в as_sql() и один раз во время интерполяции SQL с параметрами запроса в курсоре базы данных.

arg_joiner

Атрибут класса, обозначающий символ, используемый для объединения списка «выражений». По умолчанию ', '.

arity

Атрибут класса, обозначающий количество аргументов, которые принимает функция. Если этот атрибут установлен и функция вызывается с другим количеством выражений, будет выдана ошибка TypeError. По умолчанию установлено значение «Нет».

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 по мере необходимости. Например:

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, а затем интерполированы в шаблон в качестве заполнителя для выражений.

Позиционные аргументы могут быть выражениями или значениями Python. Предполагается, что строки являются ссылками на столбцы и будут заключены в выражения F(), тогда как другие значения будут заключены в выражения Value().

**дополнительные kwargs представляют собой пары ключ=значение, которые можно интерполировать в атрибут 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().

Поскольку Агрегаты являются выражениями и выражениями переноса, вы можете представлять некоторые сложные вычисления:

from django.db.models import Count

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

API Aggregate выглядит следующим образом:

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

Атрибут класса в виде строки формата, описывающий SQL, созданный для этого агрегата. По умолчанию используется '%(function)s(%(distinct)s%(expressions)s)'.

function

Атрибут класса, описывающий агрегатную функцию, которая будет сгенерирована. В частности, функция будет интерполирована как заполнитель функции в template. По умолчанию установлено значение «Нет».

window_compatible

По умолчанию установлено значение True, поскольку большинство агрегатных функций можно использовать в качестве исходного выражения в Window.

allow_distinct

Атрибут класса, определяющий, позволяет ли эта агрегатная функция передавать аргумент ключевого слова distinct. Если установлено значение False (по умолчанию), TypeError возникает, если передается distinct=True.

The expressions positional arguments can include expressions or the names of model fields. They will be converted to a string and used as the expressions placeholder within the template.

The output_field argument requires a model field instance, like IntegerField() or BooleanField(), into which Django will load the value after it’s retrieved from the database. Usually no arguments are needed when instantiating the model field as any arguments relating to data validation (max_length, max_digits, etc.) will not be enforced on the expression’s output value.

Note that output_field is only required when Django is unable to determine what field type the result should be. Complex expressions that mix field types should define the desired output_field. For example, adding an IntegerField() and a FloatField() together should probably have output_field=FloatField() defined.

Аргумент distinct определяет, следует ли вызывать агрегатную функцию для каждого отдельного значения выражений (или набора значений для нескольких выражений). Аргумент поддерживается только для агрегатов, у которых для allow_distinct установлено значение True.

Аргумент filter принимает объект Q, который используется для фильтрации агрегируемых строк. См. примеры использования в условной агрегации и фильтрации по аннотациям.

**дополнительные kwargs представляют собой пары ключ=значение, которые можно интерполировать в атрибут template.

Создание собственных агрегатных функций

Вы также можете создавать свои собственные агрегатные функции. Как минимум, вам необходимо определить функцию, но вы также можете полностью настроить генерируемый 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 описывает значение, которое будет включено в выражение, например 1, True или None. Django знает, как преобразовать эти значения Python в соответствующий тип базы данных.

The output_field argument should be a model field instance, like IntegerField() or BooleanField(), into which Django will load the value after it’s retrieved from the database. Usually no arguments are needed when instantiating the model field as any arguments relating to data validation (max_length, max_digits, etc.) will not be enforced on the expression’s output value.

Выражения ExpressionWrapper()

class 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() с аннотациями.

Условные выражения

Условные выражения позволяют использовать в запросах логику ifelifelse. Django изначально поддерживает выражения SQL CASE. Для получения более подробной информации см. условные-выражения.

Выражения Subquery()

class Subquery(queryset, output_field=None)

Вы можете добавить явный подзапрос в QuerySet, используя выражение Subquery.

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 выполнить подзапрос. В некоторых случаях можно написать эквивалентный набор запросов, который выполняет ту же задачу более четко и эффективно.

Ссылка на столбцы из внешнего набора запросов

class OuterRef(field)

Use OuterRef when a queryset in a Subquery needs to refer to a field from the outer query. 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() подзапросы

class 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 U0."id", U0."post_id", U0."email", U0."created_at"
    FROM "comment" U0
    WHERE (
        U0."created_at" >= YYYY-MM-DD HH:MM:SS AND
        U0."post_id" = ("post"."id")
    )
) AS "recent_comment" FROM "post"

Нет необходимости заставлять Exists ссылаться на один столбец, поскольку столбцы отбрасываются и возвращается логический результат. Аналогичным образом, поскольку порядок неважен в подзапросе SQL EXISTS и может только снизить производительность, он автоматически удаляется.

Вы можете выполнить запрос, используя NOT EXISTS с помощью ~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, что может привести к повышению производительности.

Changed in Django 3.0:

In previous versions of Django, it was necessary to first annotate and then filter against the annotation. This resulted in the annotated value always being present in the query result, and often resulted in a query that took more time to execute.

Использование агрегатов в выражении 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() удаляет стандартный ordering (если есть) в модели Comment. values('post') объединяет комментарии по Post. Наконец, annotate(…)`` выполняет агрегирование. Порядок применения этих методов набора запросов важен. В этом случае, поскольку подзапрос должен быть ограничен одним столбцом, требуется values('total').

Это единственный способ выполнить агрегацию внутри Subquery, поскольку использование aggregate() пытается оценить набор запросов (и если есть OuterRef, это будет невозможно решить).

Необработанные выражения SQL

class 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", (someparam,)))

Эти дополнительные поиски могут быть непереносимы в другие механизмы баз данных (поскольку вы явно пишете код SQL) и нарушают принцип DRY, поэтому вам следует избегать их, если это возможно.

Предупреждение

Чтобы защититься от атак SQL-инъекций <https://en.wikipedia.org/wiki/SQL_injection>`_, вы должны экранировать любые параметры, которыми пользователь может управлять, используя params. params — обязательный аргумент, заставляющий вас признать, что вы не интерполируете свой SQL с данными, предоставленными пользователем.

Вы также не должны заключать в кавычки заполнители в строке SQL. Этот пример уязвим для SQL-инъекций из-за кавычек вокруг %s:

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

Вы можете узнать больше о том, как работает Защита от SQL-инъекций в Django.

Оконные функции

Оконные функции позволяют применять функции к разделам. В отличие от обычной функции агрегирования, которая вычисляет окончательный результат для каждого набора, определенного группой by, оконные функции работают с frames и разделами и вычисляют результат для каждой строки.

Вы можете указать несколько окон в одном запросе, что в Django ORM будет эквивалентно включению нескольких выражений в вызов QuerySet.annotate(). ORM не использует именованные окна, вместо этого они являются частью выбранных столбцов.

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

Defaults to False. The SQL standard disallows referencing window functions in the WHERE clause and Django raises an exception when constructing a QuerySet that would do that.

template

Defaults to %(expression)s OVER (%(window)s)'. If only the expression argument is provided, the window clause will be blank.

Класс Window является основным выражением предложения OVER.

Аргумент expression представляет собой либо оконную функцию <window-functions>`, агрегирующую функцию, либо выражение, совместимое с предложением окна.

The partition_by argument is a list 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 a sequence of expressions on which you can call asc() and desc(). 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
>>> 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(),
>>>     ),
>>> )

Это позволяет вам проверить, оценивается ли фильм лучше или хуже, чем его аналоги.

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

Among Django’s built-in database backends, MySQL 8.0.2+, 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.

Рамки

Для рамки окна вы можете выбрать либо последовательность строк на основе диапазона, либо обычную последовательность строк.

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

Этот атрибут установлен в 'RANGE'.

PostgreSQL имеет ограниченную поддержку ValueRange и поддерживает только использование стандартных начальной и конечной точек, таких как CURRENT ROW и UNBOUNDED FOLLOWING.

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

Для этого атрибута установлено значение 'ROWS'.

Both classes return SQL with the template:

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

Кадры сужают строки, используемые для вычисления результата. Они перемещаются от некоторой начальной точки к некоторой заданной конечной точке. Фреймы можно использовать с разделами и без них, но зачастую полезно указать порядок расположения окон, чтобы обеспечить детерминированный результат. В кадре партнером в кадре является строка с эквивалентным значением или все строки, если предложение упорядочивания отсутствует.

Начальной точкой по умолчанию для кадра является UNBOUNDED PRECEDING, то есть первая строка раздела. Конечная точка всегда явно включается в SQL-код, генерируемый ORM, и по умолчанию имеет значение UNBOUNDED FOLLOWING. Фрейм по умолчанию включает все строки от раздела до последней строки в наборе.

The accepted values for the start and end arguments are None, an integer, or zero. A negative integer for start results in N preceding, while None yields UNBOUNDED PRECEDING. For both start and end, zero will return CURRENT ROW. Positive integers are accepted for end.

Есть разница в том, что включает в себя CURRENT ROW. Если указан режим ROWS, кадр начинается или заканчивается текущей строкой. Если указан режим RANGE, кадр начинается или заканчивается на первом или последнем одноранговом узле в соответствии с условием упорядочения. Таким образом, RANGE CURRENT ROW оценивает выражение для строк, которые имеют одинаковое значение, указанное в порядке упорядочивания. Поскольку шаблон включает в себя как начальную, так и конечную точки, это можно выразить следующим образом:

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

If the database supports it, you can specify the start and end points based on values of an expression in the partition. If the released field of the Movie model stores the release month of each movies, this ValueRange example annotates each movie with the average rating of a movie’s peers released between twelve months before and twelve months after the each movie.

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

Техническая информация

Ниже вы найдете подробности технической реализации, которые могут быть полезны авторам библиотек. Технический API и примеры, приведенные ниже, помогут создать общие выражения запросов, которые могут расширить встроенную функциональность, предоставляемую Django.

API выражений

Выражения запросов реализуют 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)

Provides the chance to do any pre-processing 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)

Перехватчик, позволяющий приводить выражение к более подходящему типу.

выражение — то же самое, что и self.

get_group_by_cols(alias=None)

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 parameter will be None unless the expression has been annotated and is used for grouping.

Changed in Django 3.0:

The alias parameter was added.

asc(nulls_first=False, nulls_last=False)

Возвращает выражение, готовое к сортировке по возрастанию.

nulls_first и nulls_last определяют, как сортируются нулевые значения. Пример использования см. в разделе Использование F() для сортировки нулевых значений.

desc(nulls_first=False, nulls_last=False)

Возвращает выражение, готовое к сортировке по убыванию.

nulls_first и nulls_last определяют, как сортируются нулевые значения. Пример использования см. в разделе Использование F() для сортировки нулевых значений.

reverse_ordering()

Возвращает self со всеми изменениями, необходимыми для изменения порядка сортировки в вызове order_by. Например, выражение, реализующее NULLS LAST, изменит свое значение на NULLS FIRST. Изменения требуются только для выражений, которые реализуют порядок сортировки, например OrderBy. Этот метод вызывается, когда reverse() вызывается для набора запросов.

Написание собственных выражений запроса

Вы можете написать свои собственные классы выражений запросов, которые используют другие выражения запросов и могут интегрироваться с ними. Давайте рассмотрим пример, написав реализацию SQL-функции COALESCE без использования встроенных Func() выражений.

Функция SQL COALESCE определяется как получение списка столбцов или значений. Он вернет первый столбец или значение, отличное от 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 pre-processing 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() для настройки предпочтительнее, чем изменение self в методах as_vendorname(), поскольку последнее может привести к ошибкам при запуске на разных базах данных. Если ваш класс использует атрибуты класса для определения данных, рассмотрите возможность переопределения в вашем методе as_sql().

Мы генерируем SQL для каждого из выражений с помощью метода 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

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

Как избежать SQL-инъекций

Поскольку аргументы ключевого слова 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 без каких-либо параметров. Поскольку подстрока передается в super().__init__() в качестве аргумента ключевого слова, она интерполируется в строку SQL перед отправкой запроса в базу данных.

Вот исправленная перезапись:

class Position(Func):
    function = 'POSITION'
    arg_joiner = ' IN '

    def __init__(self, expression, substring):
        super().__init__(substring, expression)

Если вместо этого передать подстроку в качестве позиционного аргумента, она будет передана в качестве параметра в запросе к базе данных.

Добавление поддержки в сторонние базы данных

Если вы используете серверную часть базы данных, которая использует другой синтаксис SQL для определенной функции, вы можете добавить ее поддержку, внеся новый метод в класс функции.

Допустим, мы пишем серверную часть для Microsoft 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

Вы также можете настроить SQL, используя параметр шаблона функции as_sql().

Мы используем as_sqlserver(), потому что django.db.connection.vendor возвращает sqlserver для бэкэнда.

Сторонние серверные программы могут регистрировать свои функции в файле верхнего уровня __init__.py внутреннего пакета или в файле верхнего уровня expressions.py (или пакете), который импортируется из верхнего уровня __init__.py.

Для пользовательских проектов, желающих исправить используемую ими серверную часть, этот код должен находиться в методе AppConfig.ready().

Back to Top