Выражения запросов
Выражения запросов описывают значение или вычисление, которое может быть использовано в качестве части обновления, создания, фильтрации, сортировки, аннотации или агрегирования. Существует ряд встроенных выражений (документированных ниже), которые могут быть использованы для написания запросов. Выражения могут быть объединены или, в некоторых случаях, вложены для формирования более сложных вычислений.
Была добавлена поддержка использования выражений при создании новых экземпляров модели.
Поддерживаемые арифметические операции
Django поддерживает сложение, вычитание, умножение, деление, остаток от деления, возведение в степень в выражениях запросов, используя Python-константы, переменные и даже другие выражения.
Некоторые примеры
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()))
При обращении к реляционным полям, таким как ForeignKey, F() возвращает значение первичного ключа, а не экземпляр модели:
>> car = Company.objects.annotate(built_by=F('manufacturer'))[0]
>> car.manufacturer
<Manufacturer: Toyota>
>> car.built_by
3
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)'.Если вы создаёте SQL, как
strftime('%W', 'date')и вам нужен буквенный символ%в запросе, удваивайте его (%%%%) в атрибутеtemplate, потому что строка интерполируется дважды: один раз во время интерполяции шаблона вas_sql(), и один раз во время интерполяции SQL с параметрами запроса в курсоре базы данных.
-
arg_joiner -
Атрибут класса, обозначающий символ, используемый для объединения списка
expressionsвместе. По умолчанию', '.
-
arity -
Новое в Django 1.10.
Атрибут класса, обозначающий количество аргументов, которые принимает функция. Если этот атрибут установлен, и функция вызывается с другим количеством выражений,
TypeErrorбудет вызвано. По умолчаниюNone.
-
as_sql(compiler, connection, function=None, template=None, arg_joiner=None, **extra_context)[source] -
Генерирует SQL для функции базы данных.
Методы
as_vendor()должны использоватьfunction,template,arg_joiner, и любые другие**extra_contextпараметры для настройки SQL по мере необходимости. Например:class ConcatPair(Func): ... function = 'CONCAT' ... def as_mysql(self, compiler, connection): return super(ConcatPair, self).as_sql( compiler, connection, function='CONCAT_WS', template="%(function)s('', %(expressions)s)", )Изменено в Django 1.10:Была добавлена поддержка параметров
arg_joinerи**extra_context.
-
Аргумент *expressions — это список позиционных выражений, к которым будет применена функция. Выражения будут преобразованы в строки, объединены с arg_joiner, и затем интерполированы в template как expressions.
Позиционные аргументы могут быть выражениями или значениями Python. Строки предполагаются как ссылки на столбцы и будут обернуты в F() выражения, а другие значения будут обернуты в Value() выражения.
Ключевые слова **extra — это key=value пары, которые могут быть интерполированы в атрибут template. Ключевые слова function, template, и arg_joiner могут быть использованы для замены атрибутов с аналогичными именами без необходимости определения собственного класса. 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().
Ключевые слова **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к более подходящему типу.
-
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):
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
Мы проводим некоторую базовую валидацию параметров, включая требование как минимум 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, 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 для определённой функции, вы можете добавить поддержку для неё, подменяя новым методом класс функции.
Допустим, мы пишем бэкенд для 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.10/ref/models/expressions/