F() expressions
Описание и преимущества
F класс в Django ORM - это инструмент, позволяющий напрямую работать с полями модели в базе данных при выполнении запросов и обновлений.
Его преимущества:
- Прямое манипулирование базой данных: вместо того, чтобы извлекать значения полей в Python, выполнять вычисления и затем сохранять их обратно, F выражения позволяют вам поручить эти операции базе данных.
- SQL выражения: F объекты транслируются в SQL выражения, которые описывают операции, которые вы хотите выполнить над полем базы данных.
- Предотвращение race conditions: F выражения, где обновления выполняются базой данных, помогают предотвратить “условия гонки”, которые могут возникать, если несколько Python процессов пытаются одновременно изменить одно и то же поле. То есть в случае если несколько Python процессов загружает в память объект и как-то трансформируют его, а затем сохраняют, то эти сохранения будут перезаписывать друг друга и мы просто потеряем часть изменённой информации!
Подход с использованием F() выражения:
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 переопределяет оператор
+
чтобы он сгенерировал SQL выражение “увеличить поле в базе данных”. - Главное: увеличение счетчика происходит на стороне базы данных; Python не оперирует фактическим значением.
- Python, используя F класс, только генерирует нужный SQL.
Плюсы:
- Производительность: Работа непосредственно с базой данных часто быстрее, чем многократное извлечение/ сохранение данных в Python.
- Корректность: Избегание переноса данных в Python уменьшает риск одновременных изменений, которые могут вызвать проблемы.
Warning
После использования F выражения, обязательно нужно обновить объект из базы данных (
reporter.refresh_from_db()
), чтобы продолжить с ним дальнейшую работу! Иначе, при каждом сохранении объекта, будет происходить выполнение SQL-выражения, которое сохранено в атрибутеstories_filed
в памяти python.
Особенности использования F-выражений в фильтрации
-
Сравнение значений полей:
-
F-выражения позволяют сравнивать значения полей модели между собой внутри запроса.
-
Пример:
Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks"))
- выбрать записи блога, у которых количество комментариев больше, чем количество отзывов.
-
-
Арифметические операции с F-выражениями:
-
Django поддерживает использование арифметических операций с F-выражениями, как с константами, так и с другими F-выражениями.
-
Пример:
Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2)
- выбрать записи блога, у которых количество комментариев больше, чем удвоенное количество отзывов.
-
-
Использование двойного подчеркивания для связей:
-
Можно использовать двойное подчеркивание для связей внутри F-выражения. Это добавляет необходимые соединения для доступа к связанным объектам.
-
Пример:
Entry.objects.filter(authors__name=F("blog__name"))
- выбрать записи блога, где имя автора совпадает с именем блога.
-
-
Работа с датой и временем:
-
Для полей даты и времени можно добавлять или вычитать объект
timedelta
. Например, чтобы выбрать все записи, которые были изменены более чем через 3 дня после публикации:from datetime import timedelta Entry.objects.filter(mod_date__gt=F("pub_date") + timedelta(days=3))
-
-
Также F выражения можно использовать совместно с функциями преобразования и агрегации, но мы их ещё не проходили))
Дополнительные способы использования F-выражений
-
Использование F() с аннотациями:
- F() можно использовать для создания динамических полей в моделях путем комбинирования различных полей с арифметическими операциями.
- Пример:
Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
- добавить аннотацию с полемchairs_needed
, равным разнице междуnum_employees
иnum_chairs
.
-
Использование F() с выражением
ExpressionWrapper
:-
При комбинировании полей с разными типами, вам нужно указать Django, какой тип поля будет возвращен. Для этого используется
ExpressionWrapper
. -
Пример:
from django.db.models import DateTimeField, ExpressionWrapper, F Ticket.objects.annotate( expires=ExpressionWrapper( F("active_at") + F("duration"), output_field=DateTimeField() ) )
-
-
Ссылки на связанные поля:
-
При использовании F() с полями, которые являются связями, такими как ForeignKey, F() возвращает значение первичного ключа, а не экземпляр модели.
-
Пример:
car = Company.objects.annotate(built_by=F("manufacturer"))[0] car.manufacturer # Вернет экземпляр модели Manufacturer car.built_by # Вернет значение первичного ключа производителя (manufacturer)
-
-
Сортировка значений null:
-
Можно использовать F() с ключевыми словами
nulls_first
илиnulls_last
в методахasc()
илиdesc()
для управления порядком сортировки null значений поля. По умолчанию порядок сортировки зависит от вашей базы данных. -
Пример:
from django.db.models import F Company.objects.order_by(F("last_contacted").desc(nulls_last=True)) # вернёт коллекцию компаний, отсортированную по дате последнего контакта # при этом компании, с которыми не было контактов (Null),будут перечислены в конце коллекции
-
-
Логические операции с F():
- Начиная с Django 4.2, F() выражения, возвращающие значения типа
BooleanField
, могут быть логически инвертированы с помощью оператора инверсии ~F(). - Пример:
Company.objects.update(is_active=~F("is_active"))
- инвертировать статус активации компаний.
- Начиная с Django 4.2, F() выражения, возвращающие значения типа
Value class
Основная идея:
- Объект
Value
представляет собой неделимое значение (число, булево значение, строку) внутри более сложных SQL выражений, которые формирует Django. - Чаще всего вам не придется использовать
Value
напрямую. Когда вы пишетеF('field') + 1
, Django автоматически оборачивает значение1
вValue
, чтобы можно было строить выражения. Value
становится жизненно необходимым, когда вы хотите явно указать, что некое значение - это строка, а не имя поля модели. БезValue
конструкции типаLower('name')
будут восприняты как работа с полем “name”.
Синтаксис:
*class* Value(*value*, *output_field=None*)
-
value
: Само значение, которое нужно превратить в часть SQL выражения. -
output_field
: В большинстве случаев Django сам способен правильно вывести тип поля в базе данных, основываясь на типе Python в параметреvalue
. Например, если вы передаете объектdatetime.datetime
, Django поймет, что это значение нужно сохранить в поле типаDateTimeField
.В случаях, когда определение типа неоднозначно или вы хотите явно задать тип поля, используется
output_field
.
Note
Еще раз: обычно
Value
- это инструмент Django, используемый неявно. С ним напрямую вы столкнетесь в основном при работе со строками внутри SQL выражений
Annotate
Описание
Метод annotate()
позволяет динамически добавлять вычисляемые значения к объектам прямо во время выполнения запроса. Это похоже на добавление нового столбца к результату SQL запроса.
Как это работает:
-
Вы передаете
annotate()
одно или несколько выражений.Эти выражения могут быть:
- Простыми значениями (константами);
- Ссылками на поля модели;
- Агрегирующими функциями (например,
Count
,Sum
,Avg
).
-
Django включает эти выражения в SQL запрос. Результат вычисления присваивается каждому объекту в полученном
QuerySet
. Эти атрибуты не сохраняются в базе данных, они доступны только в рамках текущегоQuerySet
. -
Аннотации, заданные с помощью аргументов с ключевыми словами, будут использовать ключевое слово в качестве псевдонима для аннотации. Для анонимных аргументов будет сгенерирован псевдоним, основанный на имени агрегатной функции и агрегируемого поля модели. Анонимными аргументами могут быть только агрегатные выражения, ссылающиеся на одно поле. Все остальное должно быть аргументом с ключевым словом.
Пример:
Представим, у вас есть модель Blog
. Вы хотите посчитать количество записей для каждого блога:
from django.db.models import Count
# без указания имени
q = Blog.objects.annotate(Count("entry"))
# Выведем количество записей для первого блога
# имя сгенерировано автоматически из названия поля - entry и функции - count
q[0].entry__count
# с указанием имени
q = Blog.objects.annotate(number_of_entries=Count("entry"))
# Выведем количество записей для первого блога
q[0].number_of_entries
Аннотации и фильтрация
Аннотации позволяют добавлять вычисляемые значения к объектам прямо в запросе (как дополнительные столбцы в SQL). Эти аннотированные поля можно использовать в фильтрации, используя метод filter()
и exclude()
.
- Фильтровать можно по именам, заданным аннотированным полям, будто это поля модели.
- Фильтровать можно по аннотациям, полученным через агрегатные функции (например,
Count
,Avg
).
Пример: Найти книги с более чем одним автором:
Book.objects.annotate(num_authors=Count("authors")).filter(num_authors__gt=1)
Порядок annotate()
и filter()
ВАЖЕН!
- Фильтрация до
annotate()
повлияет на набор данных, участвующих в вычислении аннотации. - Фильтрация после
annotate()
используется для отбора объектов, основываясь на уже вычисленном аннотированном поле.
Пример с Count
:
# Сначала аннотация, потом filter - фильтр не влияет на вычисление
Publisher.objects.annotate(num_books=Count("book", distinct=True)).filter(book__rating__gt=3.0)
# Сначала filter, потом аннотация - фильтр ограничивает данные до вычисления
Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count("book"))
〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰