Skip to content

Latest commit

 

History

History
424 lines (277 loc) · 11.7 KB

File metadata and controls

424 lines (277 loc) · 11.7 KB

Модуль 5. Урок 30. Агрегирующие функции Count, Sum, Avg, Max, Min. Метод values()

Работа с данными практически всегда предполагает не только выборку, но и аналитику. Сколько фильмов в базе? Какой средний рейтинг? Какое количество фильмов относится к определённому жанру? Какие годы выпуска встречаются чаще?

Django ORM предоставляет мощные инструменты для вычислений прямо на уровне базы данных — агрегирующие функции и метод values().

В этом уроке мы научимся работать с аналитикой, разберём типовые ошибки и проведём несколько небольших проверок через браузер.


1. Что такое агрегирующие функции?

Агрегации — это операции, которые выполняются над группой записей, например:

  • Сколько записей?
  • Какое минимальное значение?
  • Какой средний рейтинг?
  • Какова общая сумма бюджетов?

В Django агрегирующие функции находятся в модуле:

from django.db.models import Count, Sum, Avg, Max, Min

И применяются с помощью метода aggregate(), который всегда возвращает один результат, упакованный в словарь.

Например:

Movie.objects.aggregate(Count("id"))

Вернёт словарь:

{"id__count": 57}

Так работает Django: имя ключа создаётся автоматически — имяполя + __ + функция_.


#3 2. Метод count(): самый простой способ посчитать записи

Метод count() — это специальный «быстрый путь» для подсчёта количества строк.

Например, подсчитаем количество фильмов:

Movie.objects.count()

SQL, который построит Django:

SELECT COUNT(*) AS "__count" FROM "cinemahub_movie";

Почему использовать count() правильно?

  • Django не загружает записи в память.
  • Подсчёт выполняется на уровне базы (быстро и эффективно).
  • Безопасно для больших таблиц.

Если вы когда-то встречали код вроде:

len(Movie.objects.all())

— это антипаттерн. Такой код загружает ВСЕ фильмы в память, а затем считает длину списка.


3. Основные агрегирующие функции на примерах

Предположим, у нас есть модель:

class Movie(models.Model):
    title = models.CharField(max_length=200)
    year = models.IntegerField()
    rating = models.FloatField()
    budget = models.IntegerField(null=True, blank=True)
    genre = models.ForeignKey("Genre", on_delete=models.SET_NULL, null=True)

Пример 1: минимальный год выпуска

Movie.objects.aggregate(Min("year"))

Результат:

{"year__min": 1979}

Пример 2: максимальный рейтинг

Movie.objects.aggregate(Max("rating"))

Пример 3: минимальный и максимальный бюджет сразу

Movie.objects.aggregate(
    min_budget=Min("budget"),
    max_budget=Max("budget")
)

Пример 4: средний рейтинг фильмов

Movie.objects.aggregate(Avg("rating"))

Если в таблице много NULL-значений, Django будет игнорировать их — агрегаты работают корректно.

Пример 5: сумма бюджетов всех фильмов

Movie.objects.aggregate(Sum("budget"))

Пример 6: несколько функций сразу

Movie.objects.aggregate(
    Sum("budget"),
    Avg("rating"),
    Max("year"),
    Min("year"),
)

Можно считать этот метод маленьким аналитическим отчётом.


4. Выполнение математических операций внутри aggregate()

Django ORM поддерживает арифметику прямо внутри агрегирующих выражений.

Например, вычислим разницу между суммой и средним бюджетом:

Movie.objects.aggregate(
    diff=Sum("budget") - Avg("budget")
)

Или, например, вычислим среднюю стоимость оценки:

Movie.objects.aggregate(
    value=Avg("budget") / Avg("rating")
)

Важно: Если вы используете выражение, обязательно назначайте имя:

Movie.objects.aggregate(result=...)

Иначе Django просто не знает, какой ключ использовать.


5. Агрегации + фильтрация

Как и в обычных запросах, вы можете использовать .filter():

Например, количество фильмов с рейтингом выше 8:

Movie.objects.filter(rating__gt=8).aggregate(Count("id"))

Или сумма бюджетов жанра «Комедия»:

Movie.objects.filter(
    genre__name="Комедия"
).aggregate(Sum("budget"))

6. Метод values(): выборка конкретных полей

Метод values() позволяет запрашивать только часть колонок — не всю строку.

Например, выберем только названия и годы:

Movie.objects.values("title", "year")

Результат:

[
    {"title": "Inception", "year": 2010},
    {"title": "Interstellar", "year": 2014},
]

Это удобно, когда:

  • Вы делаете отчёты,
  • Готовите данные под JSON,
  • Вам не нужна вся модель.

7. values() и связанные таблицы

Запросим название фильма и название жанра:

Movie.objects.values("title", "genre__name")

Django построит SQL с JOIN:

SELECT movie.title, genre.name
FROM movie
JOIN genre ON movie.genre_id = genre.id;

8. Ленивость ORM: запрос не выполняется сразу

Очень важно понимать, что пока вы не обратились к данным, SQL-запрос не выполнен.

movies = Movie.objects.values("title")

На этом шаге — запрос не отправлен. Но когда вы пройдёте по QuerySet в цикле:

for m in movies:
    print(m["title"])

Django выполнит запрос ровно один раз.

Это делает ORM очень эффективным: вы можете собирать сложные цепочки фильтраций и аннотаций, и только в конце данные действительно загружаются.


9. Проверяем результат в браузере

Для простейшей проверки можно создать временную вьюшку:

def stats(request):
    data = Movie.objects.aggregate(
        total=Count("id"),
        avg_rating=Avg("rating"),
        max_year=Max("year")
    )
    return HttpResponse(str(data))

После обновления страницы в браузере вы увидите:

{"total": 57, "avg_rating": 7.4, "max_year": 2023}

Убедитесь, что вы:

  1. Перешли по правильному URL.
  2. Действительно создали фильмы в базе.
  3. Не забыли миграции.

10. Типовые ошибки и как их исправлять

Ошибка 1: «Cannot aggregate… unsupported operand type»

Причина: попытка сложить два агрегата без имени.

Правильно:

Movie.objects.aggregate(result=Sum("budget") - Avg("budget"))

Неправильно:

Movie.objects.aggregate(Sum("budget") - Avg("budget"))

Ошибка 2: использование len() вместо count()

len(Movie.objects.all())   # плохо
Movie.objects.count()      # правильно

Ошибка 3: values() не возвращает объекты модели

Некоторые новички ожидают:

movies = Movie.objects.values("title")
print(movies[0].title)  # AttributeError

Правильный доступ:

movies[0]["title"]

Практические задания

Все задания можно выполнить через Django shell или временную вьюшку.


Задание 1.

Посчитайте количество фильмов, выпущенных после 2010 года.


Задание 2.

Найдите:

  • минимальный рейтинг
  • максимальный рейтинг
  • средний рейтинг

Задание 3.

Получите список словарей вида:

{"title": "...", "genre__name": "..."}

Задание 4.

Подсчитайте количество фильмов в каждом жанре (используйте values() + annotate()).


Задание 5.

Вычислите:

(средний бюджет всех фильмов) - (минимальный бюджет)

Задание 6.

Получите список всех годов выпуска без повторений с помощью:

Movie.objects.values("year")

И попробуйте вывести их в браузер.


Задание 7.

Подсчитайте количество фильмов с рейтингом выше среднего.


Задание 8.

Найдите общую сумму бюджетов всех фильмов жанра «боевик».


Вопросы

  1. Что делает метод aggregate() и что он возвращает?
  2. Почему count() эффективнее, чем len(QuerySet)?
  3. Что вернёт Movie.objects.aggregate(Max("rating"))?
  4. Как получить только поля title и year без загрузки модели целиком?
  5. Что делает конструкция genre__name в методе values()?
  6. Происходит ли выполнение SQL-запроса при создании QuerySet?
  7. Почему values() возвращает dict, а не объект модели?
  8. Можно ли использовать арифметические операции в aggregate()?
  9. Что произойдёт, если попытаться использовать выражение в aggregate() без имени?
  10. В чём разница между count() и Count()?

Предыдущий урок | Следующий урок