GROUP BY и HAVING: считаем по группам
Агрегатные функции без GROUP BY считают по всей таблице. GROUP BY позволяет считать отдельно для каждой группы.
Зачем GROUP BY
Один вопрос: "Какова общая выручка?" → SUM без GROUP BY.
Другой вопрос: "Какова выручка по каждому городу?" → SUM с GROUP BY.
Синтаксис
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city;
city | user_count
--------+-----------
Москва | 45
СПб | 23
Казань | 12
База разбила всех пользователей на группы по городу и посчитала количество в каждой.
Важное правило GROUP BY
В SELECT можно указывать только:
- Столбцы из GROUP BY
- Агрегатные функции
-- Ошибка: name не в GROUP BY и не агрегат
SELECT name, city, COUNT(*)
FROM users
GROUP BY city;
-- Правильно
SELECT city, COUNT(*)
FROM users
GROUP BY city;
Группировка по нескольким столбцам
SELECT city, age, COUNT(*) AS count
FROM users
GROUP BY city, age
ORDER BY city, age;
Создаёт группу для каждой уникальной комбинации город+возраст.
HAVING, фильтрация групп
WHERE фильтрует строки ДО группировки. HAVING фильтрует группы ПОСЛЕ:
-- Города с более 10 пользователей
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city
HAVING COUNT(*) > 10;
-- WHERE и HAVING вместе
SELECT city, COUNT(*) AS active_users
FROM users
WHERE is_active = true -- сначала фильтруем строки
GROUP BY city
HAVING COUNT(*) >= 5; -- потом фильтруем группы
Практический пример: отчёт по менеджерам
SELECT
manager_name,
COUNT(*) AS total_deals,
SUM(amount) AS total_revenue,
ROUND(AVG(amount), 0) AS avg_deal
FROM deals
WHERE status = 'won'
GROUP BY manager_name
HAVING COUNT(*) >= 5
ORDER BY total_revenue DESC;
💡 Главная мысль: GROUP BY разбивает данные на группы. HAVING фильтрует уже сгруппированные результаты (в отличие от WHERE, которое фильтрует до группировки).