GROUP BY
Как получать сводные итоги по отдельным группам
Теория
Для этого есть оператор GROUP BY.
Он делит значения столбца на группы по какому-то признаку, а агрегатная функция считает итог внутри каждой такой группы.
Если написать запрос SELECT COUNT(*) FROM orders, мы получим одно общее количество заказов.
Но если нужно понять, сколько заказов со статусом 'создан', сколько 'оплачен', сколько 'доставлен', такое вычисление не подойдет.
Здесь и нужно использовать GROUP BY.
Можно думать об этом так: агрегатная функция отвечает на вопрос «что посчитать?», а GROUP BY отвечает на вопрос «по каким группам это посчитать?».
Обычно необходимость этой конструкции в SQL запросе можно заподозрить по таким формулировкам:
• сколько пользователей в каждом городе;
• сколько товаров в каждой категории;
• какая средняя зарплата по каждому отделу;
• сколько заказов у каждого статуса.
Главная идея темы: без GROUP BY агрегатные функции считают результат в одну итоговую строку, а с GROUP BY — по одной строке на каждую группу.
Синтаксис
SELECT column_name,
aggregate_function(other_column)
FROM table_name
GROUP BY column_name;
SQL
Дальше идёт FROM, а после него — GROUP BY с тем же столбцом, по которому нужно собрать одинаковые значения вместе.
Например, если написать GROUP BY city, то все пользователи из одного и того же города попадут в одну группу. После этого COUNT(*) сможет посчитать, сколько строк оказалось в каждой такой группе.
Но есть важное правило. Если в SELECT вы выводите дополнительный столбец, а не агрегатную функцию, этот столбец должен участвовать в GROUP BY. Иначе база данных не поймёт, как именно собрать строки.
В PostgreSQL группировать можно не только по самому столбцу, но и по его псевдониму или по позиционному номеру из SELECT.
Например, если нужный столбец в запросе стоит первым, можно написать GROUP BY 1. Но в учебных и рабочих запросах чаще всё же лучше писать явное имя столбца или понятный псевдоним, потому что такой код легче читать.
Ещё один важный момент: NULL тоже может участвовать в группировке. Если в столбце есть несколько строк с NULL, они соберутся в одну отдельную группу.
После группировки результат можно отсортировать через ORDER BY.
Это очень удобно, когда нужно, например, показать сначала самые большие группы.
Примеры
1. Сколько работников на каждой должности
🔄 Попробуйте изменить запрос:
- • Уберите ORDER BY и посмотрите, как изменится порядок вывода
- • Замените position на department_id и получите количество работников по отделам
2. Средняя зарплата по отделам
🔄 Попробуйте изменить запрос:
- • Поменяйте AVG(salary) на MAX(salary) и посмотрите максимальную зарплату по отделам
- • Добавьте перед группировкой фильтр WHERE salary >= 70000 и сравните результат
3. Сколько пользователей в каждом городе
🔄 Попробуйте изменить запрос:
- • Уберите COALESCE и посмотрите, как будет выглядеть группа с NULL
- • Замените текст 'город не указан' на 'неизвестный город'
4. Сколько заказов по каждому статусу в 2024 году
🔄 Попробуйте изменить запрос:
- • Поменяйте диапазон дат на BETWEEN '2023-01-01' AND '2023-12-31'
- • Уберите COALESCE и посмотрите, как отобразятся заказы без статуса
5. Сколько заказов у каждого работника в каждом статусе
🔄 Попробуйте изменить запрос:
- • Добавьте фильтр WHERE order_date >= '2024-01-01' и сравните результат
- • Попробуйте заменить worker_id на user_id и получить группировку по пользователям и статусам
Типичные ошибки
Выводят в SELECT столбец, которого нет в GROUP BY
Если столбец не обёрнут в агрегатную функцию, он должен участвовать в группировке. Иначе СУБД не поймёт, как его показать рядом с итогами по группе.
Ждут, что GROUP BY сам что-то считает без агрегатной функции
Сама по себе группировка только собирает одинаковые значения вместе. Чтобы получить количество, сумму, среднее или максимум, нужна агрегатная функция: COUNT(), SUM(), AVG(), MAX() или MIN().
Забывают, что WHERE работает раньше группировки
Сначала SQL отбирает строки по условию, и только потом собирает оставшиеся строки в группы. Поэтому фильтр может сильно менять итоговые цифры.
Пугаются группы с NULL
Если в столбце есть несколько строк с NULL, они не пропадут. Они соберутся в одну отдельную группу. Если хотите показать её понятнее, можно использовать COALESCE().
Группируют пользователей по имени и фамилии вместо идентификатора
На первый взгляд кажется, что GROUP BY first_name, last_name — нормальная идея. Но в реальной базе может оказаться два разных человека с одинаковыми именем и фамилией. Тогда их строки сольются в одну группу, и цифры получатся неверными. Если нужен итог по конкретному пользователю, безопаснее группировать по user_id.
Практика
Проверь себя
Ответьте на вопросы, чтобы закрепить материал: