GROUP BY и HAVING: группировка без каши

GROUP BY я долго воспринимал как "что-то для отчетов". Потом поймал себя на простой задаче: нужно понять, сколько денег принесли оплаченные заказы и сколько заказов висит в ожидании. Обычный SELECT показывает строки, но не дает итогов. Значит, пора группировать

GROUP BY нужен, когда мы хотим не просто посмотреть данные, а свернуть строки в группы: по статусу, по клиенту, по городу, по месяцу, по категории. HAVING нужен, когда мы хотим отфильтровать уже получившиеся группы

Разберем это на заказах

Что получится в конце

Мы напишем запрос:

SELECT status, COUNT(*) AS orders_count, SUM(amount) AS total_amount
FROM orders
GROUP BY status
HAVING SUM(amount) > 5000
ORDER BY total_amount DESC;

Он сгруппирует заказы по статусу, посчитает количество и сумму, а потом оставит только группы, где сумма больше 5000

Готовим таблицу

CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer TEXT,
  city TEXT,
  amount INTEGER,
  status TEXT
);

Добавим данные:

INSERT INTO orders (id, customer, city, amount, status) VALUES
(1, 'Анна', 'Казань', 7900, 'paid'),
(2, 'Игорь', 'Москва', 4500, 'paid'),
(3, 'Мария', 'Самара', 1200, 'pending'),
(4, 'Олег', 'Казань', 15000, 'paid'),
(5, 'Светлана', 'Москва', 3900, 'cancelled'),
(6, 'Анна', 'Казань', 3200, 'paid'),
(7, 'Игорь', 'Москва', 2500, 'pending');

Обычный вывод:

SELECT customer, city, amount, status
FROM orders;

Это список строк. Теперь превратим его в отчет

COUNT: сколько заказов в каждом статусе

SELECT status, COUNT(*) AS orders_count
FROM orders
GROUP BY status;

Результат:

cancelled1
paid4
pending2

GROUP BY status собирает строки с одинаковым статусом в группы. COUNT(*) считает количество строк внутри каждой группы

SUM: сумма по группе

Добавим сумму:

SELECT
  status,
  COUNT(*) AS orders_count,
  SUM(amount) AS total_amount
FROM orders
GROUP BY status;

Результат:

cancelled13900
paid430600
pending23700

Теперь у нас уже маленький отчет по статусам

WHERE: фильтр до группировки

Допустим, мы хотим считать только заказы из Казани:

SELECT
  status,
  COUNT(*) AS orders_count,
  SUM(amount) AS total_amount
FROM orders
WHERE city = 'Казань'
GROUP BY status;

WHERE сработал до группировки. Сначала база оставила только строки из Казани, потом сгруппировала их по статусу

Это важно: WHERE фильтрует исходные строки

HAVING: фильтр после группировки

Теперь другая задача: показать только те статусы, где итоговая сумма больше 5000

SELECT
  status,
  COUNT(*) AS orders_count,
  SUM(amount) AS total_amount
FROM orders
GROUP BY status
HAVING SUM(amount) > 5000;

HAVING сработал после группировки. База сначала посчитала суммы по статусам, потом оставила только группы с суммой больше 5000

Простое правило:

  • WHERE — фильтрует строки до группировки;
  • HAVING — фильтрует группы после группировки.

WHERE и HAVING вместе

Можно использовать оба:

SELECT
  city,
  COUNT(*) AS orders_count,
  SUM(amount) AS total_amount
FROM orders
WHERE status = 'paid'
GROUP BY city
HAVING SUM(amount) > 5000
ORDER BY total_amount DESC;

Читаем по шагам:

  1. Берем только оплаченные заказы.
  2. Группируем по городам.
  3. Считаем количество и сумму.
  4. Оставляем города с суммой больше 5000.
  5. Сортируем по сумме.

Такой запрос уже похож на реальную аналитику для маленького бизнеса

Группировка по нескольким полям

Иногда одной группы мало. Например, хотим отчет по городам и статусам:

SELECT
  city,
  status,
  COUNT(*) AS orders_count,
  SUM(amount) AS total_amount
FROM orders
GROUP BY city, status
ORDER BY city, status;

Теперь отдельная группа создается для каждой пары city + status

Это полезно, когда нужно увидеть структуру: сколько оплаченных, ожидающих и отмененных заказов в каждом городе

Среднее, минимум и максимум

Кроме COUNT и SUM часто используют:

SELECT
  city,
  COUNT(*) AS orders_count,
  AVG(amount) AS avg_amount,
  MIN(amount) AS min_amount,
  MAX(amount) AS max_amount
FROM orders
GROUP BY city;

AVG считает среднее. MIN и MAX находят минимум и максимум внутри группы

На маленьких данных это выглядит учебно. На реальных заказах такие запросы быстро отвечают на вопросы: где чек выше, где заказов больше, где есть выбросы

Частая ошибка: выбрать поле, которого нет в GROUP BY

Новичок пишет:

SELECT city, customer, SUM(amount)
FROM orders
GROUP BY city;

Логика ломается: по городу у нас может быть много клиентов. Какого именно клиента показать в строке Казани? Анну или Олега?

Правильные варианты:

  1. Либо группировать и по городу, и по клиенту:
SELECT city, customer, SUM(amount) AS total_amount
FROM orders
GROUP BY city, customer;
  1. Либо убрать customer, если нужен отчет только по городам:
SELECT city, SUM(amount) AS total_amount
FROM orders
GROUP BY city;

Некоторые базы строже, некоторые могут разрешить сомнительный запрос, но результат будет опасно неоднозначным. Лучше писать явно

Частая ошибка: HAVING вместо WHERE

Так можно, но не стоит:

SELECT status, SUM(amount) AS total_amount
FROM orders
GROUP BY status
HAVING status = 'paid';

Если фильтр относится к обычному полю строки, чаще ставь его в WHERE:

SELECT status, SUM(amount) AS total_amount
FROM orders
WHERE status = 'paid'
GROUP BY status;

HAVING оставляем для условий по агрегатам:

HAVING SUM(amount) > 5000

Мини-задания

  1. Посчитай количество заказов по каждому городу.
  2. Посчитай сумму оплаченных заказов по каждому клиенту.
  3. Покажи только клиентов, у которых сумма оплаченных заказов больше 10000.
  4. Найди средний чек по каждому статусу.
  5. Сгруппируй заказы по городу и статусу.

Возможное решение третьего задания:

SELECT
  customer,
  SUM(amount) AS paid_total
FROM orders
WHERE status = 'paid'
GROUP BY customer
HAVING SUM(amount) > 10000
ORDER BY paid_total DESC;

GROUP BY или оконные функции

Если нужен итог по группе и отдельные строки не нужны, используй GROUP BY

Если нужно оставить каждую строку и добавить расчет рядом, нужны оконные функции:

SELECT
  customer,
  amount,
  SUM(amount) OVER (PARTITION BY customer) AS customer_total
FROM orders;

Это частая развилка. GROUP BY делает отчет компактным. Оконные функции делают строки богаче

Ответы на эти вопросы могут быть для вас полезными

Что делает GROUP BY в SQL?

GROUP BY объединяет строки с одинаковыми значениями в группы, чтобы по каждой группе можно было посчитать количество, сумму, среднее, минимум или максимум

Чем WHERE отличается от HAVING?

WHERE фильтрует строки до группировки. HAVING фильтрует уже получившиеся группы после GROUP BY

Можно ли использовать HAVING без GROUP BY?

В некоторых базах можно, но для обучения лучше держать связку GROUP BY плюс HAVING. Так понятнее, что фильтруются именно группы

Почему SQL ругается на поле в SELECT?

Если в запросе есть агрегаты и GROUP BY, обычные поля в SELECT должны быть либо в GROUP BY, либо внутри агрегатной функции. Иначе непонятно, какое значение выводить для группы

Что учить после GROUP BY?

После группировки хорошо заходят оконные функции. Они помогают считать итоги, рейтинги и накопительные суммы, не схлопывая строки

Что почитать дальше по SQL

Если вы собираете тему по шагам, рядом лучше открыть:

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x