Ми познайомилися з основними агрегатними функціями, такими як 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. Тепер ви знаєте, як легко додавати обчислювані дані у свої запити!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ