JavaRush /Курси /Модуль 3: Django /Фільтрація даних з анотаціями

Фільтрація даних з анотаціями

Модуль 3: Django
Рівень 10 , Лекція 4
Відкрита

Уявіть, що у вас є інтернет-магазин, в якому потрібно знайти всіх покупців, які витратили більше певної суми за останній місяць. Як це зробити? Саме це ми і дізнаємось, заглибившись у annotate() та фільтрацію!

Найкраще те, що Django дозволяє поєднувати анотації та фільтрацію для отримання складних і витончених запитів. Після цього вам більше не доведеться писати SQL-запити вручну для 90% типових завдань. Давайте розберемося, як це робити.

Фільтрація анотованих даних: основна ідея

Коли ми застосовуємо анотації в Django ORM, ми створюємо додаткові обчислювані поля, які стають частиною нашого QuerySet. Після цього фільтрація з цими новими полями стає доступною — і саме це дозволяє комбінувати потужність анотацій з гнучкістю фільтрів.

Однак тут є нюанси. Важливо розуміти порядок застосування анотацій та фільтрів. Давайте розглянемо, як це працює.

Почнемо з простого прикладу. Уявімо, що у нас є наступна модель:

from django.db import models

class Order(models.Model):
    customer = models.CharField(max_length=255)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)

У нас є замовлення з полями: customer (ім'я покупця), amount (сума замовлення) і created_at (дата створення замовлення). Потрібно знайти всіх покупців, які зробили загальних замовлень на суму більше 1000.

Для цього нам знадобиться:

  1. Використати annotate() для обчислення суми всіх замовлень кожного покупця.
  2. Застосувати фільтр для цієї анотації.

Приклад коду:

from django.db.models import Sum
from .models import Order

# Обчислюємо суму замовлень кожного покупця
customers = Order.objects.values('customer') \
    .annotate(total_spent=Sum('amount')) \
    .filter(total_spent__gt=1000)

for customer in customers:
    print(customer)

Тут:

  • values('customer') групує дані за полем customer.
  • annotate(total_spent=Sum('amount')) додає обчислюване поле total_spent, яке містить суму всіх замовлень для кожного покупця.
  • filter(total_spent__gt=1000) фільтрує лише тих, у кого total_spent більше 1000.

Результат: список словників, де кожен словник містить customer та його total_spent.

Порядок анотацій та фільтрів

Порядок дій методів у Django ORM має значення. Давай подивимось, що станеться, якщо ми поміняємо місцями методи annotate() та filter():

# Неправильний порядок!
customers = Order.objects.filter(amount__gt=1000) \
    .values('customer') \
    .annotate(total_spent=Sum('amount'))

Цей запит не дасть потрібного результату, тому що ми спочатку фільтруємо замовлення на рівні окремих записів (amount__gt=1000), а потім анотуємо дані. У цьому випадку в розрахунках братимуть участь тільки замовлення з сумою більше 1000, що не відповідає нашій задачі.

Секрет успіху простий: завжди анотуйте дані перед фільтрацією, якщо хочете фільтрувати за анотованими значеннями.

Складні приклади фільтрації з анотаціями

Тепер розглянемо приклади складніше. Ми додамо кілька "реальних" кейсів, де фільтрація анотованих даних буде незамінною.

Приклад 1: Фільтрація за кількістю замовлень

Припустимо, ми хочемо знайти покупців, які зробили більше 5 замовлень.

from django.db.models import Count

# Рахуємо кількість замовлень кожного покупця
customers = Order.objects.values('customer') \
    .annotate(order_count=Count('id')) \
    .filter(order_count__gt=5)

for customer in customers:
    print(customer['customer'], "замовлень:", customer['order_count'])

Тут:

  • Count('id') рахує кількість рядків для кожного покупця.
  • filter(order_count__gt=5) обмежує результат тільки покупцями, у яких більше 5 замовлень.

Приклад 2: Вибірка покупців з "дорогими" замовленнями

Нам потрібно знайти всіх покупців, у яких є хоча б одна покупка на суму більше 500.

from django.db.models import Max

# Рахуємо максимальну суму замовлення для кожного покупця
customers = Order.objects.values('customer') \
    .annotate(max_order=Max('amount')) \
    .filter(max_order__gt=500)

for customer in customers:
    print(customer['customer'], "максимальне замовлення:", customer['max_order'])

Приклад 3: Фільтрація за місячними витратами

Тепер припустимо, що ми хочемо обчислити сумарні витрати покупців за поточний місяць.

from django.db.models import Sum
from django.utils.timezone import now
from datetime import timedelta

# Отримуємо дату початку місяця
start_date = now().replace(day=1)
end_date = start_date + timedelta(days=31)

# Фільтруємо замовлення за поточний місяць та анотуємо витрати покупців
customers = Order.objects.filter(created_at__range=(start_date, end_date)) \
    .values('customer') \
    .annotate(monthly_spent=Sum('amount')) \
    .filter(monthly_spent__gt=1000)

for customer in customers:
    print(f"{customer['customer']} витратив: {customer['monthly_spent']} за поточний місяць")

Тут:

  • filter(created_at__range=(start_date, end_date)) обмежує вибірку замовлень поточним місяцем.
  • annotate(monthly_spent=Sum('amount')) обчислює суму всіх замовлень за цей період.
  • filter(monthly_spent__gt=1000) залишає тільки покупців із загальними витратами більше 1000.

Типові помилки при фільтрації анотованих даних

  1. Фільтрація до анотації. Як ми вже обговорювали, важливо фільтрувати анотовані дані тільки після використання annotate(). Якщо зробити навпаки, фільтрація буде працювати не на анотованих значеннях, а на "сирих" даних, що призведе до неправильних результатів.

  2. Неправильне використання values(). Використання values() може обмежити вибрані поля, що іноді призводить до помилок. Наприклад, якщо ви використовуєте values('customer'), то інші поля, навіть анотовані, можуть бути виключені. Щоб цього уникнути, будьте уважні до того, як саме формується ваш запит.

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

Коли це знадобиться?

У багатьох випадках насправді. Наприклад:

  1. Аналіз покупок: підрахунок кількості або суми замовлень, вибірка "топових" покупців.
  2. Підготовка звітів: наприклад, для побудови аналітики щодо витрат користувачів.
  3. Оптимізація запитів: вибрати дані для відображення без завантаження зайвих записів.

Ці техніки корисні й у реальних проєктах, будь то інтернет-магазини, системи управління замовленнями або CRM.

Отже, з основами ми розібралися. Сподіваємося, що у вашому "дебагері життя" анотації та фільтрація тепер працюють чітко. А якщо раптом щось забудете або захочете заглибитися, зазирніть у документацію Django QuerySet, там багато корисного.

3
Опитування
Вступ до агрегацій в Django ORM, рівень 10, лекція 4
Недоступний
Вступ до агрегацій в Django ORM
Вступ до агрегацій в Django ORM
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ