Мы познакомились с основными агрегатными функциями, такими как 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). - С помощью
SumDjango подсчитывает сумму произведений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() |
|---|---|---|
| Результат | Добавляет вычисляемое поле к каждому объекту | Возвращает один результат (словарь) |
| Уровень детализации | Обрабатывает каждый объект в наборе данных | Обрабатывает весь набор данных целиком |
| Примеры применения | Средний тираж книг по автору | Средний тираж всех книг в базе данных |
Особенности и типичные ошибки
Аннотация после фильтрации может дать неожиданные результаты. Если вы фильтруете
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)))Объединение с другими выражениями. Не забывайте, что аннотации работают в рамках SQL-запросов. Слишком сложные или многочисленные аннотации могут привести к снижению производительности.
Переопределение имен полей. Если вы аннотируете новое поле с именем, уже существующим в модели, это значение затрёт "оригинал" в результирующем
QuerySet.
Где применять в реальной жизни?
- Аналитика данных: подсчёт средних заказов пользователя, популярности товаров, подсчёт посещений сайта.
- Оптимизация запросов: вместо того чтобы выполнять дополнительные вычисления в коде Python, перенесите их в запросы базы данных.
- Создание сложных отчётов: вывод данных с подсчётом, средними значениями и комбинированной аналитикой.
Надеюсь, после этой лекции аннотации больше не будут казаться магической функцией ORM. Теперь вы знаете, как легко добавлять вычисляемые данные в свои запросы!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ