JavaRush /Курсы /Модуль 3: Django /Практическое занятие по агрегациям и аннотациям

Практическое занятие по агрегациям и аннотациям

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

Итак, мы продолжаем работать над нашим общего проекта — веб-приложения для управления библиотекой. На данном этапе у нас есть несколько моделей: Book, Author и Publisher. Теперь мы будем использовать агрегации, аннотации, Q- и F-объекты для выполнения различных аналитических запросов.

Вот наша текущая модель базы данных:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    birth_date = models.DateField(null=True, blank=True)

    def __str__(self):
        return self.name

class Publisher(models.Model):
    name = models.CharField(max_length=100)
    country = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, related_name='books')
    price = models.DecimalField(max_digits=10, decimal_places=2)
    publication_date = models.DateField()

    def __str__(self):
        return self.title

Задача 1: общая стоимость книг

Давайте начнем с простого: нам нужно узнать, какова общая стоимость всех книг в нашей библиотеке. Конечно же, мы будем использовать функцию aggregate().

from django.db.models import Sum

total_price = Book.objects.aggregate(total=Sum('price'))
print(total_price)

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

  • aggregate(Sum('price')): возвращает словарь, где ключом будет имя, которое мы указали для результата (total), а значением — общая сумма поля price у всех записей в таблице Book.

Вывод будет выглядеть так:

{'total': Decimal('10753.50')}

--

Задача 2: средняя цена книг для каждого издателя

Теперь нам нужно вычислить среднюю стоимость книг, выпущенных каждым издателем. Здесь нам поможет функция annotate().

from django.db.models import Avg

publishers = Publisher.objects.annotate(avg_price=Avg('books__price'))
for publisher in publishers:
    print(f"{publisher.name}: {publisher.avg_price}")

Разбор:

  • annotate(avg_price=Avg('books__price')): добавляет вычисляемое поле avg_price к каждому объекту Publisher. Это поле содержит среднюю цену всех книг, связанных с этим издателем.

Пример вывода:

O'Reilly Media: 25.50
Penguin Books: 18.75
Random House: 20.00

Задача 3: количество книг, выпущенных после 2020 года

Теперь мы используем фильтрацию и аннотацию, чтобы подсчитать количество книг, выпущенных каждым издателем после 2020 года.

from django.db.models import Count

publishers = Publisher.objects.annotate(
    books_after_2020=Count('books', filter=models.Q(books__publication_date__year__gt=2020))
)

for publisher in publishers:
    print(f"{publisher.name}: {publisher.books_after_2020} книг после 2020 года")

Что нового?

  • Новый параметр filter в функции аннотации позволяет применить фильтрацию только для аннотируемых данных.
  • Q(books__publication_date__year__gt=2020): фильтрует книги с годом публикации больше 2020.

Задача 4: найти автора с максимальным количеством книг

Как насчет того, чтобы узнать, кто из авторов написал больше всего книг? Для этого подойдет функция annotate с Count.

author = Author.objects.annotate(book_count=Count('books')).order_by('-book_count').first()
print(f"Самый продуктивный автор: {author.name}, {author.book_count} книг.")
  • Count('books'): считает количество связанных книг для каждого автора.
  • .order_by('-book_count'): сортирует авторов по количеству книг в порядке убывания.
  • .first(): возвращает первого автора из отсортированного списка.

Задача 5: сравнение цен на книги одного издателя

Теперь сложнее: как сравнить цену определенной книги со средней ценой других книг того же издателя? Для этого пригодятся F-объекты.

from django.db.models import F

books = Book.objects.annotate(
    publisher_avg_price=Avg('publisher__books__price')
).filter(price__lt=F('publisher_avg_price'))

for book in books:
    print(f"{book.title} дешевле средней цены у издателя {book.publisher.name}.")

Новый элемент:

  • F('publisher_avg_price'): позволяет работать со значением вычисляемого поля прямо в запросе.

Задача 6: сложный запрос с Q-объектами

Давайте найдем всех авторов, у которых есть хотя бы одна книга дешевле 10 долларов или стоимость книги превышает 100 долларов у издателя из США.

q_authors = Author.objects.filter(
    Q(books__price__lt=10) | Q(books__price__gt=100, books__publisher__country='USA')
).distinct()

for author in q_authors:
    print(f"Автор: {author.name}")

Разбор:

  • Q(): позволяет объединить условия с помощью логического ИЛИ.
  • .distinct(): убирает дублирующиеся записи, если автор подходит под несколько условий сразу.

Задача 7: количество книг по году

Для дополнительного челенджа давайте подсчитаем количество книг, опубликованных в каждый год.

from django.db.models.functions import TruncYear

yearly_books = Book.objects.annotate(year=TruncYear('publication_date')).values('year').annotate(count=Count('id')).order_by('year')

for entry in yearly_books:
    print(f"Год: {entry['year']}, Количество книг: {entry['count']}")

Пояснения:

  • TruncYear('publication_date'): позволяет извлечь год из даты публикации.
  • .values('year'): группирует результаты по году.
  • .annotate(count=Count('id')): считает количество книг для каждого года.

Итог

Вот и всё! Мы использовали агрегации, аннотации, Q- и F-объекты для решения реальных задач, касающихся нашего проекта. Все примеры, которые мы рассмотрели, могут быть легко адаптированы для решения ваших собственных задач в веб-разработке.

Если вы чувствуете себя немного перегруженными, это нормально. Возвращайтесь к этой лекции при необходимости и постепенно экспериментируйте с разными подходами. Помните, как говорил один мудрый разработчик: "Наилучший способ узнать что-то новое — это закоммитить это в продакшен... Но лучше всё-таки в локальной копии!" 😊

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