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

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

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

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

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

Поле вывода

Многие из выражений, описанных в этом разделе, поддерживают необязательный параметр «output_field». Если указано, Django загрузит значение в это поле после получения его из базы данных.

output_field принимает экземпляр поля модели, например IntegerField() или BooleanField(). Обычно поле не нуждается в каких-либо аргументах, таких как max_length, поскольку аргументы поля относятся к проверке данных, которая не будет выполняться для выходного значения выражения.

output_field требуется только тогда, когда Django не может автоматически определить тип поля результата, например, сложные выражения, которые смешивают типы полей. Например, для добавления DecimalField() и FloatField() требуется поле вывода, например, output_field=FloatField().

output_field также позволяет использовать пользовательские поля, которые выполняют преобразования типов вне контекста определенного поля модели. Например, если вам часто приходится выполнять арифметические действия с датами с помощью timedelta, вы можете создать настраиваемое поле, которое обрабатывает преобразование, обеспечивая согласованность результатов во всех базах данных. См. Создание собственных полей для модели.

Некоторые примеры

>>> 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")))
>>> company.ticker
'GOOG'

# Annotate models with an aggregated value. Both forms
# below are equivalent.
>>> Company.objects.annotate(num_products=Count("products"))
>>> Company.objects.annotate(num_products=Count(F("products")))

# Aggregates can contain complex computations also
>>> Company.objects.annotate(num_offerings=Count(F("products") + F("services")))

# Expressions can also be used in order_by(), either directly
>>> Company.objects.order_by(Length("name").asc())
>>> Company.objects.order_by(Length("name").desc())
# or using the double underscore lookup syntax.
>>> from django.db.models import CharField
>>> from django.db.models.functions import Length
>>> CharField.register_lookup(Length)
>>> Company.objects.order_by("name__length")

# Boolean expression can be used directly in filters.
>>> from django.db.models import Exists, OuterRef
>>> Company.objects.filter(
...     Exists(Employee.objects.filter(company=OuterRef("pk"), salary__gt=10))
... )

# Lookup expressions can also be used directly in filters
>>> Company.objects.filter(GreaterThan(F("num_employees"), F("num_chairs")))
# or annotations.
>>> Company.objects.annotate(
...     need_chairs=GreaterThan(F("num_employees"), F("num_chairs")),
... )

Встроенные выражения

Примечание

Эти выражения определены в django.db.models.expressions и django.db.models.aggregates, но для удобства они доступны и обычно импортируются из django.db.models.

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

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 делает с помощью класса F() в Django, — это создает синтаксис SQL для обращения к полю и описания операции.

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

Для строковых полей, текстовых полей и ArrayField вы можете использовать синтаксис разбиения массива Python. Индексы отсчитываются от 0. Аргумент step для slice и отрицательная индексация не поддерживаются. Например:

>>> # Replacing a name with a substring of itself.
>>> writer = Writers.objects.get(name="Priyansh")
>>> writer.name = F("name")[1:5]
>>> writer.save()
>>> writer.name
'riya'

Как избежать состояний гонки с помощью F()

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

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

Если за обновление поля отвечает база данных, процесс становится более надежным: он будет обновлять поле только на основе значения поля в базе данных, когда выполняется save() или update(), а не на основе его значения при получении экземпляра.

Назначения F() обновляются после Model.save()

Объекты F(), назначенные полям модели, обновляются из базы данных в save() на бэкэндах, которые поддерживают его, без выполнения последующего запроса (SQLite, PostgreSQL и Oracle) и откладываются в противном случае (MySQL или MariaDB). Например:

>>> reporter = Reporters.objects.get(name="Tintin")
>>> reporter.stories_filed = F("stories_filed") + 1
>>> reporter.save()
>>> reporter.stories_filed  # This triggers a refresh query on MySQL/MariaDB.
14  # Assuming the database value was 13 when the object was saved.
Changed in Django 6.0:

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

Использование F() в фильтрах

F() также очень полезен в фильтрах QuerySet, где они позволяют фильтровать набор объектов по критериям, основанным на значениях их полей, а не на значениях Python.

Это описано в использовании выражений F() в запросах.

Использование F() с аннотациями

F() можно использовать для создания динамических полей в ваших моделях путем объединения различных полей с арифметическими операциями:

company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))

Если объединяемые вами поля относятся к разным типам, вам нужно будет указать Django, какой тип поля будет возвращен. Большинство выражений поддерживают output_field для этого случая, но поскольку F() не поддерживает, вам нужно будет обернуть выражение ExpressionWrapper:

from django.db.models import DateTimeField, ExpressionWrapper, F

Ticket.objects.annotate(
    expires=ExpressionWrapper(
        F("active_at") + F("duration"), output_field=DateTimeField()
    )
)

При ссылке на реляционные поля, такие как ForeignKey, F() возвращает значение первичного ключа, а не экземпляр модели:

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

Использование F() для сортировки нулевых значений

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

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

from django.db.models import F

Company.objects.order_by(F("last_contacted").desc(nulls_last=True))

Использование F() с логическими операциями

Выражения F(), выводящие BooleanField, могут быть логически инвертированы с помощью оператора инверсии ~F(). Например, чтобы поменять статус активации компаний:

from django.db.models import F

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

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

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

from django.db.models import F, Func

queryset.annotate(field_lower=Func(F("field"), function="LOWER"))

или их можно использовать для создания библиотеки функций базы данных:

class Lower(Func):
    function = "LOWER"


queryset.annotate(field_lower=Lower("field"))

Но в обоих случаях результатом будет набор запросов, в котором каждая модель будет аннотирована дополнительным атрибутом field_lower, полученным примерно из следующего SQL:

SELECT
    ...
    LOWER("db_table"."field") as "field_lower"

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

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

Ключевые слова function, template и arg_joiner можно использовать для замены одноименных атрибутов без необходимости определения собственного класса. output_field можно использовать для определения ожидаемого типа возвращаемого значения.

Очистка входных данных, используемых для настройки выражения запроса

Встроенные функции базы данных (такие как Cast) различаются в зависимости от того, могут ли аргументы, такие как output_field, передаваться позиционно или только по ключевому слову. Для output_field и некоторых других случаев ввод в конечном итоге достигает Func() в качестве аргумента ключевого слова, поэтому совет избегать создания аргументов ключевого слова из ненадежного пользовательского ввода применим как к этим аргументам, так и к **extra.

Выражения 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, default=None, order_by=None, **extra)
template

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

function

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

window_compatible

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

allow_distinct

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

allow_order_by
New in Django 6.0.

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

empty_result_set_value

По умолчанию установлено значение None, поскольку большинство агрегатных функций возвращают значение NULL при применении к пустому набору результатов.

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

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

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

Аргумент order_by ведет себя аналогично входным данным field_names функции order_by(), принимая имя поля (с необязательным префиксом -', который указывает порядок убывания) или выражение (или кортеж или список строк и/или выражений), которое определяет порядок элементов в результате.

Аргумент default принимает значение, которое будет передано вместе с агрегатом в Coalesce. Это полезно для указания возвращаемого значения, отличного от None, когда набор запросов (или группа) не содержит записей.

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

Changed in Django 6.0:

Был добавлен аргумент order_by.

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

Вы также можете создавать свои собственные агрегатные функции. Как минимум, вам необходимо определить функцию, но вы также можете полностью настроить генерируемый 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
    arity = 1

    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 в соответствующий тип базы данных.

Если output_field не указано, оно будет выведено из типа предоставленного value для многих распространенных типов. Например, передача экземпляра datetime.datetime в качестве value по умолчанию устанавливает output_field в DateTimeField.

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

class ExpressionWrapper(expression, output_field)

ExpressionWrapper окружает другое выражение и обеспечивает доступ к свойствам, таким как output_field, которые могут быть недоступны в других выражениях. ExpressionWrapper необходим при использовании арифметических операций с выражениями F() разных типов, как описано в Использование F() с аннотациями.

Приведение базы данных не выполнено

ExpressionWrapper только устанавливает поле вывода для ORM и не выполняет никакого приведения на уровне базы данных. Чтобы гарантировать, что из базы данных возвращается определенный тип, используйте вместо этого Cast.

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

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

Экземпляры OuterRef могут использоваться в сочетании с вложенными экземплярами Subquery для ссылки на содержащий набор запросов, который не является непосредственным родителем. Например, для правильного разрешения этот набор запросов должен находиться внутри пары вложенных экземпляров Subquery:

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

Ограничение подзапроса одним столбцом

Бывают случаи, когда один столбец должен быть возвращен из «Подзапроса», например, чтобы использовать «Подзапрос» в качестве цели поиска «__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() вместо среза приведет к сбою, поскольку 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().

Фильтрация по выражениям Subquery() или Exists()

Subquery(), который возвращает логическое значение, а Exists() можно использовать как условие в выражениях When или для прямой фильтрации набора запросов:

>>> recent_comments = Comment.objects.filter(...)  # From above
>>> Post.objects.filter(Exists(recent_comments))

Это гарантирует, что подзапрос не будет добавлен в столбцы SELECT, что может привести к повышению производительности.

Использование агрегатов в выражении Subquery

Агрегаты могут использоваться в Subquery, но они требуют определенной комбинации filter(), values() и annotate() для правильной группировки подзапроса.

Предположим, что обе модели имеют поле «длина», чтобы найти сообщения, длина которых превышает общую длину всех объединенных комментариев:

>>> 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-инъекций <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)
template

По умолчанию используется %(expression)s OVER (%(window)s). Если указан только аргумент «выражение», предложение окна будет пустым.

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

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

Аргумент partition_by принимает выражение или последовательность выражений (имена столбцов должны быть заключены в F-объект), которые управляют разделением строк. Секционирование сужает список строк, используемых для вычисления набора результатов.

output_field указывается либо как аргумент, либо как выражение.

Аргумент order_by принимает выражение, для которого вы можете вызвать asc() и desc(), строку имени поля (с необязательным префиксом "-", который указывает порядок убывания), или кортеж или список строк и/или выражений. Порядок определяет порядок применения выражения. Например, если вы суммируете строки в разделе, первый результат — это значение первой строки, второй — сумма первой и второй строк.

Параметр «frame» указывает, какие еще строки следует использовать в вычислении. Подробнее см. Рамки.

Например, чтобы аннотировать каждый фильм средним рейтингом фильмов одной студии в том же жанре и году выпуска:

>>> from django.db.models import Avg, F, Window
>>> Movie.objects.annotate(
...     avg_rating=Window(
...         expression=Avg("rating"),
...         partition_by=[F("studio"), F("genre")],
...         order_by="released__year",
...     ),
... )

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

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

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

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

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

>>> 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 ROW и UNBOUNDED FOLLOWING.

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

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

Оба класса возвращают SQL с шаблоном:

%(frame_type)s BETWEEN %(start)s AND %(end)s
class WindowFrameExclusion
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, то есть первая строка раздела. Конечная точка всегда явно включается в SQL-код, генерируемый ORM, и по умолчанию имеет значение UNBOUNDED FOLLOWING. Фрейм по умолчанию включает все строки от раздела до последней строки в наборе.

Принимаемые значения для аргументов start и end: None, целое число или ноль. Отрицательное целое число для start приводит к N PRECEDING, а None дает UNBOUNDED PRECEDING. В режиме ROWS для параметра start можно использовать положительное целое число, в результате чего получается N FOLLOWING. Положительные целые числа принимаются в качестве end и приводят к N FOLLOWING. В режиме ROWS отрицательное целое число может использоваться для end, что приводит к N PRECEDING. Как для start, так и для end ноль вернет CURRENT ROW.

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

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 хранит месяц выпуска каждого фильма, этот пример ValueRange аннотирует каждый фильм средним рейтингом аналогов фильма, выпущенных между двенадцатью месяцами до и двенадцатью месяцами после каждого фильма:

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

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

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

API выражений

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

Когда выражение запроса обертывает другое выражение, оно отвечает за вызов соответствующих методов обернутого выражения.

class Expression
allowed_default

Сообщает Django, что это выражение можно использовать в Field.db_default. По умолчанию установлено значение «False».

constraint_validation_compatible

Сообщает Django, что это выражение можно использовать во время проверки ограничения. Выражения с constraint_validation_совместимым, установленным в False, должны иметь только одно исходное выражение. По умолчанию установлено True.

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, что заставляет выражение вычисляться в базе данных.

set_returning
New in Django 5.2.

Сообщает Django, что это выражение содержит функцию, возвращающую множество, обеспечивающую оценку подзапроса. Он используется, например, чтобы позволить некоторым функциям Postgres, возвращающим наборы (например, JSONB_PATH_QUERY, UNNEST и т. д.), пропускать оптимизацию и правильно оцениваться, когда аннотации сами порождают строки. По умолчанию установлено значение «False».

allows_composite_expressions
New in Django 5.2.

Сообщает Django, что это выражение позволяет, например, составным выражениям поддерживать составные первичные ключи. По умолчанию установлено значение «False».

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)

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

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

get_group_by_cols()

Отвечает за возврат списка ссылок на столбцы по этому выражению. get_group_by_cols() следует вызывать для любых вложенных выражений. Объекты F(), в частности, содержат ссылку на столбец.

asc(nulls_first=None, nulls_last=None)

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

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

desc(nulls_first=None, nulls_last=None)

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

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

reverse_ordering()

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

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

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

Функция SQL COALESCE определяется как получение списка столбцов или значений. Он вернет первый столбец или значение, отличное от NULL.

Мы начнем с определения шаблона, который будет использоваться для генерации SQL, и метода __init__() для установки некоторых атрибутов:

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

Мы выполняем базовую проверку параметров, в том числе требуем наличие как минимум двух столбцов или значений и проверяем, являются ли они выражениями. Нам требуется 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, tuple(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

Давайте посмотрим, как это работает:

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