SQLучебникдля начинающихGROUP BYагрегатыCOUNTчасть-4

SQL с нуля. Часть 4: Агрегаты COUNT, SUM, AVG + GROUP BY + HAVING

2026-06-02 9 мин

Это часть 4 из 10 учебника «SQL с нуля для аналитика». Содержание серии в конце поста. ← Часть 3


TL;DR: Агрегатные функции — COUNT (сколько), SUM (сумма), AVG (среднее), MIN/MAX (минимум/максимум). GROUP BY группирует строки по колонке, агрегаты считаются внутри каждой группы. HAVING фильтрует группы (как WHERE для агрегатов). 90% метрик аналитика — это GROUP BY + агрегаты.

В этой части:


Какие 5 агрегатных функций должен знать аналитик?

ФункцияЧто считаетПример
COUNT(*)Количество строкCOUNT(*) AS orders
COUNT(col)Количество не-NULL значенийCOUNT(email)
COUNT(DISTINCT col)Уникальных значенийCOUNT(DISTINCT user_id)
SUM(col)СуммаSUM(amount) AS revenue
AVG(col)СреднееAVG(amount) AS avg_check
MIN(col) / MAX(col)Минимум / МаксимумMAX(created_at)

Примеры:

-- Сколько у нас пользователей всего
SELECT COUNT(*) FROM users;

-- Сколько уникальных юзеров делали заказы
SELECT COUNT(DISTINCT user_id) FROM orders;

-- Общая выручка
SELECT SUM(amount) AS total_revenue FROM orders WHERE status = 'paid';

Как работает GROUP BY?

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

!GROUP BY flow: строки группируются по колонке и схлопываются в агрегат

Анимация — следи как строки переходят в свои группы:

!GROUP BY animated: строки оседают по странам

Идея:

Таблица orders:
  user_id | amount
  1       | 100
  1       | 200
  2       | 500
  3       | 50

После GROUP BY user_id:
  Группа user_id=1: [100, 200]
  Группа user_id=2: [500]
  Группа user_id=3: [50]

После SUM(amount):
  user_id=1: 300
  user_id=2: 500
  user_id=3: 50

В SQL:

SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id;
Например, первая метрика большинства аналитиков: «выручка по дням». SELECT DATE(created_at), SUM(amount) FROM orders GROUP BY 1 ORDER BY 1. Откроет глаза на seasonality, day-of-week-эффект, аномалии.

Что нельзя писать в SELECT при GROUP BY?

Правило: каждая колонка в SELECT должна быть либо в GROUP BY, либо внутри агрегата.

-- ОШИБКА: email не в GROUP BY и не агрегат
SELECT user_id, email, SUM(amount)
FROM orders o
JOIN users u ON o.user_id = u.id
GROUP BY user_id;

-- ПРАВИЛЬНО: добавить email в GROUP BY
SELECT user_id, email, SUM(amount)
FROM orders o
JOIN users u ON o.user_id = u.id
GROUP BY user_id, email;

-- ИЛИ обернуть в агрегат (для одинаковых значений)
SELECT user_id, MAX(email), SUM(amount)
FROM orders o
JOIN users u ON o.user_id = u.id
GROUP BY user_id;

(JOIN разберём в Части 5).

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

Пример:

-- Все юзеры с выручкой > 10000
SELECT user_id, SUM(amount) AS total
FROM orders
WHERE status = 'paid'        -- фильтр строк до GROUP BY
GROUP BY user_id
HAVING SUM(amount) > 10000;  -- фильтр групп после
ЧтоWHEREHAVING
Когда работаетДо GROUP BYПосле GROUP BY
Можно использовать агрегаты
Можно использовать колонки таблицыТолько из GROUP BY

Правило: WHERE по возможности, HAVING — только для условий на агрегаты. WHERE быстрее, потому что меньше данных идёт в GROUP BY.

Чем COUNT(*) отличается от COUNT(col)?

-- Таблица users (10 строк, у 3 email IS NULL)
SELECT
  COUNT(*),              -- 10
  COUNT(email),          -- 7 (без NULL)
  COUNT(DISTINCT email)  -- например 6 (если 1 дубль)
FROM users;

Подробнее про NULL — в Части 7.

Какие 6 типичных задач аналитика через GROUP BY?

(CASE WHEN разберём в Части 7).

Типичные топ-5 запросов: (1) выручка/день, (2) DAU, (3) средний чек по категориям, (4) топ-10 продуктов, (5) конверсия по каналам.

Частые вопросы про GROUP BY и агрегаты

Можно ли в GROUP BY использовать индексы колонок?

GROUP BY 1, 2 (по индексу колонки в SELECT). Работает, но плохо читается. Лучше явно GROUP BY country, region.

Что делать если нужно агрегат внутри агрегата?

Подзапросы или CTE — разбираем в Части 6. Пример: «средняя выручка на пользователя» — сначала сумма по юзеру, потом среднее.

COUNT(1) — это то же что COUNT(*)?

Да. COUNT(1), COUNT(*), COUNT('any literal') — все считают строки. COUNT(*) — каноничный.

Почему GROUP BY медленный?

Требует сортировки или hash table. На больших данных — bottleneck. Оптимизация: партиционирование, индексы, materialized views. См. гайд по PG индексам.

Можно ли SUM по нескольким колонкам?

SUM(col1 + col2) — работает (предварительно сложит). SUM(col1) + SUM(col2) — тоже работает (сложит сумму). Эквивалентны при отсутствии NULL.

Что дальше?

В Части 5 — JOIN: как соединять таблицы. Без JOIN ты ограничен одной таблицей, а реальный аналитик работает с 5-20 таблицами в одном запросе.

Сейчас открой SQL-тренажёр и попробуй посчитать «выручку по странам через GROUP BY + SUM». Первые 5 задач бесплатны.

В Pro — безлимит мок-собесов на AI-интервью + 491 SQL-задача + 612 тестовых заданий + 50+ блог-постов.


Навигация по учебнику

← Часть 3 | Часть 4: GROUP BY и агрегаты | Часть 5 →

Содержание серии: 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10

← Вернуться к оглавлению

См. также GROUP BY и HAVING правильно для углубления.

Источники

SQL-тренажёр
Практика GROUP BY на реальных задачах. 491 SQL-задача, первые 5 бесплатно.
Открыть тренажёр →