PostgreSQLJuniorCoding

Что такое 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 обрабатывается в следующем логическом порядке (не физическом — планировщик вправе его переставить):

  1. FROM — строит исходный набор строк, включая JOIN и подзапросы.
  2. WHERE — отфильтровывает отдельные строки до группировки.
  3. GROUP BY — формирует группы по ключам.
  4. HAVING — фильтрует группы по условию на агрегаты.
  5. SELECT — вычисляет выражения и alias для каждой группы.
  6. ORDER BY — сортирует результат (может использовать alias из SELECT).
  7. 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-плана.

Sources

Related topics