Выражения запросов
Выражения запросов описывают значение или вычисление, которое может быть использовано в качестве части обновления, создания, фильтрации, сортировки, аннотации или агрегации. Когда выражение возвращает булево значение, оно может быть использовано непосредственно в фильтрах. Существует ряд встроенных выражений (описанных ниже), которые могут помочь вам составить запросы. Выражения могут быть объединены или, в некоторых случаях, вложены, чтобы сформировать более сложные вычисления.
Поддерживаемые арифметические операции
Django поддерживает отрицание, сложение, вычитание, умножение, деление, остаток от деления и оператор возведения в степень для выражений запросов, используя константы Python, переменные и даже другие выражения.
Поле вывода
Многие из выражений, описанных в этом разделе, поддерживают необязательный output_field параметр. Если он задан, Django загрузит значение в это поле после извлечения его из базы данных.
output_field принимает экземпляр поля модели, например IntegerField() или BooleanField(). Обычно поле не требует аргументов, как max_length, так как аргументы поля относятся к валидации данных, которая не будет выполняться для выходного значения выражения.
output_field требуется только тогда, когда Django не может автоматически определить тип поля результата, например, в сложных выражениях, объединяющих типы полей. Например, сложение DecimalField() и FloatField() требует поля вывода, например output_field=FloatField().
Примеры
>>> from django.db.models import Count, F, Value
>>> from django.db.models.functions import Length, Upper
>>> from django.db.models.lookups import GreaterThan
# Find companies that have more employees than chairs.
>>> Company.objects.filter(num_employees__gt=F("num_chairs"))
# Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent.
>>> Company.objects.filter(num_employees__gt=F("num_chairs") * 2)
>>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs"))
# How many chairs are needed for each company to seat all employees?
>>> company = (
... Company.objects.filter(num_employees__gt=F("num_chairs"))
... .annotate(chairs_needed=F("num_employees") - F("num_chairs"))
... .first()
... )
>>> company.num_employees
120
>>> company.num_chairs
50
>>> company.chairs_needed
70
# Create a new company using expressions.
>>> company = Company.objects.create(name="Google", ticker=Upper(Value("goog")))
# Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db()
>>> company.ticker
'GOOG'
# Annotate models with an aggregated value. Both forms
# below are equivalent.
>>> Company.objects.annotate(num_products=Count("products"))
>>> Company.objects.annotate(num_products=Count(F("products")))
# Aggregates can contain complex computations also
>>> Company.objects.annotate(num_offerings=Count(F("products") + F("services")))
# Expressions can also be used in order_by(), either directly
>>> Company.objects.order_by(Length("name").asc())
>>> Company.objects.order_by(Length("name").desc())
# or using the double underscore lookup syntax.
>>> from django.db.models import CharField
>>> from django.db.models.functions import Length
>>> CharField.register_lookup(Length)
>>> Company.objects.order_by("name__length")
# Boolean expression can be used directly in filters.
>>> from django.db.models import Exists, OuterRef
>>> Company.objects.filter(
... Exists(Employee.objects.filter(company=OuterRef("pk"), salary__gt=10))
... )
# Lookup expressions can also be used directly in filters
>>> Company.objects.filter(GreaterThan(F("num_employees"), F("num_chairs")))
# or annotations.
>>> Company.objects.annotate(
... need_chairs=GreaterThan(F("num_employees"), F("num_chairs")),
... )
Встроенные выражения
Примечание
Эти выражения определены в django.db.models.expressions и django.db.models.aggregates, но для удобства они доступны и обычно импортируются из django.db.models.
F() выражения
-
class F[source]
Объект 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 выглядит как обычная присваивание значения атрибуту экземпляра, на самом деле это 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() и 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.refresh_from_db()
>>> writer.name
'riya'
Избегание гонок с использованием F()
Ещё одним полезным преимуществом F() является то, что обновление значения поля базой данных, а не Python, избегает гонки условий.
Если два потока Python выполняют код в первом примере выше, один поток может извлечь, нарастить и сохранить значение поля после того, как другой извлёк его из базы данных. Значение, которое сохранит второй поток, будет основано на исходном значении; работа первого потока будет утеряна.
Если база данных отвечает за обновление поля, процесс более надёжен: он будет обновлять поле только на основе значения поля в базе данных, когда выполняется save() или update(), а не на его значении при извлечении экземпляра.
F() присвоения сохраняются после Model.save()
F() объекты, присвоенные полям модели, сохраняются после сохранения экземпляра модели и будут применяться при каждом save(). Например:
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
reporter.name = "Tintin Jr."
reporter.save()
stories_filed будет обновлено дважды в этом случае. Если оно изначально 1, окончательное значение будет 3. Это сохранение можно избежать, перезагрузив объект модели после сохранения, например, с помощью refresh_from_db().
Использование F() в фильтрах
F() также очень полезно в QuerySet фильтрах, где они позволяют фильтровать набор объектов по критериям, основанным на значениях их полей, а не на значениях Python.
Это описано в использование выражений F() в запросах.
Использование F() с аннотациями
F() может использоваться для создания динамических полей в ваших моделях путём комбинирования различных полей с арифметикой:
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
Если поля, которые вы объединяете, имеют разные типы, вам нужно указать Django, какой тип поля будет возвращён. Большинство выражений поддерживают поле вывода для этого случая, но поскольку F() этого не делает, вам нужно обернуть выражение в 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() для сортировки значений NULL
Используйте F() и nulls_first или nulls_last ключевой аргумент к Expression.asc() или desc(), чтобы управлять порядком значений NULL поля. По умолчанию порядок зависит от вашей базы данных.
Например, чтобы отсортировать компании, с которыми не контактировали (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)[source] -
-
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)[source] -
Генерирует фрагмент SQL для функции базы данных. Возвращает кортеж
(sql, params), гдеsql— строка SQL, аparams— список или кортеж параметров запроса.Методы
as_vendor()должны использоватьfunction,template,arg_joiner, и любые другие параметры**extra_context, чтобы настроить SQL по мере необходимости. Например:django/db/models/functions.pyclass 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, а затем интерполированы в template в качестве заполнителя expressions.
Позиционные аргументы могут быть выражениями или значениями Python. Строки предполагаются ссылками на столбцы и будут заключены в F() выражения, а другие значения будут заключены в Value() выражения.
Ключевые слова **extra — пары key=value, которые могут быть интерполированы в атрибут template. Чтобы избежать уязвимости SQL-инъекции, extra не должно содержать ненадёжные данные пользователя, так как эти значения интерполируются в строку SQL, а не передаются в качестве параметров запроса, где драйвер базы данных их экранировал бы.
Ключевые слова function, template, и arg_joiner могут использоваться для замены атрибутов с таким же именем, не прибегая к определению собственного класса. output_field может быть использован для определения ожидаемого типа возвращаемого значения.
Aggregate() выражения
Агрегирующее выражение — это особый случай выражения Func() expression, который сообщает запросу, что требуется GROUP BY-клауза. Все агрегационные функции, такие как Sum() и Count(), наследуют от Aggregate().
Поскольку 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, **extra)[source] -
-
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)[source]
Объект 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)[source]
ExpressionWrapper окружает другое выражение и предоставляет доступ к свойствам, таким как output_field, которые могут быть недоступны для других выражений. ExpressionWrapper необходимо при использовании арифметических операций над F() выражениями с разными типами, как описано в Использование F() с аннотациями.
Условные выражения
Условные выражения позволяют использовать логику if … elif … else в запросах. Django нативно поддерживает SQL CASE выражения. Более подробная информация приведена в Условные выражения.
Subquery() выражения
-
class Subquery(queryset, output_field=None)[source]
Вы можете добавить явное подзапрос к 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)[source]
Используйте OuterRef , когда в наборе результатов Subquery необходимо обратиться к полю из внешнего набора результатов или его преобразования. Он действует как выражение F, за исключением того, что проверка на то, ссылается ли он на допустимое поле, выполняется не до тех пор, пока не будет решен внешний набор результатов.
Примеры OuterRef могут быть использованы совместно с вложенными примерами Subquery для обращения к содержащему набору результатов, который не является непосредственным родителем. Например, этот набор результатов должен находиться внутри вложенной пары Subquery элементов, чтобы корректно разрешиться:
>>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
Ограничение подзапроса одним столбцом
Иногда из подзапроса требуется вернуть только один столбец, например, чтобы использовать его как целевой объект в поиске Subquery. Чтобы получить все комментарии к постам, опубликованным в течение последнего дня:
>>> 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)[source]
Exists — это подкласс Subquery, который использует оператор SQL EXISTS . В многих случаях он будет работать лучше, чем подзапрос, так как база данных может остановить обработку подзапроса, когда найдена первая подходящая строка.
Например, чтобы добавить к каждому посту информацию о том, есть ли у него комментарий за последний день:
>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef("pk"),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
В PostgreSQL SQL выглядит так:
SELECT "post"."id", "post"."published_at", EXISTS(
SELECT (1) as "a"
FROM "comment" U0
WHERE (
U0."created_at" >= YYYY-MM-DD HH:MM:SS AND
U0."post_id" = "post"."id"
)
LIMIT 1
) AS "recent_comment" FROM "post"
Не нужно принудительно заставлять Exists ссылаться на один столбец, поскольку столбцы отбрасываются, и возвращается булевый результат. Аналогично, поскольку порядок не имеет значения внутри подзапроса SQL EXISTS, и это только ухудшит производительность, он автоматически удаляется.
Вы можете выполнять запросы с помощью NOT EXISTS и ~Exists().
Фильтрация по Subquery() или Exists() выражениям
Subquery() , возвращающее булевое значение, и Exists() могут использоваться в качестве условия condition в выражениях When или для непосредственной фильтрации набора результатов:
>>> recent_comments = Comment.objects.filter(...) # From above >>> Post.objects.filter(Exists(recent_comments))
Это гарантирует, что подзапрос не будет добавлен в столбцы SELECT, что может привести к лучшей производительности.
Использование агрегаций в Subquery выражении
Агрегации могут использоваться в Subquery, но требуют определенной комбинации filter(), values() и annotate() для правильного группирования подзапроса.
Предполагая, что обе модели имеют поле length, чтобы найти посты, длина которых больше, чем общая длина всех комментариев:
>>> from django.db.models import OuterRef, Subquery, Sum
>>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post")
>>> total_comments = comments.annotate(total=Sum("length")).values("total")
>>> Post.objects.filter(length__gt=Subquery(total_comments))
Начальный filter(...) ограничивает подзапрос соответствующими параметрами. order_by() удаляет по умолчанию ordering (если таковой имеется) в модели Comment. values('post') агрегирует комментарии по Post. Наконец, annotate(...) выполняет агрегацию. Порядок применения этих методов набора результатов важен. В этом случае, поскольку подзапрос должен быть ограничен одним столбцом, требуется values('total').
Это единственный способ выполнить агрегацию внутри Subquery, так как использование aggregate() пытается оценить набор результатов (и если есть OuterRef, это будет невозможно разрешить).
Выражения на базе исходного SQL
-
class RawSQL(sql, params, output_field=None)[source]
Иногда выражения базы данных не могут легко выразить сложное WHERE условие. В этих крайних случаях используйте выражение RawSQL. Например:
>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (param,)))
Эти дополнительные проверки могут не быть переносимыми на разные движки баз данных (потому что вы явно пишете код SQL) и нарушают принцип DRY, поэтому их следует избегать, если это возможно.
Выражения RawSQL также могут использоваться в качестве целевых объектов __in фильтров:
>>> queryset.filter(id__in=RawSQL("select id from sometable where col = %s", (param,)))
Предупреждение
Для защиты от атаки SQL-инъекции необходимо экранировать любые параметры, которые может контролировать пользователь, используя params. params является обязательным аргументом, чтобы заставить вас признать, что вы не интерполируете свой SQL с данными, введёнными пользователем.
Вы также не должны заключать в кавычки плейсхолдеры в строке SQL. Этот пример уязвим для SQL-инъекции из-за кавычек вокруг %s:
RawSQL("select col from sometable where othercol = '%s'") # unsafe!
Вы можете узнать больше о работе защиты от SQL-инъекций Django Защиты от SQL-инъекций.
Функции окон
Функции окон предоставляют способ применения функций к разделам. В отличие от обычной агрегационной функции, которая вычисляет конечный результат для каждого набора, определённого группировкой, функции окон работают над кадрами и разделами, и вычисляют результат для каждой строки.
Вы можете указать несколько окон в одном запросе, который в Django ORM будет эквивалентен включению нескольких выражений в вызов QuerySet.annotate(). ORM не использует именованные окна, вместо этого они являются частью выбранных столбцов.
-
class Window(expression, partition_by=None, order_by=None, frame=None, output_field=None)[source] -
-
template -
По умолчанию
%(expression)s OVER (%(window)s). Если указан только аргументexpression, то фрагмент окна будет пустым.
-
Класс Window является основным выражением для фрагмента OVER.
Аргумент expression представляет собой функцию окна, функцию агрегации или выражение, совместимое с фрагментом окна.
Аргумент partition_by принимает выражение или последовательность выражений (имена столбцов должны быть заключены в F-объект), которые управляют разбиением строк. Разбиение сужает набор строк, используемых для вычисления результата.
Поле результата output_field указывается либо в качестве аргумента, либо выражением.
Аргумент order_by принимает выражение, к которому можно применить asc() и desc(), строку имени поля (с необязательным префиксом "-", указывающим на убывающий порядок) или кортеж или список строк и/или выражений. Сортировка определяет порядок применения выражения. Например, если вы суммируете строки в разделе, первый результат — значение первой строки, второй — сумма первой и второй строк.
Параметр frame определяет, какие другие строки следует использовать в вычислениях. Подробности см. в разделе Кадры.
Например, чтобы добавить к каждому фильму средний рейтинг фильмов того же студии, жанра и года выпуска:
>>> from django.db.models import Avg, F, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... ),
... )
Это позволяет проверить, оценен ли фильм лучше или хуже, чем его аналоги.
Можно применить несколько выражений к одному и тому же окну, то есть к одному и тому же разделу и кадру. Например, можно изменить предыдущий пример, чтобы также включить лучший и худший рейтинг в каждой группе фильмов (одна студия, жанр и год выпуска), используя три функции окна в одном запросе. Разбиение и порядок из предыдущего примера извлекаются в словарь для сокращения повторений:
>>> from django.db.models import Avg, F, Max, Min, Window
>>> window = {
... "partition_by": [F("studio"), F("genre")],
... "order_by": "released__year",
... }
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... **window,
... ),
... best=Window(
... expression=Max("rating"),
... **window,
... ),
... worst=Window(
... expression=Min("rating"),
... **window,
... ),
... )
Фильтрование по функциям окна поддерживается, если запросы не являются дизъюнктивными (не используется OR или XOR в качестве соединителя) и применяются к запросу с агрегацией.
Например, запрос, который опирается на агрегацию и имеет OR-фильтр по функции окна и полю, не поддерживается. Применение комбинированных предикатов после агрегации может привести к включению строк, которые обычно исключались из групп:
>>> 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)[source] -
-
frame_type -
Это свойство установлено в
'RANGE'.
PostgreSQL имеет ограниченную поддержку
ValueRangeи поддерживает только использование стандартных начальных и конечных точек, таких какCURRENT ROWиUNBOUNDED FOLLOWING.Изменено в Django 5.1:Добавлен аргумент
exclusion. -
-
class RowRange(start=None, end=None, exclusion=None)[source] -
-
frame_type -
Это свойство установлено в
'ROWS'.
Изменено в Django 5.1:Добавлен аргумент
exclusion. -
Оба класса возвращают SQL с шаблоном:
%(frame_type)s BETWEEN %(start)s AND %(end)s
-
class WindowFrameExclusion[source] -
Новое в Django 5.1.
-
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. По умолчанию кадр включает все строки раздела до последней строки в наборе.
Допустимые значения для аргументов start и end — None, целое число или ноль. Отрицательное целое число для start приводит к N PRECEDING, в то время как None даёт UNBOUNDED PRECEDING. В режиме ROWS, положительное целое число для `start приводит к N FOLLOWING. Положительные целые числа принимаются для end и приводят к N FOLLOWING. В режиме ROWS, отрицательное целое число для `end приводит к N PRECEDING. Для start и end ноль вернёт CURRENT ROW.
Разница в том, что включает CURRENT ROW. При указании в режиме ROWS, кадр начинается или заканчивается текущей строкой. В режиме RANGE, кадр начинается или заканчивается первой или последней равнозначной строкой в соответствии с условием сортировки. Таким образом, RANGE CURRENT ROW оценивает выражение для строк, имеющих одинаковое значение, заданное в условии сортировки. Так как шаблон включает в себя как start, так и end точки, это можно выразить так:
ValueRange(start=0, end=0)
Если «равнозначные» фильмы описываются как фильмы, выпущенные той же студией, в том же жанре и в том же году, этот пример RowRange добавляет к каждому фильму средний рейтинг двух предыдущих и двух последующих равнозначных фильмов:
>>> from django.db.models import Avg, F, RowRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=RowRange(start=-2, end=2),
... ),
... )
Если поле released модели Movie хранит месяц выпуска каждого фильма, этот пример ValueRange добавляет к каждому фильму средний рейтинг фильмов, выпущенных за 12 месяцев до и 12 месяцев после каждого фильма:
>>> from django.db.models import Avg, F, ValueRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=ValueRange(start=-12, end=12),
... ),
... )
Добавлена поддержка положительных целых чисел start и отрицательных целых чисел end для RowRange.
Технические сведения
Ниже приведены технические подробности реализации, которые могут быть полезны авторам библиотек. Технический API и примеры ниже помогут создавать общие выражения запросов, которые могут расширять встроенную функциональность, предоставляемую Django.
API выражений
Выражения запросов реализуют API выражений запроса, но также предоставляют ряд дополнительных методов и свойств, перечисленных ниже. Все выражения запросов должны наследоваться от Expression() или соответствующего подкласса.
Когда выражение запроса оборачивает другое выражение, оно отвечает за вызов соответствующих методов на обернутом выражении.
-
class Expression[source] -
-
allowed_default -
Новое в Django 5.0.
Сообщает Django, что данное выражение может использоваться в
Field.db_default. По умолчаниюFalse.
-
constraint_validation_compatible -
Новое в Django 5.1.
Сообщает Django, что данное выражение может использоваться во время проверки ограничений. Выражения с
constraint_validation_compatibleзначением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, что заставляет выражение вычисляться в базе данных.
-
resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False) -
Предоставляет возможность выполнить предварительную обработку или проверку выражения перед его добавлением в запрос.
resolve_expression()также должен быть вызван для всех вложенных выражений. Должен быть возвращенcopy()с необходимыми преобразованиями.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_firstиnulls_lastопределяют, как сортируются значения NULL. См. Использование F() для сортировки значений NULL для примера использования.
-
desc(nulls_first=None, nulls_last=None) -
Возвращает выражение, готовое для сортировки по убыванию.
nulls_firstиnulls_lastопределяют, как сортируются значения NULL. См. Использование F() для сортировки значений NULL для примера использования.
-
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
Производится базовая проверка параметров, в том числе требуется как минимум 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().
Мы генерируем 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-инъекций
Поскольку ключевые аргументы Func для __init__() (**extra) и as_sql() (**extra_context) интерполируются в строку SQL, а не передаются в качестве параметров запроса (где драйвер базы данных их экранирует), они не должны содержать недоверенные пользовательские данные.
Например, если substring предоставляет пользователь, эта функция уязвима для SQL-инъекций:
from django.db.models import Func
class Position(Func):
function = "POSITION"
template = "%(function)s('%(substring)s' in %(expressions)s)"
def __init__(self, expression, substring):
# substring=substring is an SQL injection vulnerability!
super().__init__(expression, substring=substring)
Эта функция генерирует строку SQL без параметров. Поскольку substring передаётся super().__init__() в качестве ключевого аргумента, он интерполируется в строку SQL до отправки запроса в базу данных.
Вот исправленная переработка:
class Position(Func):
function = "POSITION"
arg_joiner = " IN "
def __init__(self, expression, substring):
super().__init__(substring, expression)
С substring вместо этого переданным в качестве позиционного аргумента, он будет передан как параметр в запросе к базе данных.
Добавление поддержки в сторонних бэкендах баз данных
Если вы используете бэкенд базы данных, который использует другой синтаксис SQL для определенной функции, вы можете добавить поддержку для него, монтируя новый метод в класс функции.
Предположим, что мы пишем бэкенд для 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, используя параметр template в as_sql().
Мы используем as_sqlserver() потому что django.db.connection.vendor возвращает sqlserver для бэкэнда.
Бэкэнды третьих сторон могут регистрировать свои функции в главном файле __init__.py пакета бэкэнда или в главном файле expressions.py (или пакете), который импортируется из главного файла __init__.py.
Для проектов пользователей, желающих исправить используемый ими бэкэнд, этот код должен находиться в методе AppConfig.ready().
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.1/ref/models/expressions/