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

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

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

Django поддерживает отрицание, сложение, вычитание, умножение, деление, арифметику по модулю, а также оператор мощности для выражений запросов с использованием констант, переменных и даже d другие выражения.

Некоторые примеры

from django.db.models import Count, F, 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(), either directly
Company.objects.order_by(Length('name').asc())
Company.objects.order_by(Length('name').desc())
# or using the double underscore lookup syntax.
from django.db.models import CharField
from django.db.models.functions import Length
CharField.register_lookup(Length)
Company.objects.order_by('name__length')

# Boolean expression can be used directly in filters.
from django.db.models import Exists
Company.objects.filter(
    Exists(Employee.objects.filter(company=OuterRef('pk'), salary__gt=10))
)

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

Заметка

Эти выражения определены в django.db.models.expressions и django.db.models.aggregates , но для удобства они доступны и обычно импортируются из django.db.models .

Выражения F()

класс F

Объект 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()

Хотя это выглядит как обычное присвоение значения атрибуту экземпляра в Python, на самом деле это конструкция SQL, описывающая операцию с базой данных.reporter.stories_filed = F('stories_filed') + 1

Когда 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 . Этого постоянства можно избежать, перезагрузив модель после сохранения, например, используя refresh_from_db() .

Использование F() в фильтрах

F() также очень полезен в фильтрах QuerySet , где можно фильтровать набор объектов на основе критериев, основанных на значениях полей, а не на значениях Python.

Это использование задокументировано в F Expressions () в запросах .

Использование 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

Использование F() для сортировки нулевых значений

Используйте F() и именованные параметры nulls_first или nulls_last выражения Expression.asc() или desc() для управления сортировкой нулевых значений в поле. По умолчанию порядок зависит от базы данных.

Например, чтобы отсортировать компании, с которыми не связались ( last_contacted имеет значение null), после компаний, с которыми связывались

from django.db.models import F
Company.objects.order_by(F('last_contacted').desc(nulls_last=True))

Выражения Func()

Выражения Func() являются базовым типом всех выражений, которые включают функции базы данных, такие как COALESCE и LOWER , или агрегаты, подобные SUM . Их можно использовать напрямую:

from django.db.models import F, Func

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 выглядит так:

классFunc ( * выражения , ** дополнительные )
function

Атрибут класса, описывающий функцию, которая будет сгенерирована. Точнее, function будет вставлен заполнитель function внутри template . Его значение по умолчанию - None .

template

Атрибут класса в виде строки формата, который описывает код SQL, созданный для этой функции. Его значение по умолчанию - '%(function)s(%(expressions)s)' .

ЕСЛИ вы создаете код SQL, такой как, и вам нужен буквальный символ в запросе, вам необходимо увеличить его в четыре раза ( ) в атрибуте, потому что строка интерполируется дважды: один раз во время интерполяции шаблона в , и снова в интерполяции SQL с параметрами запроса в курсоре базы данных.strftime('%W', 'date') % %%%% template as_sql()

arg_joiner

Атрибут класса, определяющий последовательность символов, используемых для соединения элементов списка expressions . Его значение по умолчанию - .', '

arity

Атрибут класса, указывающий количество параметров, принимаемых функцией. Если этот атрибут установлен и функция вызывается с другим количеством выражений, TypeError будет сгенерировано исключение . По умолчанию это None .

as_sql( компиляция , соединение , функция = Нет , шаблон = Нет , arg_joiner = Нет , ** extra_context )

Создает фрагмент SQL функции базы данных. Возвращает кортеж , где - строка SQL, а - список или кортеж параметров запроса.(sql, params) sql params

Методы as_vendor() должны использовать параметры function , template , arg_joiner и любой другой параметр , **extra_context чтобы настроить код SQL , если это необходимо. Например :

django / db / models / functions.py
class ConcatPair(Func):
    ...
    function = 'CONCAT'
    ...

    def as_mysql(self, compiler, connection, **extra_context):
        return super().as_sql(
            compiler, connection,
            function='CONCAT_WS',
            template="%(function)s('', %(expressions)s)",
            **extra_context
        )

Чтобы избежать уязвимости, связанной с внедрением SQL-кода, extra_context не следует содержать небезопасный контент от пользователей, поскольку эти значения вставляются в строку SQL и не передаются в качестве параметров запроса, и в этом случае драйвер базы данных может их экранировать.

Параметр *expressions - это список позиционных выражений, к которым применяется функция. Выражения преобразуются в строки, соединяются arg_joiner , а затем вставляются template с помощью заполнителя expressions .

Позиционные параметры могут быть выражениями или значениями Python. Строки должны быть ссылками на столбцы и будут заключены в выражения, F() а другие значения заключены в выражения Value() .

Именованные параметры **extra - это пары, clé=valeur которые можно вставить в атрибут template . Чтобы избежать уязвимости, связанной с внедрением SQL-кода, extra не следует содержать небезопасный контент от пользователей, поскольку эти значения вставляются в строку SQL и не передаются в качестве параметров запроса, и в этом случае драйвер базы данных может их экранировать.

Ключевые слова 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 выглядит так:

классаAggregate ( * выражения , output_field = None , отличный = False , фильтр = None , ** за дополнительную плату )
template

Атрибут класса в виде строки формата, который описывает код SQL, созданный для этого агрегирования. Его значение по умолчанию - '%(function)s(%(distinct)s%(expressions)s)' .

function

Атрибут класса, описывающий функцию агрегирования, которая будет сгенерирована. Точнее, function будет вставлен заполнитель function внутри template . Его значение по умолчанию - None .

window_compatible

Это True значение по умолчанию, поскольку большинство агрегатных функций можно использовать в качестве исходного выражения в Window .

allow_distinct

Атрибут класса, определяющий, позволяет ли эта агрегатная функция передавать именованный параметр distinct . Если установлено значение False (по умолчанию), TypeError генерируется, если distinct=True передается.

Позиционные параметры expressions могут включать выражения или имена полей модели. Они будут преобразованы в текст и использованы в качестве заполнителя expressions внутри template .

Параметр output_field должен быть экземпляром поля модели, например IntegerField() или BooleanField() , в которое Django загрузит значение после его получения из базы данных. При создании экземпляра поля модели параметры обычно не требуются, поскольку ни один параметр, связанный с данными проверки ( max_length и max_digits т. Д.), Не будет применяться к результирующему значению выражения.

Обратите внимание, что output_field это требуется только в том случае, если Django не может определить тип поля результата. Сложные выражения, которые смешивают типы полей, должны определять output_field ожидаемый тип результата. Например, добавление a IntegerField() и a FloatField() , вероятно, потребует определения output_field=FloatField() .

Параметр distinct определяет, следует ли вызывать агрегатную функцию для каждого отдельного значения expressions (или набора значений в случае нескольких выражений). Параметр поддерживается только для агрегатов, которым это allow_distinct необходимо True .

Параметр filter принимает параметр, используемый для фильтрации агрегированных строк. Примеры использования см. В разделе « Условное агрегирование и фильтрация в аннотациях» .objet Q

Именованные параметры **extra - это пары, clé=valeur которые можно вставить в атрибут путем интерполяции template .

Создание собственных функций агрегирования

Вы также можете создавать свои собственные функции агрегирования. Вы должны определить как минимум function , но вы также можете полностью настроить созданный код SQL. Вот небольшой пример:

from django.db.models import Aggregate

class Sum(Aggregate):
    # Supports SUM(ALL field).
    function = 'SUM'
    template = '%(function)s(%(all_values)s%(expressions)s)'
    allow_distinct = False

    def __init__(self, expression, all_values=False, **extra):
        super().__init__(
            expression,
            all_values='ALL ' if all_values else '',
            **extra
        )

Выражения Value()

classValue ( значение , output_field = None )

Объект Value() представляет собой наименьший возможный компонент выражения: одно значение. Когда вам нужно представить значение целого, логического или строкового значения внутри выражения, вы можете вставить это значение в объект Value() .

Обращаться напрямую к Value() . Когда вы пишете выражение , Django неявно вставляет в объект , что позволяет использовать простые значения в более сложных выражениях. следует использовать при передаче строки в выражение. Большинство выражений интерпретируют текстовый параметр как имя поля, например .F('champ') + 1 1 Value() Value() Lower('name')

Параметр value описывает значение, которое нужно включить в выражение, например 1 , True или None . Django знает, как преобразовать эти значения Python в правильный тип для соответствующей базы данных.

Параметр output_field должен быть экземпляром поля модели, например IntegerField() или BooleanField() , в которое Django загрузит значение после его получения из базы данных. При создании экземпляра поля модели параметры обычно не требуются, поскольку ни один параметр, связанный с данными проверки ( max_length и max_digits т. Д.), Не будет применяться к результирующему значению выражения.

Выражения ExpressionWrapper()

классExpressionWrapper ( выражение , output_field )

ExpressionWrapper оборачивает другое выражение и предоставляет доступ к свойствам, например output_field , которые могут быть недоступны в других выражениях. ExpressionWrapper требуется при использовании арифметики с выражениями F() разных типов, как описано в разделе Использование F () с аннотациями .

Условные выражения

Условные выражения позволяют использовать логику if ... elif ... else в запросах. Django изначально поддерживает выражения SQL CASE . Подробнее см. Условные выражения .

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

classSubquery ( queryset , output_field = None )

С QuerySet помощью выражения можно добавить явные подзапросы к объекту Subquery .

Например, чтобы аннотировать каждый post адрес электронной почты автора последнего комментария этого post :

>>> 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 выполнить подзапрос. В некоторых случаях можно было бы написать эквивалентный запрос, который выполняет ту же задачу более четко или более эффективно.

Ссылка на столбцы родительского запроса

классOuterRef ( поле )

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

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

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

Предел подзапроса в один столбец

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

>>> 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() возвращать только один столбец: первичный ключ post .

Ограничение на подзапросы в одну строку

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

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

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

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

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

классExists ( набор запросов )

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

Например, чтобы аннотировать каждый post с указанием наличия или отсутствия комментария в течение прошедшего дня:

>>> 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() или Exists()

Подзапросы, Subquery() которые возвращают логические значения и Exists() могут использоваться как condition в выражениях When , так и непосредственно для фильтрации набора запросов.

>>> recent_comments = Comment.objects.filter(...)  # From above
>>> Post.objects.filter(Exists(recent_comments))

Это гарантирует, что подзапрос не будет добавлен в столбцы SELECT , что может повысить производительность.

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

В предыдущих версиях Django необходимо было сначала аннотировать, а затем фильтровать эту аннотацию. При этом аннотированное значение всегда присутствовало в результатах запроса, и это часто приводило к замедлению выполнения запроса.

Использование агрегирования в выражении Subquery

Можно использовать агрегаты в подзапросе, но это требует определенной комбинации filter() , values() и для annotate() того , чтобы получить правильную группировку в подзапросе.

Предполагая, что в обеих моделях есть поле length , вот как найти те posts , длина которых превышает общую длину всех комментариев вместе:

>>> 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') является обязательным.

Это единственный способ выполнить агрегацию внутри подзапроса, поскольку aggregate() выполняется оценка набора запросов (а когда он есть OuterRef , его нельзя разрешить. ).

Необработанные выражения SQL

классRawSQL ( sql , params , output_field = None )

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

>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))

Эти необработанные выражения не всегда переносимы между механизмами базы данных (потому что вы пишете код SQL явно), и они нарушают принцип DRY (не повторять), поэтому нужно избегать их в максимально возможной степени.

Предупреждение

Чтобы защитить себя от атак `SQL-инъекций <https://en.wikipedia.org/wiki/Injection_SQL>` _ , вы должны экранировать любой параметр, которым могут управлять пользователи с помощью params . params является обязательным параметром, чтобы заставить вас подтвердить, что вы не выполняете SQL-интерполяцию с данными от пользователей.

Вам также следует избегать заключения заполнителей в кавычки в строке SQL. Этот пример уязвим для SQL-инъекции из-за кавычек %s :

RawSQL("select col from sometable where othercol = '%s'")  # unsafe!

Вы можете узнать больше о том, как работает защита Django от SQL-инъекций .

Оконные функции

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

Вы можете определить несколько окон в одном запросе, что в контексте Django ORM было бы эквивалентно включению нескольких выражений в вызов QuerySet.annotate () . ORM не использует именованные окна, они фактически являются частью выбранных столбцов.

classWindow ( выражение , partition_by = None , order_by = None , frame = None , output_field = None )
filterable

Есть False по умолчанию. Стандарт SQL запрещает ссылаться на оконные функции в этом предложении, WHERE и Django выдает исключение при создании одного QuerySet , которое попытается это сделать.

template

По умолчанию . Если задан только параметр , предложение окон будет пустым.%(expression)s OVER %(window)s expression

Класс Window - это основное выражение предложения OVER .

Параметр expression является либо оконной функцией , либо агрегатной функцией, либо выражением, совместимым с оконным предложением.

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

Поле output_field определяется либо как параметр, либо как выражение.

Параметр order_by принимает ряд выражений, для которых вы можете вызывать asc() и desc() . Эта сортировка контролирует порядок, в котором применяется выражение. Например, если вы суммируете строки в счете, первым результатом будет значение первой строки, а вторым - сумма первой и второй строк.

Параметр frame указывает, какие другие строки следует использовать в вычислении. Подробнее см. Области (рамки) .

Например, чтобы аннотировать каждый фильм со средним рейтингом фильмов одной студии в том же жанре и в том же году.

>>> from django.db.models import Avg, F, Window
>>> from django.db.models.functions import ExtractYear
>>> Movie.objects.annotate(
>>>     avg_rating=Window(
>>>         expression=Avg('rating'),
>>>         partition_by=[F('studio'), F('genre')],
>>>         order_by=ExtractYear('released').asc(),
>>>     ),
>>> )

Это дает возможность сравнивать рейтинг фильма по отношению к аналогам.

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

>>> from django.db.models import Avg, F, Max, Min, Window
>>> from django.db.models.functions import ExtractYear
>>> window = {
>>>    'partition_by': [F('studio'), F('genre')],
>>>    'order_by': ExtractYear('released').asc(),
>>> }
>>> Movie.objects.annotate(
>>>     avg_rating=Window(
>>>         expression=Avg('rating'), **window,
>>>     ),
>>>     best=Window(
>>>         expression=Max('rating'), **window,
>>>     ),
>>>     worst=Window(
>>>         expression=Min('rating'), **window,
>>>     ),
>>> )

Среди механизмов баз данных, включенных в Django, MySQL 8.0.2+, PostgreSQL и Oracle поддерживают оконные выражения. Поддержка различных функций оконных выражений зависит от базы данных. Например, параметры в asc() и desc() не всегда поддерживаются. При необходимости обратитесь к документации по базе данных.

Прицелы (рамки)

Для оконной рейки вы можете выбрать последовательность строк на основе интервалов или обычную последовательность строк.

classValueRange ( начало = Нет , конец = Нет )
frame_type

Для этого атрибута установлено значение 'RANGE' .

PostgreSQL имеет ValueRange ограниченную поддержку и обрабатывает только стандартные начальные и конечные точки, такие как и .CURRENT ROW UNBOUNDED FOLLOWING

classRowRange ( начало = Нет , конец = Нет )
frame_type

Для этого атрибута установлено значение 'ROWS' .

Оба класса возвращают SQL в соответствии с шаблоном

%(frame_type)s BETWEEN %(start)s AND %(end)s

Области уменьшают количество строк, используемых для вычисления результата. Они нацелены на заданную начальную точку до конечной точки. Ножки можно использовать с партитурой или без нее, но обычно полезно указать сортировку окон, чтобы гарантировать детерминированный результат. В области одноранговым узлом является строка с эквивалентным значением или все строки, если предложение сортировки отсутствует.

По умолчанию начальная точка нотоносца соответствует первой строке партитуры. Конечная точка всегда явно включается в код SQL, сгенерированный ORM, и используется по умолчанию . Область по умолчанию содержит все строки в счете до последней строки в наборе данных.UNBOUNDED PRECEDING UNBOUNDED FOLLOWING

Приняты значения параметров start и end являются None , целое число или ноль. Отрицательное число для start продукта , а продукт . Что касается , то нулевой доход . Допускаются положительные целые числа .N preceding None UNBOUNDED PRECEDING start end CURRENT ROW end

Есть разница в том, что внутри . При установке в режиме интервал начинается и заканчивается текущей строкой. При установке в режиме интервал начинается и заканчивается в первой или последней строке группы в зависимости от условия сортировки. Итак, вычисляет выражение для строк, которые имеют одинаковое значение, определенное сортировкой. Поскольку шаблон содержит обе точки и , это можно выразить какCURRENT ROW ROWS RANGE RANGE CURRENT ROW start end

ValueRange(start=0, end=0)

Если «аналогичные» фильма определены как фильмы, выпущенные одной и той же студией в том же жанре и в том же году, в этом примере RowRange каждому фильму присваивается средний рейтинг двух предыдущих фильмов и двух фильмов, следующих за соответствующим фильмом.

>>> from django.db.models import Avg, F, RowRange, Window
>>> from django.db.models.functions import ExtractYear
>>> Movie.objects.annotate(
>>>     avg_rating=Window(
>>>         expression=Avg('rating'),
>>>         partition_by=[F('studio'), F('genre')],
>>>         order_by=ExtractYear('released').asc(),
>>>         frame=RowRange(start=-2, end=2),
>>>     ),
>>> )

Если база данных поддерживает это, вы можете установить начальную и конечную точки на основе значений выражения в счете. Если поле released модели Movie хранит месяц публикации каждого фильма, этот пример ValueRange аннотирует каждый фильм со средней оценкой аналогичных фильмов, опубликованных между двенадцатью месяцами до и двенадцатью месяцами после каждого фильма.

>>> from django.db.models import Avg, ExpressionList, F, ValueRange, Window
>>> Movie.objects.annotate(
>>>     avg_rating=Window(
>>>         expression=Avg('rating'),
>>>         partition_by=[F('studio'), F('genre')],
>>>         order_by=F('released').asc(),
>>>         frame=ValueRange(start=-12, end=12),
>>>     ),
>>> )

Техническая информация

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

Expression API

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

Когда выражение запроса включает в себя другое выражение, оно отвечает за вызов соответствующих методов вложенного выражения.

класс Expression
contains_aggregate

Сообщает Django, что это выражение содержит агрегирование и что в запрос следует добавить предложение.GROUP BY

contains_over_clause

Сообщает Django, что это выражение содержит выражение Window . Это, например, используется для запрещения выражений оконных функций в запросах, изменяющих данные.

filterable

Сообщает Django, что на это выражение можно ссылаться в QuerySet.filter() . Есть True по умолчанию.

window_compatible

Сообщает Django, что это выражение можно использовать как исходное выражение в Window . Есть False по умолчанию.

resolve_expression( query = None , allow_joins = True , reuse = None , summarize = False , for_save = False )

Предлагает возможность выполнить любую предварительную обработку или проверку выражения перед его добавлением в запрос. resolve_expression() также должен вызываться для любого вложенного выражения. Копия copy() из self должна быть возвращена с необходимыми преобразованиями.

query - реализация запроса на базе движка базы данных.

allow_joins - логическое значение, разрешающее или запрещающее использование объединений в запросе.

reuse представляет собой набор объединений, которые можно повторно использовать в нескольких сценариях объединения.

summarize является логическим значением, которое, когда оно установлено True , указывает, что вычисляемый запрос является запросом агрегирования терминала.

for_save - логическое значение, которое, если оно установлено True , указывает, что выполняемый запрос выполняет создание или обновление.

get_source_expressions()

Возвращает отсортированный список внутренних выражений. Например :

>>> Sum(F('foo')).get_source_expressions()
[F('foo')]
set_source_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 такое же, как self .

get_group_by_cols( псевдоним = Нет )

Отвечает за возврат списка столбцов, на которые ссылается это выражение. get_group_by_cols() должен вызываться для любого вложенного выражения. F() В частности, объекты содержат ссылку на столбец. Параметр alias будет, None если выражение не аннотировано и не используется при группировке.

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

Параметр alias добавлен.

asc( nulls_first = Ложь , nulls_last = Ложь )

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

nulls_first и nulls_last определить способ сортировки нулевых значений. См. Пример использования в разделе Использование F () для сортировки нулевых значений .

desc( nulls_first = Ложь , nulls_last = Ложь )

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

nulls_first и nulls_last определить способ сортировки нулевых значений. См. Пример использования в разделе Использование F () для сортировки нулевых значений .

reverse_ordering()

Возвращает self с любыми изменениями, необходимыми для отмены порядка сортировки в вызове order_by . Например, реализующее выражение изменит свое значение на . Изменения требуются только для выражений, реализующих порядок сортировки, например . Этот метод вызывается при вызове запроса set ( ).NULLS LAST NULLS FIRST OrderBy reverse() QuerySet

Написание собственных выражений запросов

Вы можете написать свои собственные классы выражений запросов, которые используют и могут интегрироваться с другими выражениями запросов. Давайте рассмотрим пример, написав реализацию функции 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().__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, sql_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_nomfournisseur() переопределять данные, используемые для генерации строки SQL. Лучше использовать параметры, названные по имени, для as_sql() настройки, чем преобразовывать self в методы, as_nomfournisseur() поскольку это может привести к ошибкам при работе с другими механизмами базы данных. Если ваш класс полагается на атрибуты класса для определения данных, рассмотрите возможность разрешения перегрузок в вашем методе 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-инъекций

Поскольку именованные параметры функции Func для __init__() ( **extra ) и as_sql() ( **extra_context ) интерполируются для формирования строки SQL и не передаются в качестве параметров запроса (где драйвер базы данных позаботился бы об их экранировании), они не должны содержать небезопасные данные, отправленные пользователями.

Например, если содержимое substring исходит от пользователя, эта функция уязвима для SQL-инъекции.

from django.db.models import Func

class Position(Func):
    function = 'POSITION'
    template = "%(function)s('%(substring)s' in %(expressions)s)"

    def __init__(self, expression, substring):
        # substring=substring is an SQL injection vulnerability!
        super().__init__(expression, substring=substring)

Эта функция создает строку SQL без параметров. Поскольку substring передается super().__init__() как именованный параметр, он интерполируется в строку SQL перед отправкой запроса в базу данных.

Вот исправленная перезапись

class Position(Func):
    function = 'POSITION'
    arg_joiner = ' IN '

    def __init__(self, expression, substring):
        super().__init__(substring, expression)

Когда substring передается как позиционный параметр, он затем передается как параметр в запрос к базе данных.

Добавлена ​​поддержка внешних движков баз данных

Если вы используете ядро ​​базы данных, которое использует другой синтаксис 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() .

Copyright ©2021 All rights reserved