GROUP BY - группировка или источник факапов

Все знают GROUP BY.
Тот самый оператор, который превращает кучу строк в аккуратную табличку с суммами и средними.

Но можно и по-другому взглянуть на GROUP BY

А пока подписывайся на мой канал На связи: SQL Там я публикую посты про особенности и нюансы SQL. Этот канал про то, как не бояться баз данных, понимать, что такое JOIN, GROUP BY и почему NULL ≠ 0. Его я веду с нуля подписчиков. Присоединяйся!

GROUP BY - группировка или источник факапов

В большинстве случаев GROUP BY используют вместе с агрегирующими функциями SUM, COUNT или AVG.

Но есть и другие возможности использования группировки.

  1. В качестве изящной замены DISTINCT
    SELECT department FROM employees GROUP BY department;
    работает так же, как
    SELECT DISTINCT department FROM employees;
  2. Группировать можно по выражениям, а не только по столбцам
    Например, хочешь посчитать заказы по годам:
    SELECT EXTRACT(YEAR FROM created_at) AS year, COUNT(*)
    FROM orders
    GROUP BY EXTRACT(YEAR FROM created_at);


    Или сгруппировать товары по тысячам рублей:
    SELECT (price / 1000)::int AS price_group, COUNT(*)
    FROM products
    GROUP BY (price / 1000)::int;
  3. GROUP BY умеет строить иерархии
    ROLLUP, CUBE, GROUPING SETS — три команды богов:
    SELECT region, city, SUM(sales)
    FROM orders
    GROUP BY ROLLUP (region, city);

    → покажет суммы по городам, по регионам и общий итог.
    И всё это одним запросом.
  4. NULL — это тоже группа
    Если у тебя несколько строк с NULL в поле department, то GROUP BY department соберёт их все в одну группу NULL.
    SELECT department, COUNT(*)
    FROM employees
    GROUP BY department;

    Логичней использовать COALESCE, чтобы потом не работать с пустыми строками
    SELECT COALESCE(department, 'Unknown') AS department, COUNT(*)
    FROM employees
    GROUP BY COALESCE(department, 'Unknown');
  5. SELECT vs GROUP BY — всё, что не агрегат, должно быть в GROUP BY
    SELECT department, name, COUNT(*)
    FROM employees
    GROUP BY department;


    Запрос упадёт, потому что name не в агрегате и не в GROUP BY.
    В PostgreSQL есть хитрости: можно использовать array_agg(name) или string_agg(name, ', ')
  6. GROUP BY и оконные функции — не конкуренты
    GROUP BY сжимает таблицу.
    OVER(PARTITION BY) — сохраняет строки, но добавляет агрегат.
    SELECT name, department, SUM(salary) OVER (PARTITION BY department) AS dep_total
    FROM employees;
  7. SQL сам решает, как группировать
    PostgreSQL может выбрать:
    HashAggregate — если данных много
    Sort + GroupAggregate — если их мало или мало уникальных значений
    То есть одна и та же команда GROUP BY под капотом работает по-разному.
    Вот почему один и тот же запрос на 10k строк работает мгновенно, а на 10M — вечность.
    PostgreSQL не просто тупо группирует строки, а выбирает стратегию (план выполнения) — как именно эту группировку реализовать.
    Это можно отследить в EXPLAIN и уже потом контролировать включением/выключением конкретных алгоритмов.
    SET enable_hashagg = off;
    SET enable_sort = off;

    Это полезно для тестирования или отладки - посмотреть, как изменится план.

GROUP BY — это не просто «посчитать среднюю зарплату по отделу».Это мощный инструмент, который может:

  • имитировать DISTINCT
  • строить иерархические отчёты
  • объединяться с оконными функциями
  • …и при этом легко устроить тебе день боли, если ты не знаешь, что делаешь 😅
Начать дискуссию