Django Group By в Python — практические примеры
Предложение SQL GROUP BY группирует строки, возвращаемые запросом, в группы в Python.
- Введение в группу Django
- Примеры с Django Group By
- 1) Пример Group By с подсчетом
- 2) Пример Django Group By с суммой
- 3) Пример Django group by с минимальным, максимальным и средним значением
- 4) Пример с объединением
- Django GROUP BY с HAVING
Введение в группу Django
Обычно вы используете агрегатные функции, такие как count, min, max, avg и sum с предложением GROUP BY, чтобы вернуть агрегированное значение для каждой группы. Рассмотрим, как использовать Django Group By с агрегатными функциями для расчета агрегации для групп.
Вот базовое использование предложения GROUP BY в операторе SELECT:
SELECT column_1, AGGREGATE(column_2) FROM table_name GROUP BY column1;
В Django вы можете использовать метод annotate() с values(), чтобы применить агрегацию к группам следующим образом:
(Entity.objects .values('column_2') .annotate(value=AGGREGATE('column_1')) )
В этом синтаксисе;
- values(‘column_2’) – передайте столбец, который вы хотите сгруппировать, в метод values().
- annotate(value=AGGREGATE(‘column_1’)) – укажите, что следует агрегировать в методе annotate().
Обратите внимание, что порядок вызова values() и annotates() имеет значение. Если вы не вызовете метод values() первым, а annotate() вторым, выражение не даст агрегированных результатов.
Примеры с Django Group By
Для демонстрации мы будем использовать модели Employee и Department из приложения HR. Модели Emloyee и Department сопоставляются с таблицами hr_employee и hr_department в базе данных:
1) Пример Group By с подсчетом
В следующем примере используются методы values() и annotate() для получения количества сотрудников по отделам:
>>>(Employee.objects ... .values('department') ... .annotate(head_count=Count('department')) ... .order_by('department') ... ) SELECT "hr_employee"."department_id", COUNT("hr_employee"."department_id") AS "head_count" FROM "hr_employee" GROUP BY "hr_employee"."department_id" ORDER BY "hr_employee"."department_id" ASC LIMIT 21 Execution time: 0.001492s [Database: default] <QuerySet [{'department': 1, 'head_count': 30}, {'department': 2, 'head_count': 40}, {'department': 3, 'head_count': 28}, {'department': 4, 'head_count': 29}, {'department': 5, 'head_count': 29}, {'department': 6, 'head_count': 30}, {'department': 7, 'head_count': 34}]>
Как это работает.
- Сначала сгруппируем сотрудников по отделам, используя метод values():
values('department')
- Во-вторых, примените Count() к каждой группе:
annotate(head_count=Count('department'))
- В-третьих, отсортируйте объекты в QuerySet по отделу:
order_by('department')
За кулисами Django выполняет оператор SELECT с предложением GROUP BY:
SELECT "hr_employee"."department_id", COUNT("hr_employee"."department_id") AS "head_count" FROM "hr_employee" GROUP BY "hr_employee"."department_id" ORDER BY "hr_employee"."department_id" ASC LIMIT 21
2) Пример Django Group By с суммой
Аналогичным образом можно использовать агрегат Sum() для расчета общей заработной платы сотрудников в каждом отделе:
>>>(Employee.objects ... .values('department') ... .annotate(total_salary=Sum('salary')) ... .order_by('department') ... ) SELECT "hr_employee"."department_id", SUM("hr_employee"."salary") AS "total_salary" FROM "hr_employee" GROUP BY "hr_employee"."department_id" ORDER BY "hr_employee"."department_id" ASC LIMIT 21 Execution time: 0.000927s [Database: default] <QuerySet [{'department': 1, 'total_salary': Decimal('3615341.00')}, {'department': 2, 'total_salary': Decimal('5141611.00')}, {'department': 3, 'total_salary': Decimal('3728988.00')}, {'department': 4, 'total_salary': Decimal('3955669.00')}, {'department': 5, 'total_salary': Decimal('4385784.00')}, {'department': 6, 'total_salary': Decimal('4735927.00')}, {'department': 7, 'total_salary': Decimal('4598788.00')}]>
3) Пример Django group by с минимальным, максимальным и средним значением
В следующем примере к группам применяется несколько агрегатных функций для получения самой низкой, средней и самой высокой зарплаты сотрудников в каждом отделе:
>>>(Employee.objects ... .values('department') ... .annotate( ... min_salary=Min('salary'), ... max_salary=Max('salary'), ... avg_salary=Avg('salary') ... ) ... .order_by('department') ... ) SELECT "hr_employee"."department_id", MIN("hr_employee"."salary") AS "min_salary", MAX("hr_employee"."salary") AS "max_salary", AVG("hr_employee"."salary") AS "avg_salary" FROM "hr_employee" GROUP BY "hr_employee"."department_id" ORDER BY "hr_employee"."department_id" ASC LIMIT 21 Execution time: 0.001670s [Database: default] <QuerySet [{'department': 1, 'min_salary': Decimal('45427.00'), 'max_salary': Decimal('149830.00'), 'avg_salary': Decimal('120511.366666666667')}, {'department': 2, 'min_salary': Decimal('46637.00'), 'max_salary': Decimal('243462.00'), 'avg_salary': Decimal('128540.275000000000')}, {'department': 3, 'min_salary': Decimal('40762.00'), 'max_salary': Decimal('248265.00'), 'avg_salary': Decimal('133178.142857142857')}, {'department': 4, 'min_salary': Decimal('43000.00'), 'max_salary': Decimal('238016.00'), 'avg_salary': Decimal('136402.379310344828')}, {'department': 5, 'min_salary': Decimal('42080.00'), 'max_salary': Decimal('246403.00'), 'avg_salary': Decimal('151233.931034482759')}, {'department': 6, 'min_salary': Decimal('58356.00'), 'max_salary': Decimal('248312.00'), 'avg_salary': Decimal('157864.233333333333')}, {'department': 7, 'min_salary': Decimal('40543.00'), 'max_salary': Decimal('238892.00'), 'avg_salary': Decimal('135258.470588235294')}]>
4) Пример с объединением
В следующем примере методы values() и annotate() используются для получения количества сотрудников в отделе:
>>>(Department.objects ... .values('name') ... .annotate( ... head_count=Count('employee') ... ) ... ) SELECT "hr_department"."name", COUNT("hr_employee"."id") AS "head_count" FROM "hr_department" LEFT OUTER JOIN "hr_employee" ON("hr_department"."id" = "hr_employee"."department_id") GROUP BY "hr_department"."name" LIMIT 21 Execution time: 0.001953s [Database: default] <QuerySet [{'name': 'Marketing', 'head_count': 28}, {'name': 'Finance', 'head_count': 29}, {'name': 'SCM', 'head_count': 29}, {'name': 'GA', 'head_count': 30}, {'name': 'Sales', 'head_count': 40}, {'name': 'IT', 'head_count': 30}, {'name': 'HR', 'head_count': 34}]>
Как это работает.
- values(‘name’) – группирует отделы по названию.
- annotate(headcount=Count(’employee’)) – подсчитывает количество сотрудников в каждом отделе.
В фоновом режиме Django использует LEFT JOIN для объединения таблицы hr_department с таблицей hr_employee и применяет функцию COUNT() к каждой группе.
Django GROUP BY с HAVING
Чтобы применить условие к группам, используйте метод filter(). Например, следующий пример использует метод filter() для получения отдела с количеством сотрудников более 30:
>>>(Department.objects ... .values('name') ... .annotate( ... head_count=Count('employee') ... ) ... .filter(head_count__gt=30) ... ) SELECT "hr_department"."name", COUNT("hr_employee"."id") AS "head_count" FROM "hr_department" LEFT OUTER JOIN "hr_employee" ON("hr_department"."id" = "hr_employee"."department_id") GROUP BY "hr_department"."name" HAVING COUNT("hr_employee"."id") > 30 LIMIT 21 Execution time: 0.002893s [Database: default] <QuerySet [{'name': 'Sales', 'head_count': 40}, {'name': 'HR', 'head_count': 34}]>
За кулисами Django использует предложение HAVING для фильтрации группы на основе условия, которое мы передаем методу filter():
SELECT "hr_department"."name", COUNT("hr_employee"."id") AS "head_count" FROM "hr_department" LEFT OUTER JOIN "hr_employee" ON("hr_department"."id" = "hr_employee"."department_id") GROUP BY "hr_department"."name" HAVING COUNT("hr_employee"."id") > 30