SQLLab
Все статьи

GROUP BY и HAVING в SQL: агрегация с фильтрацией

Разбираем GROUP BY и HAVING: как группировать данные, считать агрегаты и фильтровать группы. Отличие HAVING от WHERE с примерами.

1 марта 2026 г.·4 мин чтения·

Если хочешь посчитать количество заказов по каждому клиенту, средний чек по городам или найти продавцов с выручкой больше миллиона — тебе нужен GROUP BY. А если ещё и отфильтровать результат — HAVING.

GROUP BY: группируем строки

GROUP BY схлопывает строки с одинаковым значением в группу, к которой применяется агрегатная функция.

SELECT city, COUNT(*) AS orders_count
FROM orders
GROUP BY city;
cityorders_count
Москва142
Санкт-Петербург87
Казань34

Агрегатные функции, которые работают с GROUP BY:

ФункцияЧто делает
COUNT(*)Количество строк (включая NULL)
COUNT(col)Количество строк, где col не NULL
SUM(col)Сумма
AVG(col)Среднее
MIN(col) / MAX(col)Минимум / максимум

COUNT(*) и COUNT(col) — не одно и то же. Если в столбце есть NULL-значения, COUNT(col) посчитает меньше строк. Это удобно: COUNT(phone) покажет, у скольких клиентов заполнен телефон.

Важное правило

В SELECT при использовании GROUP BY можно указывать только:

  • Столбцы из GROUP BY
  • Агрегатные функции
-- ✅ Правильно
SELECT city, COUNT(*) FROM orders GROUP BY city;

-- ❌ Ошибка: customer_name не в GROUP BY и не агрегирован
SELECT city, customer_name, COUNT(*) FROM orders GROUP BY city;

Нюанс про MySQL: в MySQL при отключённом режиме ONLY_FULL_GROUP_BY такой запрос не упадёт с ошибкой, а вернёт случайное значение customer_name из группы. Это классический источник трудноуловимых багов. Всегда придерживайтесь стандарта и явно агрегируйте всё, что хотите видеть.

Группировка по нескольким полям

SELECT city, status, COUNT(*) AS cnt
FROM orders
GROUP BY city, status
ORDER BY city, cnt DESC;

Это создаст группу для каждой уникальной комбинации city + status. Например: ('Москва', 'completed') — одна группа, ('Москва', 'cancelled') — другая, ('Казань', 'completed') — третья.

HAVING: фильтруем группы

WHERE фильтрует строки до группировки. HAVINGпосле.

-- Города с более чем 50 заказами
SELECT city, COUNT(*) AS cnt
FROM orders
GROUP BY city
HAVING COUNT(*) > 50;

WHERE vs HAVING — в чём разница?

-- WHERE: убирает строки до группировки
SELECT city, COUNT(*) AS cnt
FROM orders
WHERE status = 'completed'   -- сначала берём только выполненные
GROUP BY city
HAVING COUNT(*) > 10;        -- затем фильтруем города

-- HAVING нельзя использовать без GROUP BY (почти всегда)

Запомни: WHERE — для строк, HAVING — для групп.

Совет по оптимизации: старайтесь максимально фильтровать в WHERE, а не в HAVING. WHERE выполняется до группировки и может использовать индексы — это уменьшает объём данных, попадающих в дорогостоящую операцию агрегации. HAVING фильтрует уже сгруппированный результат, когда индексы бесполезны.

Практические примеры

Топ клиентов по сумме покупок

SELECT customer_id, SUM(amount) AS total
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
HAVING SUM(amount) > 10000
ORDER BY total DESC
LIMIT 10;

Среднее время выполнения заказа по менеджерам

SELECT manager_id,
       COUNT(*) AS orders_cnt,
       ROUND(AVG(duration_days), 1) AS avg_days
FROM orders
GROUP BY manager_id
HAVING COUNT(*) >= 5   -- только те, у кого достаточно данных
ORDER BY avg_days;

Дни с аномальным количеством заказов

SELECT DATE(created_at) AS day,
       COUNT(*) AS cnt
FROM orders
GROUP BY DATE(created_at)
HAVING COUNT(*) > (SELECT AVG(daily_cnt) * 2
                   FROM (SELECT DATE(created_at), COUNT(*) AS daily_cnt
                         FROM orders GROUP BY DATE(created_at)) sub)
ORDER BY day;

Порядок выполнения SQL

Важно понимать, в каком порядке база выполняет запрос:

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

Поэтому нельзя использовать алиас из SELECT в HAVING:

-- ❌ Не сработает в большинстве СУБД
SELECT city, COUNT(*) AS cnt
FROM orders
GROUP BY city
HAVING cnt > 50;  -- cnt ещё не определён на этом этапе

-- ✅ Правильно
SELECT city, COUNT(*) AS cnt
FROM orders
GROUP BY city
HAVING COUNT(*) > 50;

Исключение: PostgreSQL и некоторые другие СУБД разрешают использовать алиасы из SELECT в ORDER BY, но не в HAVING.

Типичные вопросы на собеседовании

В чём разница между WHERE и HAVING? WHERE фильтрует строки до группировки, HAVING — группы после GROUP BY.

Можно ли использовать HAVING без GROUP BY? Технически да — тогда вся таблица считается одной группой. Но на практике это редкость.

Почему нельзя использовать агрегатные функции в WHERE? Потому что WHERE выполняется до агрегации — значений ещё нет.

Похожие статьи

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

Тренажёр с реальными задачами — бесплатно и без регистрации

Открыть тренажёр →