Выражения запросов
Выражения запросов описывают значение или вычисление, которое может быть использовано в качестве части фильтра, сортировки, аннотации или агрегации. Существует ряд встроенных выражений (документированных ниже), которые могут помочь вам в написании запросов. Выражения могут быть объединены или, в некоторых случаях, вложены, чтобы сформировать более сложные вычисления.
Поддерживаемая арифметика
Django поддерживает сложение, вычитание, умножение, деление, остаток от деления и операцию возведения в степень для выражений запросов, используя Python-константы, переменные и даже другие выражения.
Поддержка оператора возведения в степень ** была добавлена.
Примеры
Некоторые примеры полагаются на функциональность, которая является новой в Django 1.8.
from django.db.models import F, Count
from django.db.models.functions import Length
# 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
# 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()
Company.objects.order_by(Length('name').asc())
Company.objects.order_by(Length('name').desc())
Встроенные выражения
Примечание
Эти выражения определены в 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 выглядит как обычная Python-присвоение значения атрибуту экземпляра, на самом деле это SQL-конструкция, описывающая операцию в базе данных.
Когда Django сталкивается с экземпляром F(), он переопределяет стандартные Python-операторы, чтобы создать инкапсулированное SQL-выражение; в данном случае, которое инструктирует базу данных увеличить поле базы данных, представленное reporter.stories_filed.
Какое значение было или есть в reporter.stories_filed, Python никогда не узнает - с ним полностью работает база данных. Все, что делает Python через класс Django F(), - это создать синтаксис SQL для ссылки на поле и описания операции.
Примечание
Для доступа к новому значению, сохраненному таким образом, объект необходимо перезагрузить:
reporter = Reporters.objects.get(pk=reporter.pk)
Помимо использования в операциях с отдельными экземплярами, как показано выше, F() может быть использован для наборов QuerySets экземпляров объектов с update(). Это сокращает две запросы, которые мы использовали выше - get() и save() - до одной:
reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)
Мы также можем использовать update() для увеличения значения поля на нескольких объектах - что может быть намного быстрее, чем извлечение всех из базы данных в Python, перебор их, увеличение значения поля каждого и сохранение каждого обратно в базу данных:
Reporter.objects.all().update(stories_filed=F('stories_filed') + 1)
F() поэтому может предлагать преимущества производительности, благодаря:
- заставляя базу данных, а не Python, выполнять работу
- сокращению количества запросов, необходимых для некоторых операций
Избегание гонок с использованием F()
Еще одним полезным преимуществом F() является то, что использование базы данных для обновления значения поля, а не Python, предотвращает возникновение _конфликта гонок_.
Если два потока Python выполняют код в первом примере выше, один поток может извлечь, увеличить и сохранить значение поля после того, как другой извлёк его из базы данных. Значение, которое сохранит второй поток, будет основано на исходном значении; работа первого потока просто будет потеряна.
Если за обновление поля отвечает база данных, процесс более устойчив: он будет обновлять поле только на основе значения поля в базе данных, когда вызывается save() или update(), а не на его значении в момент извлечения экземпляра.
Использование F() в фильтрах
F() также очень полезно в QuerySet фильтрах, где они позволяют отфильтровать набор объектов по критериям, основанным на их значениях полей, а не на значениях Python.
Это документировано в использовании выражений F() в запросах.
Использование F() с аннотациями
F() может использоваться для создания динамических полей в ваших моделях путём объединения различных полей с арифметикой:
company = Company.objects.annotate(
chairs_needed=F('num_employees') - F('num_chairs'))
Если поля, которые вы объединяете, имеют разные типы, вам нужно указать Django, какого типа будет возвращаемое поле. Поскольку F() напрямую не поддерживает output_field, вам нужно обернуть выражение с помощью ExpressionWrapper:
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F('active_at') + F('duration'), output_field=DateTimeField()))
Func() выражения
Func() выражения являются базовым типом всех выражений, которые включают функции базы данных, такие как COALESCE и LOWER, или агрегаты, такие как SUM. Их можно использовать непосредственно:
from django.db.models import Func, F
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будет интерполирован какfunctionplaceholder внутриtemplate. По умолчаниюNone.
-
template -
Атрибут класса в формате строки, описывающий SQL, генерируемый для этой функции. По умолчанию
'%(function)s(%(expressions)s)'.
-
arg_joiner -
Атрибут класса, обозначающий символ, используемый для объединения списка
expressionsвместе. По умолчанию', '.
-
Аргумент *expressions представляет собой список позиционных выражений, к которым будет применена функция. Выражения будут преобразованы в строки, объединены с arg_joiner, а затем интерполированы в template как expressions placeholder.
Позиционные аргументы могут быть выражениями или значениями Python. Строки предполагаются ссылками на столбцы и будут обернуты в F() выражения, в то время как другие значения будут обернуты в Value() выражения.
Ключевые слова **extra представляют собой пары key=value, которые могут быть интерполированы в атрибут template. Обратите внимание, что ключевые слова function и template могут быть использованы для замены атрибутов function и template соответственно, без необходимости определения собственного класса. output_field может быть использован для определения ожидаемого типа возвращаемого значения.
Aggregate() выражения
Выражение агрегации — это частный случай выражения Func() выражение, которое информирует запрос о необходимости 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(expression, output_field=None, **extra)[source] -
-
template -
Атрибут класса в формате строки, описывающий SQL, генерируемый для этого агрегата. По умолчанию
'%(function)s( %(expressions)s )'.
-
function -
Атрибут класса, описывающий функцию агрегации, которая будет сгенерирована. В частности,
functionбудет интерполирован какfunctionplaceholder внутриtemplate. По умолчаниюNone.
-
Аргумент expression может представлять собой имя поля модели или другое выражение. Он будет преобразован в строку и использован как expressions placeholder внутри template.
Аргумент output_field требует экземпляра поля модели, например, IntegerField() или BooleanField(), в который Django загрузит значение после извлечения из базы данных. Обычно при создании экземпляра поля модели не нужны какие-либо аргументы, так как любые аргументы, связанные с проверкой данных (max_length, max_digits, и т. д.), не будут применяться к выходному значению выражения.
Обратите внимание, что output_field требуется только в тех случаях, когда Django не может определить тип поля результата. Сложные выражения, которые смешивают типы полей, должны определять желаемый тип output_field. Например, сложение IntegerField() и FloatField(), вероятно, должно иметь определённый output_field=FloatField().
output_field — новый параметр.
Ключевые слова **extra представляют собой пары key=value, которые могут быть интерполированы в атрибут template.
Функции агрегирования теперь могут использовать арифметику и ссылаться на несколько полей модели в одной функции.
Создание собственных функций агрегирования
Создание собственной функции агрегирования очень просто. Как минимум, необходимо определить function, но также можно полностью настроить генерируемый SQL-запрос. Вот краткий пример:
from django.db.models import Aggregate
class Count(Aggregate):
# supports COUNT(distinct field)
function = 'COUNT'
template = '%(function)s(%(distinct)s%(expressions)s)'
def __init__(self, expression, distinct=False, **extra):
super(Count, self).__init__(
expression,
distinct='DISTINCT ' if distinct else '',
output_field=IntegerField(),
**extra)
Value() выражения
-
class Value(value, output_field=None)[source]
Объект Value() представляет собой наименьшую возможную составляющую выражения: простое значение. Когда нужно представить значение целого числа, булевого значения или строки в выражении, можно заключить это значение в Value().
Вам редко придётся использовать Value() напрямую. Когда вы пишете выражение F('field') + 1, Django неявно заключает 1 в Value(), что позволяет использовать простые значения в более сложных выражениях.
Аргумент value описывает значение, которое должно быть включено в выражение, например, 1, True или None. Django умеет преобразовывать эти значения Python в соответствующий тип базы данных.
Аргумент output_field должен быть экземпляром поля модели, например, IntegerField() или BooleanField(), в который Django загрузит значение после извлечения из базы данных. Обычно при создании экземпляра поля модели не нужны какие-либо аргументы, так как любые аргументы, связанные с проверкой данных (max_length, max_digits, и т. д.), не будут применяться к выходному значению выражения.
ExpressionWrapper() выражения
-
class ExpressionWrapper(expression, output_field)[source]
ExpressionWrapper просто окружает другое выражение и предоставляет доступ к свойствам, таким как output_field, которые могут быть недоступны в других выражениях. ExpressionWrapper необходим при использовании арифметики над F() выражениями с разными типами, как описано в Использование F() с аннотациями.
Условные выражения
Условные выражения позволяют использовать логику if ... elif ... else в запросах. Django нативно поддерживает SQL CASE выражения. Более подробную информацию см. в Условные выражения.
Выражения с необработанным 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", (someparam,)))
Эти дополнительные запросы могут быть несовместимы с другими базами данных (потому что вы явно пишете SQL-код) и нарушают принцип DRY, поэтому следует избегать их, если это возможно.
Предупреждение
Следует очень осторожно обрабатывать любые параметры, которые могут контролироваться пользователем, используя params, чтобы защититься от атак с использованием SQL-инъекций.
Техническая информация
Ниже приведены технические детали реализации, которые могут быть полезны авторам библиотек. Технический API и примеры ниже помогут в создании универсальных выражений запросов, которые могут расширить встроенные возможности Django.
API выражений
Выражения запросов реализуют API выражений запросов, но также предоставляют ряд дополнительных методов и атрибутов, перечисленных ниже. Все выражения запросов должны наследовать от Expression() или соответствующего подкласса.
Когда выражение запроса содержит другое выражение, оно отвечает за вызов соответствующих методов вложенного выражения.
-
class Expression[source] -
-
contains_aggregate -
Сообщает Django, что это выражение содержит агрегат и что в запрос нужно добавить
GROUP BYпредложение.
-
resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False) -
Предоставляет возможность выполнить предварительную обработку или проверку выражения перед его добавлением в запрос.
resolve_expression()также должен быть вызван для любых вложенных выражений. Должно быть возвращеноcopy()сselfнеобходимыми преобразованиями.query— это реализация запроса бэкенда.allow_joins— это булево значение, которое разрешает или запрещает использование объединений в запросе.reuse— это набор переиспользуемых объединений для многосвязанных сценариев.summarize— это булево значение, которое, когда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(self, value, expression, connection, context) -
Метод, позволяющий выражению принудительно преобразовать
valueв более подходящий тип.
-
refs_aggregate(existing_aggregates) -
Возвращает кортеж, содержащий
(aggregate, lookup_path)первой функции агрегирования, на которую ссылается это выражение (или любое вложенное выражение), или(False, ()), если функция агрегирования не указана. Например:queryset.filter(num_chairs__gt=F('sum__employees'))Выражение
F()здесь ссылается на предыдущее вычислениеSum(), что означает, что это выражение фильтра должно быть добавлено кHAVINGпредложению, а не кWHEREпредложению.В большинстве случаев возвращение результата
refs_aggregateдля любого вложенного выражения должно быть уместным, так как необходимые встроенные выражения вернут правильные значения.
-
get_group_by_cols() -
Отвечает за возврат списка столбцов, на которые ссылается это выражение.
get_group_by_cols()должен быть вызван для любых вложенных выражений. ОбъектыF()содержат ссылку на столбец.
-
asc() -
Возвращает выражение, готовое к сортировке по возрастанию.
-
desc() -
Возвращает выражение, готовое к сортировке по убыванию.
-
reverse_ordering() -
Возвращает
selfс необходимыми изменениями для изменения порядка сортировки в вызовеorder_by. Например, выражение, реализующееNULLS LAST, изменило бы своё значение наNULLS FIRST. Изменения необходимы только для выражений, которые реализуют порядок сортировки, таких какOrderBy. Этот метод вызывается при вызовеreverse()на наборе запросов.
-
Написание собственных выражений запросов
Вы можете написать собственные классы выражений запросов, которые используют и могут интегрироваться с другими выражениями запросов. Давайте пройдём по примеру, написав реализацию COALESCE SQL-функции, не используя встроенные выражения Func().
Функция COALESCE SQL определена как принимающая список столбцов или значений. Она вернет первый столбец или значение, которое не NULL.
Мы начнем с определения шаблона, используемого для генерации SQL, и метода __init__() для установки некоторых атрибутов:
import copy
from django.db.models import Expression
class Coalesce(Expression):
template = 'COALESCE( %(expressions)s )'
def __init__(self, expressions, output_field, **extra):
super(Coalesce, self).__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
self.extra = extra
Мы проводим базовую валидацию параметров, включая требование как минимум 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):
sql_expressions, sql_params = [], []
for expression in self.expressions:
sql, params = compiler.compile(expression)
sql_expressions.append(sql)
sql_params.extend(params)
self.extra['expressions'] = ','.join(sql_expressions)
return self.template % self.extra, sql_params
def as_oracle(self, compiler, connection):
"""
Example of vendor specific handling (Oracle in this case).
Let's make the function name lowercase.
"""
self.template = 'coalesce( %(expressions)s )'
return self.as_sql(compiler, connection)
Мы генерируем 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
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/ref/models/expressions/