4

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

  • строить иерархические отчёты

  • объединяться с оконными функциями

  • …и при этом легко устроить тебе день боли, если ты не знаешь, что делаешь 😅