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

Курс по Django: https://stepik.org/a/183363

На этом занятии мы с вами рассмотрим несколько агрегирующих методов. С одним из них, мы в принципе, уже знакомы – это метод count(), который подсчитывает число записей. В самом простом случае, с его помощью можно определить число записей в таблице women:

Women.objects.count()

В результате у нас формируется SQL-запрос с вызовом функции COUNT() на уровне самой СУБД:

SELECT COUNT(*) AS "__count" FROM "women_women"

То есть, число записей подсчитывается в БД, а нам лишь возвращается вычисленный результат. Это большой плюс такого подхода: нам нет необходимости передавать большие блоки данных лишь для того, чтобы, скажем, подсчитать число записей. Все делается «на месте» в БД и это часто заметно ускоряет подобные процессы. Именно поэтому так важны агрегирующие функции. Информацию о них можно почитать на странице документации:

https://docs.djangoproject.com/en/4.2/ref/models/querysets/#aggregation-functions

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

https://www.youtube.com/watch?v=KXdiuTOEFGA

Вообще ORM Django поддерживает следующие основные команды агрегации: Count, Sum, Avg, Max, Min. Для этого их вначале следует импортировать из ветки django.db.models:

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

И прописывать в специальном методе aggregate(). Например, воспользуемся моделью Husband и найдем минимальный возраст мужчин:

Husband.objects.aggregate(Min("age"))

На выходе получим словарь:

{'age__min': 30}

где ключ формируется, как имя поля, два подчеркивания и имя агрегирующей функции, а далее значение этого поля. В данном случае получили минимальный возраст, равный 30.

Также можно прописывать сразу несколько агрегирующих функций через запятую, например:

Husband.objects.aggregate(Min("age"), Max("age"))

Получим словарь:

{'age__min': 30, 'age__max': 101}

Здесь уже два ключа с соответствующими именами. Если по каким-либо причинам стандартные ключи нам не подходят, и мы бы хотели их поменять, то делается это так:

Husband.objects.aggregate(young=Min("age"), old=Max("age"))

на выходе получим:

{'young': 30, 'old': 101}

С агрегирующими значениями можно выполнять различные математические операции, например:

Husband.objects.aggregate(res=Sum("age") - Avg("age"))

причем параметр res в таком случае прописывать строго обязательно, имя ключа автоматически не генерируется.

По аналогии используются все остальные агрегирующие операции:

Women.objects.aggregate(Avg("id"))

или так:

Women.objects.filter(pk__gt=2).aggregate(res=Count("cat_id"))

Здесь агрегация выполняется не для всех записей, а только для тех, у которых id больше 2.

Метод values()

Во всех наших примерах выше, при выборке записей автоматически возвращались все поля. Если это была таблица women, то получали девять полей от id до husband_id. Но часто этого не требуется и достаточно ограничиться несколькими из них. Кроме того, такое ограничение положительно сказывается на скорости обращения к БД.

Итак, для указания нужных полей в выборке, используется метод values() с указанием названий полей, например, так:

Women.objects.values("title", "cat_id").get(pk=1)

На выходе имеем запись только с двумя полями. Причем, смотрите, если мы укажем взять данные из связанной таблицы для имени категории:

Women.objects.values("title", "cat__name").get(pk=1)

то ORM Django сформирует запрос с использованием оператора JOIN SQL-запроса:

SELECT "women_women"."title", "women_category"."name" FROM "women_women" INNER JOIN "women_category" ON ("women_women"."cat_id" = "women_category"."id") WHERE "women_women"."id" = 1 LIMIT 21

Благодаря такой конструкции одним запросом выбираются все нужные данные. Или, даже так:

w = Women.objects.values("title", "cat__name")

При выполнении этой строчки пока ни один SQL-запрос выполнен не был, т.к. запросы в ORM Django «ленивые», обращение к БД происходит только в момент получения данных. Но, если вывести список постов:

for p in w:
     print(p["title"], p["cat__name"])

то увидим, что для этой операции также был сделан всего один запрос. То есть, ORM Django достаточно хорошо оптимизирует процесс обращения к БД.

Курс по Django: https://stepik.org/a/183363

Видео по теме