Почему агрегация — это один из главных навыков в аналитике
Очень большая часть аналитической работы начинается с вопроса не про отдельную строку, а про группу объектов. Сколько заказов у пользователя, какая средняя выручка по стране, сколько ошибок на сервис по неделям, какой retention у сегмента. Именно поэтому `GROUP BY` — одна из самых фундаментальных конструкций в SQL. Она позволяет перейти от событий к сжатому, управляемому представлению данных.
Но начинающие часто воспринимают `GROUP BY` как механический синтаксис, а не как способ переописать уровень детализации задачи. На практике это важнее всего: после агрегации таблица уже рассказывает другую историю.
Что именно делает агрегация
$$ group\_metric(g) = f(x_1, x_2, \ldots, x_n) $$Для каждой группы g мы берем набор строк и применяем к нему агрегирующее правило: сумму, среднее, число событий, максимум и так далее. В результате вместо множества строк появляется одна сводная характеристика группы.
group\_metric(g)— итоговая метрика для группы gf— агрегирующая функция, например sum или avgx_1 \ldots x_n— строки, которые попали в эту группу
Этот взгляд полезен тем, что заставляет не просто писать `count(*)`, а понимать, какой объект анализа возникает после группировки: пользователь, день, категория, сегмент, страна.
Почему HAVING нужен после GROUP BY
`WHERE` фильтрует строки до агрегации. `HAVING` фильтрует уже сами группы после того, как агрегаты посчитаны. Это простая идея, но именно здесь часто возникает путаница. Например, если хочется оставить только тех пользователей, у кого суммарная выручка выше определенного порога, `WHERE` уже не подойдет — такого поля еще не существует до агрегации. Нужен именно `HAVING`.
То есть `HAVING` — это не редкий экзотический оператор, а естественное продолжение группировки, когда нас интересует отбор уже на уровне агрегированных объектов.
Как увидеть это в Python-аналогии
import pandas as pd # создаем таблицу заказов для демонстрации группировки
orders = pd.DataFrame({ # задаем пользователя и сумму заказа
'user_id': [1, 1, 2, 2, 2, 3], # ключ пользователя для будущей группировки
'amount': [500, 900, 200, 300, 700, 150], # суммы отдельных заказов
})
agg = orders.groupby('user_id', as_index=False).agg( # агрегируем данные по пользователю
orders_cnt=('amount', 'size'), # считаем число заказов в группе
revenue_sum=('amount', 'sum'), # считаем суммарную выручку по пользователю
)
filtered = agg[agg['revenue_sum'] >= 1000] # имитируем HAVING после агрегации
print(filtered) # смотрим только те группы, что прошли условие по агрегатуОчень важно почувствовать разницу: сначала мы строим новый уровень данных, а уже потом фильтруем его. Именно это и делает `GROUP BY` плюс `HAVING` одним из базовых инструментов аналитического мышления.