Learnly
SQL для аналитиков/Глава 6/Урок 3

GROUP BY и HAVING: считаем по группам

GROUP BY и HAVING: считаем по группам

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

Зачем GROUP BY

Один вопрос: "Какова общая выручка?" → SUM без GROUP BY.

Другой вопрос: "Какова выручка по каждому городу?" → SUM с GROUP BY.

Синтаксис

SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city;
city    | user_count
--------+-----------
Москва  |         45
СПб     |         23
Казань  |         12

База разбила всех пользователей на группы по городу и посчитала количество в каждой.

Важное правило GROUP BY

В SELECT можно указывать только:

  1. Столбцы из GROUP BY
  2. Агрегатные функции
-- Ошибка: name не в GROUP BY и не агрегат
SELECT name, city, COUNT(*)
FROM users
GROUP BY city;

-- Правильно
SELECT city, COUNT(*)
FROM users
GROUP BY city;

Группировка по нескольким столбцам

SELECT city, age, COUNT(*) AS count
FROM users
GROUP BY city, age
ORDER BY city, age;

Создаёт группу для каждой уникальной комбинации город+возраст.

HAVING, фильтрация групп

WHERE фильтрует строки ДО группировки. HAVING фильтрует группы ПОСЛЕ:

-- Города с более 10 пользователей
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city
HAVING COUNT(*) > 10;

-- WHERE и HAVING вместе
SELECT city, COUNT(*) AS active_users
FROM users
WHERE is_active = true         -- сначала фильтруем строки
GROUP BY city
HAVING COUNT(*) >= 5;          -- потом фильтруем группы

Практический пример: отчёт по менеджерам

SELECT
    manager_name,
    COUNT(*)                   AS total_deals,
    SUM(amount)                AS total_revenue,
    ROUND(AVG(amount), 0)      AS avg_deal
FROM deals
WHERE status = 'won'
GROUP BY manager_name
HAVING COUNT(*) >= 5
ORDER BY total_revenue DESC;

💡 Главная мысль: GROUP BY разбивает данные на группы. HAVING фильтрует уже сгруппированные результаты (в отличие от WHERE, которое фильтрует до группировки).