Что такое GROUP BY и в каком порядке выполняются клаузы (FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT)?
GROUP BY объединяет строки с одинаковыми ключами в группы для агрегатов; логический порядок клауз: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.
Что делает GROUP BY
GROUP BY объединяет строки с одинаковыми значениями указанных колонок в одну группу. После этого в SELECT можно использовать только те колонки, которые есть в GROUP BY, или агрегатные функции (COUNT, SUM, AVG, MIN, MAX). Попытка выбрать неагрегированную колонку вне GROUP BY — ошибка.
Логический порядок клауз
SQL обрабатывается в следующем логическом порядке (не физическом — планировщик вправе его переставить):
- FROM — строит исходный набор строк, включая JOIN и подзапросы.
- WHERE — отфильтровывает отдельные строки до группировки.
- GROUP BY — формирует группы по ключам.
- HAVING — фильтрует группы по условию на агрегаты.
- SELECT — вычисляет выражения и alias для каждой группы.
- ORDER BY — сортирует результат (может использовать alias из SELECT).
- LIMIT / OFFSET — обрезает итоговый набор.
Пример с разбором
SELECT
customer_id,
SUM(total_amount) AS revenue
FROM orders
WHERE status = 'paid' -- шаг 2: исключает неоплаченные до группировки
GROUP BY customer_id -- шаг 3: одна строка на клиента
HAVING SUM(total_amount) > 1000 -- шаг 4: только крупные клиенты
ORDER BY revenue DESC -- шаг 6: alias из SELECT уже виден
LIMIT 20; -- шаг 7
Физические стратегии агрегации в PostgreSQL
Планировщик выбирает между несколькими алгоритмами в зависимости от данных и индексов:
- HashAggregate — строит хэш-таблицу в памяти (work_mem); быстро для случайных ключей.
- GroupAggregate — требует отсортированного ввода; используется, когда данные уже отсортированы индексом.
- MixedAggregate — PostgreSQL 16+ может комбинировать подходы при partial aggregation для параллельных планов.
-- Посмотреть выбранную стратегию
EXPLAIN (ANALYZE, BUFFERS)
SELECT customer_id, SUM(total_amount) AS revenue
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
HAVING SUM(total_amount) > 1000
ORDER BY revenue DESC
LIMIT 20;
Функциональная зависимость
В PostgreSQL разрешено не перечислять в GROUP BY колонки, функционально зависящие от первичного ключа. Например, если группируем по id, то name той же таблицы можно выбрать без агрегата — PostgreSQL знает, что на каждый id одно name. Но полагаться на это стоит осторожно: при JOIN семантика усложняется.
Подводные камни
- ORDER BY не гарантирован без явного указания — не полагайтесь на «естественный» порядок GroupAggregate.
- HashAggregate может «упасть» на диск (temp files), если work_mem мал и групп много — смотрите
temp writtenв BUFFERS. - NULL-значения в GROUP BY образуют отдельную группу;
GROUP BY statusпри NULL даст группу NULL. - GROUP BY номером позиции (
GROUP BY 1, 2) — синтаксический сахар; при изменении SELECT-списка может сгруппировать не по тем колонкам. - HAVING SUM(...) > x и WHERE amount > x — разный смысл; поставить условие не туда — классическая ошибка junior-уровня.
- При большом количестве групп HashAggregate требует много памяти; индекс по GROUP BY-ключам может переключить план на GroupAggregate и сэкономить work_mem.
- GROUPING SETS, CUBE, ROLLUP — расширения GROUP BY для многомерной агрегации; не путайте с обычным GROUP BY при code review.
- Физический порядок не равен логическому — EXPLAIN может показать Filter до Group, если планировщик протолкнул предикат вниз.
Common mistakes
- Думать, что SELECT выполняется первым.
- Выводить неагрегированные поля без GROUP BY.
- Путать логический порядок с физическим планом.
What the interviewer is testing
- Просит объяснить доступность alias.
- Проверяет понимание агрегации по ключам.
- Уточняет разницу логики SQL и EXPLAIN-плана.