Как работает GROUP BY в SQL? Что такое HAVING и чем он отличается от WHERE?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
GROUP BY, WHERE и HAVING в SQL
Группировка данных — это фундаментальный навык для любого аналитика. Понимание, как работает GROUP BY и отличие между WHERE и HAVING критично для написания правильных аналитических запросов.
Как работает GROUP BY
GROUP BY группирует строки по одному или нескольким столбцам и позволяет применить агрегатные функции (COUNT, SUM, AVG, MIN, MAX) к каждой группе.
Простой пример:
SELECT
country,
COUNT(*) as user_count,
AVG(revenue) as avg_revenue
FROM users
GROUP BY country;
Этот запрос:
- Разделяет всех пользователей по странам
- Для каждой страны считает количество пользователей
- Для каждой страны считает среднюю выручку
Группировка по нескольким столбцам:
SELECT
DATE_TRUNC('day', created_at) as day,
device_type,
COUNT(*) as sessions,
SUM(duration) as total_duration,
AVG(duration) as avg_duration
FROM sessions
GROUP BY DATE_TRUNC('day', created_at), device_type
ORDER BY day DESC, device_type;
Это создаст комбинации день + тип устройства. Если в базе 2 дня и 3 типа устройств, результат будет до 6 строк.
WHERE vs HAVING: ключевое отличие
WHERE:
- Фильтрует строки ДО группировки
- Работает со строками, а не с агрегатами
- Выполняется РАНЬШЕ GROUP BY
- Можешь использовать любые столбцы из таблицы
- Более эффективен (уменьшает данные перед группировкой)
HAVING:
- Фильтрует группы ПОСЛЕ группировки
- Работает с агрегатными функциями (COUNT, SUM, AVG)
- Выполняется ПОСЛЕ GROUP BY
- Можешь использовать только столбцы, по которым группируешь, или агрегаты
- Менее эффективен (группирует, потом отсеивает)
Пример:
-- WHERE: найти активные сеансы ПЕРЕД группировкой
SELECT
user_id,
COUNT(*) as session_count,
AVG(duration) as avg_duration
FROM sessions
WHERE duration > 60 -- ПЕРЕД группировкой, для каждой строки
GROUP BY user_id
HAVING COUNT(*) > 5; -- ПОСЛЕ группировки, для каждой группы
Этот запрос:
- WHERE отсеивает сеансы, длительность которых > 60 секунд
- GROUP BY группирует по пользователям
- HAVING отсеивает пользователей, у которых менее 5 таких сеансов
Порядок выполнения SQL
Важно помнить, в каком порядке выполняются части запроса:
- FROM — выбирается таблица
- WHERE — фильтруются строки
- GROUP BY — строки группируются
- HAVING — фильтруются группы
- SELECT — выбираются столбцы
- ORDER BY — сортируется результат
- LIMIT — ограничивается количество строк
Практические примеры для Product Analytics
Пример 1: Когорты по удержанию
SELECT
DATE_TRUNC('week', signup_date) as cohort,
COUNT(DISTINCT user_id) as cohort_size,
COUNT(DISTINCT CASE WHEN last_activity >= NOW() - INTERVAL '7 days' THEN user_id END) as retained_7d
FROM users
WHERE signup_date >= '2024-01-01' -- WHERE: только новые пользователи
GROUP BY DATE_TRUNC('week', signup_date)
HAVING COUNT(DISTINCT user_id) >= 10 -- HAVING: когорты с минимум 10 пользователями
ORDER BY cohort DESC;
Пример 2: Анализ доходов по стране и платформе
SELECT
country,
platform,
COUNT(DISTINCT user_id) as user_count,
COUNT(*) as transaction_count,
SUM(amount) as total_revenue,
AVG(amount) as avg_transaction,
ROUND(SUM(amount) / COUNT(DISTINCT user_id), 2) as revenue_per_user
FROM transactions
WHERE created_at >= DATE_TRUNC('month', NOW() - INTERVAL '1 month')
AND status = 'completed' -- WHERE: только завершенные транзакции
GROUP BY country, platform
HAVING COUNT(DISTINCT user_id) >= 100 -- HAVING: только страны/платформы с 100+ пользователями
ORDER BY total_revenue DESC;
Пример 3: Поиск аномалий
WITH daily_stats AS (
SELECT
DATE_TRUNC('day', created_at) as day,
event_type,
COUNT(*) as event_count
FROM events
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY DATE_TRUNC('day', created_at), event_type
)
SELECT
event_type,
day,
event_count,
AVG(event_count) OVER (PARTITION BY event_type) as avg_count
FROM daily_stats
HAVING event_count > 2 * AVG(event_count) OVER (PARTITION BY event_type)
ORDER BY day DESC, event_type;
Частые ошибки
Ошибка 1: Использование столбца в SELECT, которого нет в GROUP BY
-- Неправильно (в PostgreSQL даст ошибку)
SELECT user_id, name, COUNT(*) as order_count
FROM orders
GROUP BY user_id; -- name не в GROUP BY!
-- Правильно
SELECT user_id, name, COUNT(*) as order_count
FROM orders
GROUP BY user_id, name;
Ошибка 2: Использование WHERE вместо HAVING для фильтрации агрегатов
-- Неправильно
SELECT country, COUNT(*) as user_count
FROM users
GROUP BY country
WHERE COUNT(*) > 1000; -- синтаксическая ошибка!
-- Правильно
SELECT country, COUNT(*) as user_count
FROM users
GROUP BY country
HAVING COUNT(*) > 1000;
Производительность
- WHERE более эффективен, так как уменьшает количество данных перед группировкой
- Всегда используй WHERE для фильтрации по исходным данным
- HAVING используй только для фильтрации по агрегатам
Эти принципы критичны для написания эффективных аналитических запросов.