JavaRush /Курсы /Модуль 3: Django /Работа с аннотациями с помощью annotate()

Работа с аннотациями с помощью annotate()

Модуль 3: Django
10 уровень , 3 лекция
Открыта

Мы познакомились с основными агрегатными функциями, такими как Sum, Avg и Count. Теперь настало время погрузиться в мощный инструмент Django ORM — annotate().

Аннотация — это способ добавления новых вычисляемых полей к каждому объекту в наборе данных QuerySet. В отличие от функции aggregate(), которая возвращает единый результат (например, сумму по всей таблице), annotate() добавляет вычисляемое значение для каждого отдельного объекта. Это позволяет работать с данными на более детализированном уровне.

Можно представить аналогию с таблицей Excel, где у вас есть список продаж. С помощью aggregate() вы можете посчитать общую сумму всех продаж. Но если вы хотите добавить новую колонку, в которой отображается, например, "общая стоимость продажи" (количество * цена), то это уже задача для annotate().

Базовая структура annotate()

Синтаксис метода annotate():

Model.objects.annotate(new_field=SomeFunction('existing_field'))
  • new_field — это имя поля, которое будет добавлено к каждому объекту.
  • SomeFunction — это агрегатная функция или выражение, которое вычисляет значение для каждого объекта.

annotate() возвращает новый QuerySet, где каждый объект имеет дополнительно поле new_field.

Использование annotate(): примеры

Давайте разбираться с примерами на практике! Предположим, у нас есть следующие модели:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
    copies_sold = models.IntegerField(default=0)

Пример 1: подсчёт количества связанных объектов

Задача: вывести каждого автора и количество книг, которые он написал.

from django.db.models import Count

authors = Author.objects.annotate(book_count=Count('books'))
for author in authors:
    print(f"{author.name} написал {author.book_count} книг.")

Что происходит?

  • Модель Author аннотируется новым полем book_count, содержащим количество связанных объектов из модели Book.

SQL за кулисами:

SELECT
    author.id,
    author.name,
    COUNT(book.id) AS book_count
FROM
    author
LEFT OUTER JOIN
    book ON author.id = book.author_id
GROUP BY
    author.id, author.name;

Пример 2: сумма значений

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

from django.db.models import Sum, F

authors = Author.objects.annotate(total_earnings=Sum(F('books__price') * F('books__copies_sold')))
for author in authors:
    print(f"{author.name} заработал {author.total_earnings} единиц.")

Что здесь нового?

  • Мы используем F-объекты для ссылок на поля модели (более подробно об этом в Лекции 97).
  • С помощью Sum Django подсчитывает сумму произведений price и copies_sold для каждой книги.

Совмещение annotate() с фильтрацией

Вы можете комбинировать annotate() с фильтрацией. Однако порядок выполнения имеет значение: аннотация создаёт дополнительные поля до применения фильтров.

Пример 3: фильтрация после аннотации

Задача: найти всех авторов, которые написали больше 3 книг.

authors = Author.objects.annotate(book_count=Count('books')).filter(book_count__gt=3)
for author in authors:
    print(f"{author.name} написал {author.book_count} книг.")

Пример 4: использование фильтрации внутри аннотации

Задача: для каждого автора подсчитать количество книг, проданных более чем в 1000 экземпляров.

authors = Author.objects.annotate(popular_books=Count('books', filter=models.Q(books__copies_sold__gt=1000)))
for author in authors:
    print(f"{author.name} написал {author.popular_books} популярных книг.")

Разница между Примером 3 и Примером 4:

  • В Примере 3 мы сначала аннотируем, а потом фильтруем весь QuerySet.
  • В Примере 4 фильтрация применяется внутри функции Count.

Использование annotate() для сложных вычислений

annotate() поддерживает более сложные вычисления, включая вложенные функции. Давайте рассмотрим пример:

Пример 5: вычисление средней цены книг

Задача: найти среднюю цену книг для каждого автора.

from django.db.models import Avg

authors = Author.objects.annotate(avg_price=Avg('books__price'))
for author in authors:
    print(f"Средняя цена книг автора {author.name}: {author.avg_price}")

Пример 6: вложенные аннотации

Задача: найти авторов, чьи книги в среднем продаются более чем в 1000 экземплярах.

authors = Author.objects.annotate(
    avg_copies=Avg('books__copies_sold')
).filter(avg_copies__gt=1000)
for author in authors:
    print(f"{author.name} имеет средний тираж: {author.avg_copies}")

В чём разница между annotate() и aggregate()?

Особенность annotate() aggregate()
Результат Добавляет вычисляемое поле к каждому объекту Возвращает один результат (словарь)
Уровень детализации Обрабатывает каждый объект в наборе данных Обрабатывает весь набор данных целиком
Примеры применения Средний тираж книг по автору Средний тираж всех книг в базе данных

Особенности и типичные ошибки

  1. Аннотация после фильтрации может дать неожиданные результаты. Если вы фильтруете QuerySet перед аннотацией, важно понимать, что аннотация не "видит" записи, исключённые фильтром. Это часто приводит к путанице, особенно для начинающих.

    # Неправильно:
    authors = Author.objects.filter(books__price__gt=20).annotate(book_count=Count('books'))
    
    # Правильно:
    authors = Author.objects.annotate(book_count=Count('books', filter=models.Q(books__price__gt=20)))
  2. Объединение с другими выражениями. Не забывайте, что аннотации работают в рамках SQL-запросов. Слишком сложные или многочисленные аннотации могут привести к снижению производительности.

  3. Переопределение имен полей. Если вы аннотируете новое поле с именем, уже существующим в модели, это значение затрёт "оригинал" в результирующем QuerySet.

Где применять в реальной жизни?

  • Аналитика данных: подсчёт средних заказов пользователя, популярности товаров, подсчёт посещений сайта.
  • Оптимизация запросов: вместо того чтобы выполнять дополнительные вычисления в коде Python, перенесите их в запросы базы данных.
  • Создание сложных отчётов: вывод данных с подсчётом, средними значениями и комбинированной аналитикой.

Надеюсь, после этой лекции аннотации больше не будут казаться магической функцией ORM. Теперь вы знаете, как легко добавлять вычисляемые данные в свои запросы!

1
Задача
Модуль 3: Django, 10 уровень, 3 лекция
Недоступна
Создание примера аннотации
Создание примера аннотации
1
Задача
Модуль 3: Django, 10 уровень, 3 лекция
Недоступна
Сумма выручки с книг
Сумма выручки с книг
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ