Выражения запросов
Выражения запросов описывают значение или вычисление, которое может быть использовано в качестве части обновления, создания, фильтрации, сортировки, аннотации или агрегирования. Существует ряд встроенных выражений (документированных ниже), которые могут помочь вам в написании запросов. Выражения могут быть объединены или, в некоторых случаях, вложены для формирования более сложных вычислений.
Поддержка использования выражений при создании новых экземпляров моделей была добавлена.
Поддерживаемые арифметические операции
Django поддерживает сложение, вычитание, умножение, деление, остаток от деления и оператор степени над выражениями запросов, используя Python-константы, переменные и даже другие выражения.
Примеры
Некоторые примеры используют функциональность, которая является новой в Django 1.8.
from django.db.models import F, Count, Value
from django.db.models.functions import Length, Upper
# Find companies that have more employees than chairs.
Company.objects.filter(num_employees__gt=F('num_chairs'))
# Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent.
Company.objects.filter(num_employees__gt=F('num_chairs') * 2)
Company.objects.filter(
num_employees__gt=F('num_chairs') + F('num_chairs'))
# How many chairs are needed for each company to seat all employees?
>>> company = Company.objects.filter(
... num_employees__gt=F('num_chairs')).annotate(
... chairs_needed=F('num_employees') - F('num_chairs')).first()
>>> company.num_employees
120
>>> company.num_chairs
50
>>> company.chairs_needed
70
# Create a new company using expressions.
>>> company = Company.objects.create(name='Google', ticker=Upper(Value('goog')))
# Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db()
>>> company.ticker
'GOOG'
# Annotate models with an aggregated value. Both forms
# below are equivalent.
Company.objects.annotate(num_products=Count('products'))
Company.objects.annotate(num_products=Count(F('products')))
# Aggregates can contain complex computations also
Company.objects.annotate(num_offerings=Count(F('products') + F('services')))
# Expressions can also be used in order_by()
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) # 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.all().update(stories_filed=F('stories_filed') + 1)
F() поэтому может обеспечить преимущества производительности, за счёт:
- передачи работы базе данных, а не Python
- сокращения количества запросов, необходимых для некоторых операций
Избегание гонок с помощью F()
Еще одно полезное преимущество F() заключается в том, что обновление значения поля базой данных, а не Python, избегает *гонки*.
Если два потока Python выполняют код в первом примере выше, один поток мог бы извлечь, увеличить и сохранить значение поля после того, как другой извлек его из базы данных. Значение, которое сохраняет второй поток, будет основано на исходном значении; работа первого потока просто будет потеряна.
Если база данных отвечает за обновление поля, процесс более надёжен: он будет обновлять поле только на основе значения поля в базе данных, когда выполняется save() или update(), а не на основе его значения при извлечении экземпляра.
F() присваивания сохраняются после Model.save()
F() объекты, присвоенные полям моделей, сохраняются после сохранения экземпляра модели и будут применяться при каждом save(). Например:
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
reporter.name = 'Tintin Jr.'
reporter.save()
stories_filed будет обновлён дважды в этом случае. Если первоначально он равен 1, конечное значение будет 3.
Использование 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будет интерполирован какfunctionплейсхолдер внутриtemplate. По умолчаниюNone.
-
template -
Атрибут класса, в формате строки, описывающий SQL, который генерируется для этой функции. По умолчанию
'%(function)s(%(expressions)s)'.
-
arg_joiner -
Атрибут класса, обозначающий символ, используемый для объединения списка
expressionsвместе. По умолчанию', '.
-
Аргумент *expressions — список позиционных выражений, к которым будет применена функция. Выражения будут преобразованы в строки, соединены с arg_joiner, а затем интерполированы в template как expressions плейсхолдер.
Позиционные аргументы могут быть выражениями или значениями Python. Строки предполагаются как ссылки на столбцы и будут обернуты в F() выражения, а другие значения будут обернуты в Value() выражения.
Ключевые аргументы **extra — пары «ключ-значение», которые могут быть интерполированы в атрибут 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будет интерполирован как заполнительfunctionвtemplate. По умолчаниюNone.
-
Аргумент expression может быть именем поля модели или другим выражением. Он будет преобразован в строку и использован в качестве заполнителя expressions в 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() когда вы хотите передать строку в выражение. Большинство выражений интерпретируют строковый аргумент как имя поля, как Lower('name').
Аргумент 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-инъекции. 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()на наборе запросов.
-
Создание собственных выражений запроса
Вы можете написать собственные классы выражений запроса, которые используют и могут интегрироваться с другими выражениями запроса. Давайте рассмотрим пример, написав реализацию SQL-функции COALESCE, не используя встроенные выражения 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
Добавление поддержки в сторонних бэкендах баз данных
Если вы используете бэкенд базы данных, который использует другой синтаксис 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/1.9/ref/models/expressions/