Метод annotate

Метод annotate применяется к QuerySet и возвращает новый QuerySet, каждый объект которого дополнен новым атрибутом - аннотацией. В качестве аргументов метод принимает именованные параметры, где имя становится ключом аннотации, а значение - это выражение для вычисления значения. Это может быть агрегатная функция (Count, Sum, Avg и другие), выражение F или любая другая функция базы данных.

Синтаксис

pythonModel.objects.annotate(**annotations)

Пример . С Count

Посчитаем количество комментариев к каждой статье. Для этого используем агрегатную функцию Count, передав в неё имя связанного поля:

pythonfrom myapp.models import Article
from django.db.models import Count

articles_with_comments = Article.objects.annotate(
  comments_count=Count('comments')
)

for article in articles_with_comments:
  print(f"{article.title}: {article.comments_count} comments")

Результат выполнения кода:

python"Article 1: 5 comments"
"Article 2: 2 comments"
"Article 3: 0 comments"

Пример . С Sum

Рассчитаем общую сумму всех заказов для каждого пользователя. Используем аннотацию с функцией Sum:

pythonfrom myapp.models import User, Order
from django.db.models import Sum

users_with_total = User.objects.annotate(
  total_orders_amount=Sum('orders__amount')
)

for user in users_with_total:
  print(f"{user.username}: ${user.total_orders_amount or 0}")

Результат выполнения кода:

python"john: $1500"
"jane: $3200"
"bob: $0"

Пример . С Avg

Вычислим средний рейтинг для каждого продукта. Для пустых наборов значений результат будет равен None, поэтому можно использовать Coalesce для замены:

pythonfrom myapp.models import Product, Review
from django.db.models import Avg, Coalesce, Value
from django.db.models.functions import Coalesce

products_with_rating = Product.objects.annotate(
  avg_rating=Coalesce(Avg('reviews__rating'), Value(0))
)

for product in products_with_rating:
  print(f"{product.name}: {product.avg_rating:.1f}")

Результат выполнения кода:

python"Laptop: 4.5"
"Mouse: 4.8"
"Keyboard: 0.0"

Пример . С F-выражениями

Создадим аннотацию, которая вычисляет значение на основе других полей модели. Например, посчитаем итоговую цену с учётом скидки:

pythonfrom myapp.models import Product
from django.db.models import F, ExpressionWrapper, FloatField

products_with_discount = Product.objects.annotate(
  price_with_discount=ExpressionWrapper(
    F('price') * (1 - F('discount') / 100),
    output_field=FloatField()
  )
)

for product in products_with_discount:
  print(f"{product.name}: ${product.price_with_discount:.2f}")

Результат выполнения кода:

python"Laptop: $850.00"
"Mouse: $24.00"
"Keyboard: $45.00"

Пример . С условиями в аннотации

Добавим аннотацию, которая показывает статус товара на основе количества на складе. Используем Case и When:

pythonfrom myapp.models import Product
from django.db.models import Case, When, Value, IntegerField

products_with_status = Product.objects.annotate(
  stock_status=Case(
    When(stock_quantity__gte=100, then=Value('Много')),
    When(stock_quantity__gte=50, then=Value('Достаточно')),
    When(stock_quantity__gt=0, then=Value('Мало')),
    default=Value('Нет в наличии'),
    output_field=IntegerField()
  )
)

for product in products_with_status:
  print(f"{product.name}: {product.stock_status}")

Результат выполнения кода:

python"Laptop: Много"
"Mouse: Достаточно"
"Keyboard: Мало"

Смотрите также

  • метод aggregate,
    который возвращает итоговые агрегированные значения
  • метод alias,
    который создаёт аннотации без добавления их в результаты запроса
  • класс Count,
    который используется для подсчёта количества записей
  • класс Sum,
    который используется для суммирования значений поля