До сих пор мы разглядывали строки. Но половина реальных вопросов — не «покажи», а «посчитай»: сколько заказов, на какую сумму, какой средний чек. Для этого есть агрегатные функции — они схлопывают множество строк в одно число.
Пять функций
SELECT COUNT(*) FROM orders WHERE status = 'paid';
COUNT(*) — сколько строк. Остальные четыре работают со значениями столбца:
SUM(amount)— сумма;AVG(amount)— среднее;MIN(amount)/MAX(amount)— минимум и максимум.
Можно несколько сразу:
SELECT COUNT(*), SUM(amount), AVG(amount) FROM orders WHERE status = 'paid';
Один нюанс, который отличает профессионала: COUNT(*) считает строки, а COUNT(amount) — строки, где amount не пуст (не NULL). На чистых данных числа совпадут, на грязных — разойдутся, и эта разница сама по себе находка. А COUNT(DISTINCT customer_id) считает уникальные значения: не «сколько заказов», а «сколько разных клиентов заказывали».
GROUP BY: посчитать по группам
Одно число на всю таблицу — редко то, что нужно. Чаще нужно «в разрезе»: сколько заказов в каждом статусе. Это делает GROUP BY:
SELECT status, COUNT(*), SUM(amount)
FROM orders
GROUP BY status;
status | count | sum
created | 1 | 900
paid | 2 | 3700
База разложила строки по кучкам с одинаковым status и посчитала каждую кучку отдельно. Группировать можно по нескольким столбцам: GROUP BY status, customer_id — «по каждому клиенту в каждом статусе».
Главное правило GROUP BY: в SELECT могут стоять только столбцы из GROUP BY и агрегатные функции. Запрос SELECT status, amount … GROUP BY status не выполнится — база резонно спросит: «в кучке „paid" два разных amount, который из них показывать?»
HAVING: фильтр после подсчёта
WHERE фильтрует строки до группировки, и в нём нельзя использовать агрегаты. А если условие — на сам результат подсчёта («покажи клиентов, у которых больше пяти заказов»)? Для этого есть HAVING — фильтр после подсчёта:
SELECT customer_id, COUNT(*)
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 5;
Запомнить просто: WHERE — про строки, HAVING — про группы. Они спокойно живут вместе: WHERE status = 'paid' сначала оставит оплаченные, GROUP BY customer_id сгруппирует, HAVING SUM(amount) > 10000 оставит только крупных клиентов.
Где это применяется
Агрегаты — это сверка миров: интерфейс говорит «у вас 17 заказов» — COUNT(*) по базе обязан сказать то же самое; отчёт насчитал выручку — SUM её подтверждает или опровергает. Плюс поиск аномалий одной строкой: GROUP BY email HAVING COUNT(*) > 1 находит дубли пользователей, MIN(created_at) отвечает «с какого года у нас данные», сравнение COUNT(*) и COUNT(email) мгновенно показывает, у скольких записей не заполнена почта.
Где спотыкаются начинающие:
- Ставят агрегат в WHERE (
WHERE COUNT(*) > 5) — ошибка: условия на результат подсчёта живут в HAVING. - Смешивают в SELECT агрегаты и «просто столбцы», которых нет в GROUP BY. База честно отказывается угадывать, какое из многих значений показать.
- Считают по задвоенным строкам после JOIN. Сначала убедитесь, что соединение не размножило строки, потом считайте — иначе SUM и COUNT врут в разы.
Что учить дальше. У GROUP BY есть потолок: он схлопывает строки, а часто нужны и строки, и подсчёт разом — это делают оконные функции. А разница COUNT(*) и COUNT(столбец) приведёт вас к NULL и типам данных.