Именно здесь появляются GROUP BY и HAVING. Формально это синтаксис SQL. По смыслу это способ изменить уровень взгляда на данные. Пока вы работаете без агрегации, вы видите мир на уровне отдельных событий: чеков, кликов, транзакций, сессий, заказов. Как только появляется GROUP BY, вы начинаете смотреть на города, пользователей, категории товаров, недели, сегменты, каналы трафика. То есть вы переходите от отдельных точек к структуре.
Именно поэтому GROUP BY и HAVING лучше понимать не как очередной синтаксис из учебника, а как один из центральных способов перейти от сырых строк к аналитическим объектам, на которых потом держатся и отчёты, и признаки для моделей.
Для Data Science это фундаментально. Большая часть полезных признаков в табличных задачах вообще возникает через агрегации. Средний чек клиента, число покупок за месяц, максимальная задержка платежа, доля возвратов по категории, средняя конверсия канала, медианная выручка по группе пользователей — все это логика GROUP BY. А HAVING нужен тогда, когда после агрегации вы хотите оставить только те группы, которые удовлетворяют какому-то условию. Не отдельные строки, а уже собранные агрегаты.
Почему GROUP BY — это не просто удобный синтаксис, а смена уровня анализа
Представим таблицу заказов интернет-магазина. В ней каждая строка — это один заказ: user_id, дата, сумма, категория, канал. Если смотреть на такую таблицу построчно, она почти ничего не говорит о пользователе как об объекте анализа. Мы видим лишь поток событий. Но если сгруппировать эти строки по user_id, сразу возникает новая сущность: клиент как единица поведения. Мы можем посчитать количество заказов, средний чек, первую и последнюю покупку, долю возвратов, сумму выручки. Это уже не просто таблица событий. Это пользовательский профиль.
Интуитивно GROUP BY можно понять как операцию сборки. SQL берет много строк, которые объединены общим ключом, и склеивает их в одну аналитическую сущность. До агрегации у нас хаотичный поток транзакций. После агрегации — компактная таблица признаков. Именно поэтому аналитики и data scientist'ы так много времени проводят в SQL: они не только достают данные, они формируют уровень, на котором эти данные вообще становятся пригодными для модели. На практике этот уровень почти всегда живёт не в изоляции, а внутри более широкого слоя warehouse, где и закрепляется общая логика витрин и агрегатов.
Что именно делает GROUP BY
Когда вы пишете GROUP BY city, вы просите SQL разбить все строки на группы по значению поля city. После этого каждая функция агрегации начинает работать уже не по всей таблице, а внутри каждой такой группы. COUNT(*) отвечает на вопрос, сколько строк попало в группу. SUM(revenue) — какова общая сумма внутри нее. AVG(price) — каково среднее значение. MIN() и MAX() показывают крайние значения.
Важно понять, что после GROUP BY меняется сама допустимая логика выборки. В SELECT вы уже не можете свободно брать любые столбцы. Каждый столбец должен либо входить в ключ группировки, либо быть обернут в агрегатную функцию. Это не каприз SQL, а следствие самой математики операции. Если одна группа содержит сотни строк с разными значениями столбца, SQL не может честно вернуть одно произвольное значение без явного правила.
Раздел математики: математическая статистика и теория множеств.
(A_g=\frac{1}{n_g}\sum_{i \in g} x_i)
Что означает каждый символ:
(A_g) — агрегированное среднее значение для группы (g).
(n_g) — число строк в группе (g).
(\sum_{i \in g}) — суммирование всех значений внутри выбранной группы.
(x_i) — значение метрики в (i)-й строке группы.
Какова роль формулы в SQL: она выражает смысл AVG(x) после GROUP BY. Мы считаем среднее не по всей таблице, а отдельно для каждой собранной группы.
Численный пример: пусть в группе «Москва» есть три заказа со значениями (1000), (1500) и (2000). Тогда (A_{\text{Москва}}=\frac{1000+1500+2000}{3}=1500). Именно такой результат вернет AVG(order_amount) для этой группы.
Геометрическая интерпретация: агрегация как переход от облака точек к центрам и профилям
Если смотреть на данные геометрически, то исходная таблица — это облако точек в многомерном пространстве. Каждая строка — одна точка. Но машинное обучение и аналитика очень часто работают не с отдельными событиями, а с объектами более высокого уровня: клиентами, магазинами, городами, временными окнами. В этом смысле GROUP BY — это способ спроецировать большое облако точек на более компактное пространство групп.
Среднее внутри группы можно интерпретировать как центр тяжести, сумма — как массу группы, количество строк — как ее размер, максимум — как крайнюю точку по оси, минимум — как нижнюю границу. Поэтому агрегация — это не только синтаксическая операция, но и геометрическая компрессия данных. Мы сворачиваем множество точек в более компактное представление, которое лучше подходит для анализа и построения признаков.
Почему HAVING нужен после GROUP BY, а не вместо WHERE
Очень многие путают WHERE и HAVING. Разница между ними не косметическая. WHERE фильтрует строки до агрегации. HAVING фильтрует группы после агрегации. Это два разных этапа мышления. Сначала вы можете удалить ненужные исходные события, например оставить только заказы за 2026 год. Потом сгруппировать их по пользователю. И уже после этого отобрать только тех пользователей, у кого, скажем, больше пяти заказов.
Интуитивно это очень легко представить на бытовом примере. Если вы хотите найти города, в которых было больше 100 заказов, бессмысленно фильтровать отдельные строки условием «число заказов больше 100», потому что у одной строки нет такого свойства. Это свойство появляется только после сборки группы. Значит, здесь нужен не WHERE, а HAVING COUNT(*) > 100.
SQL как язык feature engineering
В Data Science GROUP BY особенно важен не как средство подготовки отчета, а как средство построения признаков. Большая часть хороших табличных моделей живет на агрегатах. Если вы предсказываете churn, вам почти наверняка нужны число сессий за период, средний интервал между покупками, доля успешных действий, максимальная давность активности. Если вы решаете задачу antifraud, вам нужны агрегаты по устройству, карте, IP, пользователю, часовому окну. Если вы строите рекомендательную систему, вам нужны счетчики взаимодействий, популярность объектов, агрегаты по пользователю и товару.
Именно поэтому SQL и машинное обучение связаны гораздо сильнее, чем кажется новичкам. SQL не стоит до Data Science и не заканчивается после загрузки таблицы. Он участвует в самой математике модели через признаки. Хороший data scientist понимает, что сила модели очень часто рождается не только из алгоритма, но и из того, как именно были агрегированы исходные события.
Раздел математики: прикладная статистика и feature engineering.
(\mathbf{z}_u=\left[c_u, s_u, \bar{x}_u, m_u\right])
Что означает каждый символ:
(\mathbf{z}_u) — вектор признаков пользователя (u) после агрегации.
(c_u) — число событий пользователя, например COUNT(*).
(s_u) — суммарная метрика, например SUM(revenue).
(\bar{x}_u) — среднее значение, например AVG(order_amount).
(m_u) — максимум или минимум, например MAX(days_since_last_order).
Какова роль формулы в ML: она показывает, что после агрегации SQL фактически строит признаковый вектор объекта, который затем попадет в модель. GROUP BY user_id превращает историю событий в одну строку с признаками пользователя.
Численный пример: если у клиента было (c_u=4) заказа, общая выручка составила (s_u=12000), средний чек равен (\bar{x}_u=3000), а максимум задержки между покупками (m_u=18) дней, то строка признаков для модели будет выглядеть как (\mathbf{z}_u=[4, 12000, 3000, 18]).
Где GROUP BY и HAVING встречаются в машинном обучении
На уровне production-систем эти конструкции встречаются почти везде. Перед обучением модели нужно собрать train-dataset из событийных таблиц. Перед online-scoring или batch-scoring часто нужно пересчитать агрегаты по окнам времени. При анализе метрик качества модели приходится группировать ошибки по сегментам, каналам, регионам, типам устройств, классам пользователей. Даже простой анализ data drift часто начинается с агрегаций: как изменилась средняя сумма чека, какая доля новых категорий пришла, как распределились события по часу суток. А когда таких запросов становится много и они начинают разрастаться, очень полезно отдельно разобрать и SQL CTE, потому что именно они часто делают сложные агрегации читаемыми и поддерживаемыми.
Поэтому GROUP BY и HAVING полезно воспринимать не как тему из начального SQL, а как один из базовых языков описания структуры данных. Если вы плохо чувствуете агрегацию, вы почти наверняка будете слабее и в аналитике, и в ML-практике, потому что не сможете быстро собирать осмысленные признаки и качественные диагностические срезы.
Почему агрегировать нужно осторожно
Есть важная ловушка. Как только аналитик начинает любить агрегации, у него появляется соблазн агрегировать слишком рано или слишком грубо. Например, усреднить данные по неделям и потерять внутринедельную структуру, которая была критична для модели. Или сгруппировать по пользователю и незаметно стереть временную динамику, хотя именно она предсказывала churn. Агрегация — это всегда компрессия. Она делает данные компактнее, но одновременно выбрасывает часть информации. Именно поэтому полезно отдельно смотреть и на происхождение признаков: тогда становится яснее, на каком шаге именно агрегат ещё помогает модели, а на каком уже прячет важную структуру данных.
Поэтому хороший вопрос звучит не «как бы еще сгруппировать», а «какой уровень объекта сейчас нужен задаче». Иногда нужен клиент. Иногда нужна пара клиент-месяц. Иногда нужна категория-день. Иногда сессия. Именно выбор единицы агрегации очень часто определяет качество итогового анализа.
Как читать запрос с GROUP BY по смыслу, а не по синтаксису
Полезный навык для студента — перестать читать SQL как текстовую инструкцию и начать читать его как аналитическое утверждение. Например, запрос
SELECT
city,
COUNT(*) AS orders_cnt,
AVG(order_amount) AS avg_order_amount
FROM orders
WHERE order_date >= '2026-01-01'
GROUP BY city
HAVING COUNT(*) >= 100;
по смыслу означает не «выполни count и avg», а следующее: возьми только заказы 2026 года, собери их по городам, для каждого города посчитай число заказов и средний чек, а затем оставь только достаточно крупные города, где накопилось не меньше ста заказов. То есть это уже почти полноценное аналитическое предложение на естественном языке.
Python: как та же агрегация выглядит в pandas
import pandas as pd # Подключаем pandas для работы с таблицей.
orders = pd.read_csv("orders.csv") # Загружаем таблицу заказов.
orders["order_date"] = pd.to_datetime(orders["order_date"]) # Приводим дату заказа к типу datetime.
filtered = orders[orders["order_date"] >= "2026-01-01"] # Оставляем только заказы, начиная с 2026 года.
grouped = ( # Начинаем строить агрегированную таблицу по городам.
filtered
.groupby("city") # Группируем строки по полю city.
.agg( # Считаем агрегаты внутри каждой группы.
orders_cnt=("order_id", "count"), # Подсчитываем число заказов в городе.
avg_order_amount=("order_amount", "mean") # Считаем средний чек в городе.
)
.reset_index() # Возвращаем city из индекса обратно в обычный столбец.
)
result = grouped[grouped["orders_cnt"] >= 100] # Это аналог HAVING: оставляем только крупные группы.
print(result.head()) # Показываем первые строки результата.Этот пример полезен именно связью между SQL и Python. В pandas логика та же самая: сначала фильтрация строк, потом группировка, потом агрегация, потом фильтрация агрегированных групп. То есть SQL и Python здесь не конкурируют, а описывают одну и ту же операцию на разных языках. Это особенно важно для Data Science, где один и тот же аналитический ход иногда удобнее сделать в SQL, а иногда в pandas — в зависимости от объема данных и архитектуры пайплайна. Когда же такие запросы начинают тормозить уже не учебную таблицу, а реальное хранилище, рядом естественно смотреть и на SQL performance.
HAVING как аналитический фильтр второго уровня
Очень полезно думать о HAVING как о фильтре второго уровня. Первый уровень — строки. Там работает WHERE. Второй уровень — уже собранные группы. Там работает HAVING. Такой взгляд быстро снимает путаницу. Если условие относится к исходному событию, это WHERE. Если условие относится к агрегированному свойству группы, это HAVING.
Например, «оставить транзакции выше 1000» — это WHERE amount > 1000. А вот «оставить пользователей, у которых суммарная выручка выше 10000» — это уже HAVING SUM(amount) > 10000. В первом случае фильтруется каждая строка. Во втором — итог собранной группы.
Как изучать GROUP BY и HAVING в 2026 году
В 2026 году эти конструкции лучше всего изучать не на игрушечных таблицах из двух колонок, а на реальных аналитических сценариях. Сначала научиться собирать базовые агрегаты: количество, сумму, среднее, минимум, максимум. Затем переходить к оконным срезам, к агрегациям по времени, к комбинациям нескольких ключей, к построению признаков уровня пользователя, продукта, региона. После этого уже становится понятно, почему SQL остается ключевым языком для аналитиков и data scientist'ов даже в эпоху Python-first workflows. И на этом же этапе полезно подключать тему валидации данных, чтобы агрегаты были не только красивыми, но и действительно надёжными для моделей.
Очень важно параллельно держать в голове и смысл, и технику. Смысл — это выбор уровня объекта анализа. Техника — это порядок операций: FROM, WHERE, GROUP BY, агрегаты, HAVING. Когда оба слоя складываются вместе, SQL перестает быть набором ключевых слов и превращается в инструмент мышления о данных.
Что важно вынести из темы
GROUP BY — это способ перейти от потока строк к аналитическим сущностям: пользователям, городам, сегментам, неделям, категориям. HAVING — это способ отбирать уже собранные группы, а не исходные события. Вместе они образуют фундамент агрегации в SQL, а значит и фундамент значительной части аналитики и feature engineering в Data Science.
Если сформулировать совсем коротко, то GROUP BY отвечает на вопрос «как собрать строки в осмысленные группы», а HAVING — «какие из этих групп действительно важны для анализа». И как только это понимание становится интуитивным, SQL перестает быть техническим этапом перед Data Science и становится частью самой аналитической логики.
Kaggle notebook по теме:
https://www.kaggle.com/code/opkaggle123/sql-3-exercise-group-by-having-count