Spec-Zone.ru › Django 1.8

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

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

Поддерживаемая арифметика

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 будет интерполирован как function placeholder внутри 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 будет интерполирован как function placeholder внутри 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/

Spec-Zone.ru

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