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+

Логика выполнения:

  1. FROM employees — берём всю таблицу
  2. WHERE status = 'active' — оставляем только активных
  3. GROUP BY department — группируем оставшихся по отделу
  4. HAVING COUNT(*) > 2 — берём только группы с 3+ сотрудниками
  5. 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. В следующем уроке — группировка по нескольким колонкам: как комбинировать несколько измерений в одном запросе и что меняется, когда групп несколько.

Попробуйте интерактивную версию

Практические задачи, квизы и AI-наставник — бесплатный старт без карты

Перейти к практике