Spec-Zone.ru › Django 1.11

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

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

Поддерживаемые арифметические операции

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 выглядит как обычная операция присваивания значения атрибуту экземпляра, на самом деле это 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.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-выражения. Подробнее см. в Условные выражения.

Subquery() выражения

class Subquery(queryset, output_field=None) [source]
Новое в Django 1.11.

Вы можете добавить явное подзапрос к 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]
Новое в Django 1.11.

Используйте OuterRef, когда набор результатов в Subquery должен ссылаться на поле из внешнего запроса. Он действует как выражение F, за исключением того, что проверка, относится ли он к действительному полю, выполняется только после разрешения внешнего набора результатов.

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

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

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

Временами требуется вернуть один столбец из Subquery, например, для использования Subquery в качестве целевого __in-поиска. Для возврата всех комментариев к постам, опубликованным в течение последнего дня:

>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> posts = Post.objects.filter(published_at__gte=one_day_ago)
>>> Comment.objects.filter(post__in=Subquery(posts.values('pk')))

В этом случае подзапрос должен использовать values() для возвращения только одного столбца: первичного ключа поста.

Ограничение подзапроса одной строкой

Чтобы предотвратить возвращение подзапросом нескольких строк, используется срез ([:1]) набора результатов:

>>> subquery = Subquery(newest.values('email')[:1])
>>> Post.objects.annotate(newest_commenter_email=subquery)

В этом случае подзапрос должен возвращать только один столбец и одну строку: адрес электронной почты самого последнего комментария.

(Использование get() вместо среза приведет к ошибке, потому что OuterRef не может быть разрешен до использования набора результатов внутри Subquery.)

Exists() подзапросы

class Exists(queryset) [source]
Новое в Django 1.11.

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 U0."id", U0."post_id", U0."email", U0."created_at"
    FROM "comment" U0
    WHERE (
        U0."created_at" >= YYYY-MM-DD HH:MM:SS AND
        U0."post_id" = ("post"."id")
    )
) AS "recent_comment" FROM "post"

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

Вы можете выполнять запросы, используя NOT EXISTS с ~Exists().

Фильтрация по выражению Subquery подзапроса

Невозможно напрямую фильтровать с использованием Subquery и Exists, например:

>>> Post.objects.filter(Exists(recent_comments))
...
TypeError: 'Exists' object is not iterable

Вы должны фильтровать по выражению подзапроса, сначала аннотируя набор запросов, а затем фильтруя по этой аннотации:

>>> Post.objects.annotate(
...     recent_comment=Exists(recent_comments),
... ).filter(recent_comment=True)

Использование агрегатов внутри выражения 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", (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(value, expression, connection, context)

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

get_group_by_cols()

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

asc(nulls_first=False, nulls_last=False)

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

nulls_first и nulls_last определяют, как сортируются нулевые значения.

Изменено в Django 1.11:

Добавлены параметры nulls_last и nulls_first.

desc(nulls_first=False, nulls_last=False)

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

nulls_first и nulls_last определяют, как сортируются нулевые значения.

Изменено в Django 1.11:

Добавлены параметры nulls_first и nulls_last.

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(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.11/ref/models/expressions/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API