GROUP BY

GROUP BY объединяет строки с одинаковыми ключами группировки. После группировки запрос возвращает одну строку на группу.

SELECT state,
       COUNT(*) AS order_count,
       SUM(total) AS total_amount
FROM orders
GROUP BY state;

Все NULL одного ключа группировки попадают в одну группу. Это не означает, что NULL = NULL: так определена семантика группировки.

Что разрешено в SELECT

В переносимом SQL выражение в SELECT должно:

  • входить в GROUP BY;
  • быть агрегатной функцией;
  • либо однозначно зависеть от ключа группировки, если конкретная СУБД умеет доказывать функциональную зависимость.
SELECT user_id, state, COUNT(*)
FROM orders
GROUP BY user_id, state;

Нельзя бездумно выбирать orders.id в этом запросе: в группе несколько заказов, и непонятно, какой id вернуть.

Несколько уровней

SELECT user_id,
       CAST(created_at AS DATE) AS day,
       SUM(total) AS day_total
FROM orders
GROUP BY user_id, CAST(created_at AS DATE);

Функция в ключе группировки может требовать вычисления и сортировки/хеширования. Для регулярных отчетов иногда полезен вычисляемый столбец, функциональный индекс или предварительная агрегация — решение принимают по плану и профилю записи.

WHERE и HAVING

SELECT user_id, COUNT(*) AS paid_count
FROM orders
WHERE state = 'PAID'
GROUP BY user_id
HAVING COUNT(*) >= 3;

WHERE сократил входной набор, HAVING оставил группы с тремя и более заказами.

Ловушки

  • Группировка не гарантирует сортировку результата.
  • COUNT(column) игнорирует NULL, а COUNT(*) считает строки.
  • Группировка после соединения «один ко многим» может случайно умножить суммы.
  • Добавление столбца в GROUP BY меняет гранулярность результата.

Связано: Агрегатные функции, Оператор HAVING, Запрос из 2х таблиц, SQL оценка производительности.

Вопросы

Что является строкой результата? Одна группа, определенная набором ключей.

Сортирует ли GROUP BY? Нет, порядок гарантирует только ORDER BY.

Почему сумма после JOIN стала больше? Строки могли размножиться отношением «один ко многим» до агрегации.

Где фильтровать обычные строки? В WHERE, чтобы не тащить их в группировку.