SQLGROUP BYaggregationtutorial

Что такое GROUP BY в SQL? Группировка строк для начинающих

GROUP BY — это команда «схлопни строки в группы и посчитай каждую». Простыми словами: как получить «сколько заказов у каждого клиента» одной строкой кода. Что можно и нельзя писать в SELECT, разница с DISTINCT, GROUP BY по нескольким колонкам и частые ошибки.

4 мин чтенияСправочникSQL · GROUP BY · aggregation · tutorial

GROUP BY — это команда «схлопни одинаковые строки в одну и посчитай по ним что-нибудь». Самый базовый способ получить статистику: «сколько заказов у каждого клиента», «сумма продаж по каждому товару», «средняя зарплата по департаменту».

Представь таблицу заказов. Без GROUP BY каждая строка — отдельный заказ, и их сотни. С GROUP BY customer_id все заказы одного клиента превращаются в одну строку, в которой можно посмотреть итоги: сколько у него заказов всего, на какую сумму, последний когда.

Зачем нужен GROUP BY

SELECT без GROUP BY отвечает на вопрос «покажи мне строки». С GROUP BY — на вопрос «покажи мне сводку по группам». Это две разные задачи.

Типичные сценарии:

  • Сколько пользователей зарегистрировалось в каждой стране.
  • Какая средняя цена товара в каждой категории.
  • Сколько раз каждый клиент совершал покупку.
  • Топ-10 самых популярных тегов на блоге.

GROUP BY плюс агрегатная функция (COUNT, SUM, AVG, MIN, MAX) — это и есть «отчёт».

Базовый синтаксис

SELECT country, COUNT(*) AS users_count
FROM users
GROUP BY country;

Что происходит:

  1. Postgres берёт все строки из users.
  2. Группирует их по country — все одинаковые страны вместе.
  3. Для каждой группы выполняет COUNT(*) — считает строки в группе.
  4. Возвращает по одной строке на группу.

Пример с таблицей

Есть таблица users:

id name country
1 Аня RU
2 Боб US
3 Вера RU
4 Гриша RU
5 Дима US

Запрос:

SELECT country, COUNT(*) AS users_count
FROM users
GROUP BY country;

Результат:

country users_count
RU 3
US 2

5 строк превратились в 2 — одна на каждую страну. У России три юзера (Аня, Вера, Гриша), у США два (Боб, Дима).

Самые частые агрегаты

SELECT
  category,
  COUNT(*)        AS items_count,    -- сколько строк
  SUM(price)      AS total_price,    -- сумма
  AVG(price)      AS avg_price,      -- среднее
  MIN(price)      AS cheapest,       -- минимум
  MAX(price)      AS expensive       -- максимум
FROM products
GROUP BY category;

Каждая группа теперь даёт одну строку со всеми этими цифрами сразу.

GROUP BY по нескольким колонкам

Можно группировать по комбинации:

SELECT country, tier, COUNT(*) AS users_count
FROM users
GROUP BY country, tier;

Группа = пара (country, tier). Если есть Россия с tier=free и Россия с tier=gold — это две разные группы.

Результат может выглядеть так:

country tier users_count
RU free 47
RU gold 12
US free 23
US gold 8

Главное правило: что можно в SELECT

В SELECT после GROUP BY можно класть только две вещи:

  1. Колонки, по которым группируешь (country в нашем примере).
  2. Агрегатные функции (COUNT, SUM, AVG, MIN, MAX).

Положить туда что-то другое — например, name — нельзя:

-- НЕПРАВИЛЬНО
SELECT country, name, COUNT(*) FROM users GROUP BY country;
-- ERROR: column "users.name" must appear in the GROUP BY clause...

Логика: в группе RU три юзера — Аня, Вера, Гриша. Какое из трёх имён показать? Postgres не угадает. Поэтому либо добавь name в GROUP BY (тогда группы станут (country, name)), либо вынеси в агрегат (MAX(name) — алфавитно последнее).

В MySQL это правило исторически было слабее — он молча возвращал «случайное» имя. Это поведение убрано в строгом режиме, но если работаешь со старыми MySQL — будь осторожен.

GROUP BY vs DISTINCT — в чём разница

Оба «схлопывают» дубликаты, но цели разные:

  • SELECT DISTINCT country FROM users — даёт уникальные страны (RU, US). Никаких счётчиков.
  • SELECT country, COUNT(*) FROM users GROUP BY country — то же самое плюс счётчик в каждой группе.

Если просто хочешь уникальные значения — DISTINCT короче. Если нужна агрегация — только GROUP BY.

ORDER BY с GROUP BY

После группировки ты можешь сортировать как обычно:

-- Топ-5 стран по количеству юзеров
SELECT country, COUNT(*) AS users_count
FROM users
GROUP BY country
ORDER BY users_count DESC
LIMIT 5;

ORDER BY сортирует результат группировки, а не исходные строки.

Частые ошибки новичков

1. Положили в SELECT колонку, которой нет в GROUP BY. Самая типичная ошибка для новичков. Postgres падает с понятной ошибкой — но в MySQL до недавнего времени просто возвращал «любое» значение из группы.

2. Фильтрация агрегата через WHERE. WHERE фильтрует строки до группировки и не знает про COUNT/SUM. Для условий на агрегаты есть отдельный HAVING:

-- НЕПРАВИЛЬНО
SELECT country, COUNT(*) FROM users
GROUP BY country
WHERE COUNT(*) > 100;

-- ПРАВИЛЬНО
SELECT country, COUNT(*) FROM users
GROUP BY country
HAVING COUNT(*) > 100;

WHERE режет строки на входе, HAVING режет группы на выходе.

3. COUNT(*) vs COUNT(column). COUNT(*) считает все строки, включая те, где значения NULL. COUNT(email) считает только строки, где email IS NOT NULL. На пустых колонках разница огромна.

4. Сортировка по колонке, которой нет в SELECT. В ORDER BY после GROUP BY можно сортировать только по тем же колонкам, что и в SELECT, или по агрегатам (или по их алиасам).

5. NULL в GROUP BY. Все строки с NULL попадают в одну группу (Postgres трактует NULL = NULL для целей группировки). Если ожидал, что NULL пропадёт — добавь WHERE country IS NOT NULL.

6. GROUP BY по дробным числам или датам. GROUP BY price — у каждой цены своя группа, групп будет столько же, сколько уникальных значений. Обычно бессмысленно. Для дат используй DATE_TRUNC('month', created_at) — тогда группы будут по месяцам, а не посекундно.

Мини-резюме

  • GROUP BY column — схлопывает строки в группы, по одной на уникальное значение.
  • В SELECT можно класть только колонки из GROUP BY или агрегаты (COUNT, SUM, AVG, MIN, MAX).
  • Несколько колонок — GROUP BY a, b — группа = пара (a, b).
  • Фильтр на агрегаты — HAVING, не WHERE.
  • Уникальные значения без счёта — короче через DISTINCT.
  • NULL'ы все попадают в одну группу — фильтруй заранее, если не хочешь их видеть.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр