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-выражений в фильтрации

  1. Сравнение значений полей:

    • F-выражения позволяют сравнивать значения полей модели между собой внутри запроса.

    • Пример:

      Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks")) - выбрать записи блога, у которых количество комментариев больше, чем количество отзывов.

  2. Арифметические операции с F-выражениями:

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

    • Пример:

      Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2) - выбрать записи блога, у которых количество комментариев больше, чем удвоенное количество отзывов.

  3. Использование двойного подчеркивания для связей:

    • Можно использовать двойное подчеркивание для связей внутри F-выражения. Это добавляет необходимые соединения для доступа к связанным объектам.

    • Пример:

      Entry.objects.filter(authors__name=F("blog__name")) - выбрать записи блога, где имя автора совпадает с именем блога.

  4. Работа с датой и временем:

    • Для полей даты и времени можно добавлять или вычитать объект timedelta. Например, чтобы выбрать все записи, которые были изменены более чем через 3 дня после публикации:

      from datetime import timedelta
       
      Entry.objects.filter(mod_date__gt=F("pub_date") + timedelta(days=3))
  5. Также F выражения можно использовать совместно с функциями преобразования и агрегации, но мы их ещё не проходили))

Дополнительные способы использования F-выражений

  1. Использование F() с аннотациями:

    • F() можно использовать для создания динамических полей в моделях путем комбинирования различных полей с арифметическими операциями.
    • Пример: Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs")) - добавить аннотацию с полем chairs_needed, равным разнице между num_employees и num_chairs.
  2. Использование 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()
          )
      )
  3. Ссылки на связанные поля:

    • При использовании F() с полями, которые являются связями, такими как ForeignKey, F() возвращает значение первичного ключа, а не экземпляр модели.

    • Пример:

      car = Company.objects.annotate(built_by=F("manufacturer"))[0]
      car.manufacturer  # Вернет экземпляр модели Manufacturer
      car.built_by      # Вернет значение первичного ключа производителя (manufacturer)
  4. Сортировка значений 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),будут перечислены в конце коллекции
  5. Логические операции с F():

    • Начиная с Django 4.2, F() выражения, возвращающие значения типа BooleanField, могут быть логически инвертированы с помощью оператора инверсии ~F().
    • Пример: Company.objects.update(is_active=~F("is_active")) - инвертировать статус активации компаний.

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 запроса.

Как это работает:

  1. Вы передаете annotate() одно или несколько выражений.

    Эти выражения могут быть:

    • Простыми значениями (константами);
    • Ссылками на поля модели;
    • Агрегирующими функциями (например, CountSumAvg).
  2. Django включает эти выражения в SQL запрос. Результат вычисления присваивается каждому объекту в полученном QuerySet. Эти атрибуты не сохраняются в базе данных, они доступны только в рамках текущего QuerySet.

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

Пример:

Представим, у вас есть модель 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().

  • Фильтровать можно по именам, заданным аннотированным полям, будто это поля модели.
  • Фильтровать можно по аннотациям, полученным через агрегатные функции (например, CountAvg).

Пример: Найти книги с более чем одним автором:

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"))

〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰