HAVING: фильтрация групп
HAVING: фильтрация групп
В предыдущем уроке вы научились группировать строки с помощью GROUP BY и применять агрегатные функции к каждой группе. Теперь встаёт следующая задача: что если нужно отобрать только те группы, которые соответствуют определённому условию? Например, показать только отделы с более чем тремя сотрудниками, или категории товаров с суммарной выручкой выше миллиона. Для фильтрации групп существует клауза HAVING — аналог WHERE, но применяемый после группировки.
WHERE vs HAVING: ключевое различие
Прежде чем разбирать синтаксис, важно понять принципиальное различие:
WHEREфильтрует строки до группировки — работает с исходными даннымиHAVINGфильтрует группы после группировки — работает с результатами агрегатных функций
-- WHERE: фильтрация строк до группировки
SELECT department, COUNT(*) AS headcount
FROM employees
WHERE salary > 80000 -- убираем строки с маленькой зарплатой ПЕРЕД группировкой
GROUP BY department;
-- HAVING: фильтрация групп после группировки
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department
HAVING COUNT(*) > 2; -- убираем группы с малым числом сотрудников ПОСЛЕ группировки
Это разные задачи и разные результаты.
Синтаксис HAVING
SELECT колонка_группировки, агрегат(колонка)
FROM таблица
WHERE условие_на_строки -- опционально
GROUP BY колонка_группировки
HAVING условие_на_агрегат; -- фильтрация групп
Пример — отделы с более чем двумя сотрудниками:
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department
HAVING COUNT(*) > 2;
Таблица employees (5 строк: 3 разработчика, 1 менеджер, 1 дизайнер):
department | headcount
------------+----------
Разработка | 3
Только «Разработка» с тремя сотрудниками прошла фильтр > 2. Менеджмент и Дизайн с по одному сотруднику — отброшены.
HAVING с числовыми агрегатами
Самый частый случай — фильтрация по SUM или AVG:
-- Категории товаров с выручкой выше 1 млн рублей
SELECT category,
COUNT(*) AS products_sold,
SUM(total_amount) AS revenue
FROM sales
GROUP BY category
HAVING SUM(total_amount) > 1000000
ORDER BY revenue DESC;
-- Отделы со средней зарплатой выше 90000
SELECT department,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 90000;
Проверь себя: что вернёт HAVING COUNT(*) >= 1 для группировки по department? Это то же самое, что не использовать HAVING?
HAVING с псевдонимами
В PostgreSQL в HAVING можно использовать псевдоним из SELECT... а можно и нет — зависит от версии PostgreSQL и конкретного случая. Безопаснее всегда повторять выражение:
-- Безопасно: повторить выражение
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department
HAVING COUNT(*) > 2;
-- В PostgreSQL обычно работает, но не стандартно:
HAVING headcount > 2;
Чтобы не зависеть от особенностей реализации, повторяйте агрегатное выражение в HAVING явно.
WHERE и HAVING вместе
Часто нужны оба фильтра одновременно: сначала исключить «плохие» строки, потом отфильтровать маленькие группы.
-- Активные сотрудники (WHERE), отделы с более чем 2 активными (HAVING)
SELECT department,
COUNT(*) AS active_count,
AVG(salary) AS avg_salary
FROM employees
WHERE status = 'active' -- строки: только активные
GROUP BY department
HAVING COUNT(*) > 2; -- группы: только отделы с 3+
Логика выполнения:
FROM employees— берём всю таблицуWHERE status = 'active'— оставляем только активныхGROUP BY department— группируем оставшихся по отделуHAVING COUNT(*) > 2— берём только группы с 3+ сотрудникамиSELECT— формируем итоговые строки
HAVING с несколькими условиями
Как и в WHERE, в HAVING можно комбинировать несколько условий через AND и OR:
SELECT department,
COUNT(*) AS headcount,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING COUNT(*) >= 2
AND AVG(salary) > 80000;
Это вернёт отделы, где одновременно и не менее двух сотрудников и средняя зарплата выше 80000.
Или — используя OR:
HAVING COUNT(*) > 5 OR SUM(salary) > 1000000
Группа пройдёт фильтр, если выполняется хотя бы одно из условий.
Правила приоритета операторов (AND > OR) и использование скобок для явности — те же, что в WHERE.
Разница: когда что использовать
Типичный вопрос новичков: «мне нужно отфильтровать по зарплате — WHERE или HAVING?»
Ответ зависит от задачи:
WHERE salary > 80000 — перед группировкой. При группировке по отделам в каждый отдел попадут только сотрудники с зарплатой выше 80000. COUNT(*) посчитает только таких.
HAVING AVG(salary) > 80000 — после группировки. В каждый отдел сначала попадут все сотрудники, потом СУБД посчитает среднюю зарплату, и отделы с AVG ≤ 80000 будут исключены.
Запросы семантически разные:
- «Отделы, где у всех зарплата выше 80000» — это сложнее (нужен подзапрос)
- «Отделы, где среди тех, у кого зарплата выше 80000, больше 2 сотрудников» →
WHERE + HAVING - «Отделы, где в среднем зарплата выше 80000» → только
HAVING
Если не уверены — подумайте: фильтрация происходит до или после подсчёта?
HAVING без GROUP BY
Технически HAVING можно использовать без GROUP BY. В этом случае вся таблица рассматривается как одна группа:
SELECT COUNT(*) AS total
FROM employees
HAVING COUNT(*) > 3;
Вернёт одну строку с COUNT(*), если в таблице более 3 строк. Если нет — вернёт 0 строк. Это редкий, но рабочий сценарий — например, для проверки «есть ли хотя бы N строк». На практике чаще используют подзапрос или EXISTS для таких проверок, но HAVING COUNT(*) > N без GROUP BY тоже встречается в коде.
HAVING и NULL в агрегатах
Помните: COUNT(col) не считает NULL. Это влияет на условия HAVING:
SELECT department, COUNT(phone) AS with_phone
FROM employees
GROUP BY department
HAVING COUNT(phone) >= 2;
Этот запрос вернёт только отделы, где хотя бы 2 сотрудника имеют заполненный телефон. Сотрудники с phone IS NULL не учитываются в COUNT(phone). Если использовать COUNT(*), считались бы все строки — это другая семантика.
Выбор между COUNT(*) и COUNT(col) в HAVING зависит от задачи: считать всех сотрудников или только тех, у кого конкретное поле заполнено.
Практический пример: топ-категории
Задача для e-commerce: найти категории товаров, по которым продано более 100 единиц и средний чек выше 5000 рублей, отсортированные по суммарной выручке:
SELECT category,
COUNT(*) AS orders_count,
SUM(quantity) AS units_sold,
SUM(total_amount) AS revenue,
ROUND(AVG(total_amount), 2) AS avg_check
FROM orders
WHERE status = 'completed'
GROUP BY category
HAVING SUM(quantity) > 100
AND AVG(total_amount) > 5000
ORDER BY revenue DESC
LIMIT 10;
Здесь HAVING содержит два условия через AND — группа проходит фильтр, только если выполняются оба условия одновременно. Запрос возвращает топ-10 прибыльных категорий среди тех, которые активно продаются с высоким средним чеком.
Типичная ошибка: WHERE вместо HAVING
Самая частая ошибка при работе с агрегатами:
-- Ошибка: COUNT(*) в WHERE
SELECT department, COUNT(*) AS cnt
FROM employees
WHERE COUNT(*) > 2 -- ERROR: aggregate functions not allowed in WHERE
GROUP BY department;
-- Правильно: COUNT(*) в HAVING
SELECT department, COUNT(*) AS cnt
FROM employees
GROUP BY department
HAVING COUNT(*) > 2;
PostgreSQL явно скажет «aggregate functions not allowed in WHERE». Правило простое: агрегатные функции — только в HAVING и SELECT, не в WHERE.
Порядок клауз: итоговая схема
SELECT ... -- что возвращаем
FROM ... -- источник
WHERE ... -- фильтр строк (до группировки)
GROUP BY ... -- группировка
HAVING ... -- фильтр групп (после группировки)
ORDER BY ... -- сортировка
LIMIT ... -- ограничение
OFFSET ... -- сдвиг
Этот порядок фиксирован. Нарушение любого шага вызовет синтаксическую ошибку.
Краткий итог
HAVINGфильтрует группы послеGROUP BY;WHEREфильтрует строки до группировки- Синтаксис:
GROUP BY col HAVING агрегат(col) > значение - В
HAVINGможно использовать те же агрегатные функции, что и вSELECT WHEREиHAVINGможно использовать вместе: разные этапы фильтрацииHAVINGбезGROUP BYрассматривает всю таблицу как одну группу- Агрегатные функции нельзя использовать в
WHERE— только вHAVINGиSELECT
Что дальше
Вы освоили HAVING. В следующем уроке — группировка по нескольким колонкам: как комбинировать несколько измерений в одном запросе и что меняется, когда групп несколько.