Если объяснять совсем интуитивно, то GROUP BY сжимает таблицу, а оконная функция сохраняет ее зерно. Это разница принципиальна. Когда Data Scientist или аналитик работает с событиями, заказами, логами, кликами, платежами или пользовательскими сессиями, ему часто нужно одновременно видеть и саму строку, и ее контекст внутри группы. Например, не просто средний чек по клиенту, а каждый конкретный заказ вместе со средним чеком этого клиента. Или не просто число транзакций за день, а каждую транзакцию и ее порядковый номер в цепочке. Именно здесь оконные функции оказываются незаменимыми.
Если смотреть на тему прикладно, оконные функции становятся следующим важным шагом после того, как появляется базовое понимание, зачем SQL вообще нужен в Data Science. Они появляются ровно в тот момент, когда аналитик перестает довольствоваться простой выборкой строк и начинает строить контекстные признаки, сравнивать объект с его группой и работать с последовательностью событий.
Почему оконные функции важны именно в Data Science
В Data Science мы постоянно строим признаки, сравниваем объекты с их группой и ищем временную структуру поведения. Нужно понять, насколько текущая покупка отклоняется от среднего чека клиента. Нужно узнать, какой это по счету вход пользователя в систему. Нужно посчитать долю события относительно общего числа событий в сегменте. Нужно взять последнее действие до момента конверсии. Все это задачи, где простого GROUP BY уже недостаточно.
Оконные функции важны еще и потому, что они учат правильному мышлению о данных. Они заставляют специалиста явно формулировать, что такое группа, в каком порядке расположены наблюдения и какой контекст должен быть доступен каждой строке. Это почти всегда полезно при построении признаков для моделей. Хорошая модель начинается не с выбора алгоритма, а с аккуратного представления структуры данных.
Главная идея: строка остается на месте, а метрика считается вокруг нее
Это ключевая мысль, которую стоит удерживать. Оконная функция не уничтожает строку. Она как будто дает этой строке возможность посмотреть на своих соседей внутри определенного окна. Окно может быть всей группой, частью временного ряда или набором строк до текущего наблюдения. Сама строка остается в таблице, но получает дополнительную информацию о контексте.
Эта идея особенно полезна в продуктовой аналитике. Допустим, у нас есть таблица заказов. Мы хотим видеть каждый заказ, но одновременно знать, как он соотносится со средним заказом данного пользователя, какой он по счету и сколько денег пользователь успел потратить к этому моменту. Все это естественным образом выражается через оконные функции.
Чем оконные функции отличаются от GROUP BY
На практике путаница почти всегда возникает именно здесь. GROUP BY агрегирует строки и уменьшает таблицу. Если в исходной таблице было десять заказов пользователя, после группировки может остаться одна строка с суммой или средним. Это полезно, когда нужна витрина на уровне пользователя. Но это плохо, когда нам нужна детализация по каждому заказу. Поэтому перед оконными функциями полезно уверенно понимать, как работают GROUP BY и HAVING и в какой момент агрегация уже слишком грубо сжимает данные.
Оконная функция позволяет получить ту же агрегированную информацию, но не потерять исходные записи. Это делает ее особенно ценной для feature engineering, ретеншн-анализа, funnel analysis, fraud detection и любых задач, где важен контекст события внутри истории пользователя. В 2026 году это еще актуальнее, потому что многие аналитические пайплайны сразу готовят данные и для BI, и для ML. Потерять зерно таблицы на слишком раннем этапе означает сузить пространство последующих решений.
PARTITION BY: как задать группу, внутри которой считается метрика
Когда в оконной функции пишут PARTITION BY, они фактически отвечают на вопрос: кто для данной строки считается «своими». Если мы разбиваем по user_id, то каждая строка пользователя будет смотреть только на другие строки этого же пользователя. Если по city, строка будет сравниваться с другими строками того же города. Это не синтаксическая деталь, а аналитическая постановка.
В этом месте часто проявляется зрелость специалиста. Неопытный аналитик механически берет поле, которое кажется естественным. Опытный сначала спрашивает себя, какая именно группа должна формировать контекст. Если задача про индивидуальное поведение, группа — пользователь. Если про конкуренцию внутри категории товаров, группа — категория. Если про динамику команды продаж, группа — менеджер или регион. Окно всегда отражает смысл задачи.
ORDER BY внутри окна: почему порядок строк меняет ответ
Как только в окне появляется порядок, оконная функция перестает быть просто агрегатом по группе и начинает работать с последовательностью. Это особенно важно для временных данных. Если мы сортируем по дате события, то можем говорить о первом действии, предыдущем действии, накопительной сумме, сдвиге относительно прошлого периода и ранге наблюдения.
Для Data Science это крайне полезно, потому что многие признаки имеют именно последовательную природу. Последняя покупка, интервал между событиями, число действий за последние N шагов, изменение суммы относительно предыдущей транзакции — все это опирается на порядок. Без него таблица событий остается просто набором строк, а с ним превращается в историю поведения.
Какие оконные функции используются чаще всего
Если убрать экзотику, то для прикладных задач обычно хватает нескольких функций. ROW_NUMBER() дает порядковый номер строки внутри группы. RANK() и DENSE_RANK() позволяют ранжировать строки по значению. SUM(), AVG(), MIN(), MAX() внутри окна дают агрегаты без потери строк. LAG() и LEAD() позволяют обратиться к предыдущей или следующей записи. Уже этот набор покрывает огромную долю повседневной аналитики.
Хорошая интуиция здесь такая: оконные функции отвечают либо на вопрос о позиции строки, либо на вопрос о контексте строки. Позиция — это номер, ранг, первое или последнее наблюдение. Контекст — это среднее по группе, накопление суммы, значение предыдущего события, максимум в сегменте и так далее.
ROW_NUMBER: когда нужно упорядочить события внутри истории
ROW_NUMBER() особенно полезна тогда, когда мы хотим пронумеровать события внутри группы. Это встречается чаще, чем кажется. Например, в retention-анализе нужно определить первое посещение пользователя. В продажах — первый заказ клиента. В fraud-задачах — порядок транзакций в течение короткого интервала. В рекомендациях — позицию просмотра контента в рамках одной сессии.
Если смотреть глубже, ROW_NUMBER() помогает превратить хаотичные данные в последовательность. А последовательность уже гораздо ближе к тому, как мыслит модель о времени и поведении. Это одна из причин, почему даже простые SQL-оконные функции так полезны для ML-подготовки данных.
LAG и LEAD: как смотреть назад и вперед без self join
До знакомства с оконными функциями многие пытаются решать задачи через self join: соединяют таблицу саму с собой, чтобы добраться до предыдущей записи. Это тяжеловесно и плохо читается. LAG() и LEAD() делают то же самое намного естественнее. Они позволяют взять значение предыдущей или следующей строки внутри группы, если порядок уже задан. Вообще умение аккуратно собрать исходную таблицу через SQL JOIN остается важным и здесь: оконные функции почти всегда работают уже поверх корректно соединенных источников.
Для аналитики это почти всегда означает работу с динамикой. Нас интересует, насколько текущая покупка больше предыдущей, сколько дней прошло с прошлого визита, был ли предыдущий заказ возвратом, меняется ли сегмент пользователя со временем. В feature engineering это тоже стандартный прием: признаки разницы и темпа часто оказываются информативнее, чем абсолютные значения.
Накопительные метрики: когда важен не итог, а траектория
Одна из самых красивых практических возможностей оконных функций — накопительные суммы и средние. Допустим, нас интересует не просто суммарная выручка клиента, а то, как она нарастала по времени. Или не просто общее число транзакций, а сколько их накопилось к текущему моменту. Такие метрики полезны и для аналитики, и для построения временных признаков.
Здесь важно понимать, что модель часто чувствительна не только к финальному значению, но и к форме траектории. Два клиента могут принести одинаковую суммарную выручку, но один сделал это быстро и в начале жизненного цикла, а другой — медленно и равномерно. С точки зрения бизнеса это разные типы поведения. Оконные функции позволяют эту разницу увидеть прямо на уровне SQL.
Как оконные функции связаны с математикой и ML
Хотя тема кажется сугубо SQL-ной, связь с математикой здесь прямая. Когда мы считаем среднее, сумму, долю, отклонение или ранг внутри окна, мы фактически строим статистику условного контекста. Каждая строка получает не только собственное значение, но и положение относительно своей группы. Это уже форма нормализации и контекстуализации признака.
Для машинного обучения это особенно полезно. Абсолютное значение может быть малоинформативным, если не учитывать, внутри какой группы оно появилось. Заказ на сумму 5000 рублей может быть крупным для одного клиента и обычным для другого. Десять логинов в неделю могут означать высокую вовлеченность для одного сегмента и низкую для другого. Оконные функции помогают сформировать признаки относительного положения: ранг, отклонение от среднего, долю в группе, темп изменения. А именно такие признаки часто усиливают модель.
Почему оконные функции уменьшают хаос в SQL-запросах
Еще одна недооцененная сторона темы состоит в том, что оконные функции часто делают запросы чище. Без них аналитик начинает строить временные таблицы, nested subqueries и self joins, чтобы дотянуться до предыдущих строк или групповых метрик. С оконными функциями многие такие конструкции схлопываются в одну понятную логику: вот группа, вот порядок, вот метрика, которую получает каждая строка. Когда запрос все равно становится длинным, эту логику удобно собирать через SQL CTE, чтобы этапы расчета окна оставались читаемыми.
В командной работе это особенно важно. SQL в Data Science — это не только получение ответа, но и воспроизводимость. Хороший запрос должен быть читаемым и проверяемым. Когда логика расчета прозрачна, меньше вероятность, что через месяц кто-то неверно интерпретирует запрос и соберет неправильную витрину признаков. А если оконных расчетов становится много и они начинают тяжело работать на больших таблицах, уже приходится думать и о SQL performance, иначе красивая аналитика быстро превращается в дорогое место в пайплайне.
Пример SQL: считаем ранг и средний чек по пользователю
Пусть у нас есть таблица заказов. Мы хотим видеть каждый заказ, его место по сумме внутри пользователя и средний чек пользователя. Если бы мы использовали только GROUP BY, пришлось бы потерять детализацию по каждому заказу. Оконные функции решают это естественно.
SELECT
user_id,
order_id,
order_date,
amount,
AVG(amount) OVER (PARTITION BY user_id) AS avg_user_amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date) AS order_num,
RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS amount_rank
FROM orders;
Здесь каждая строка заказа сохраняется. Но одновременно мы видим средний чек пользователя, порядковый номер заказа по времени и ранг заказа по сумме. Это уже не просто таблица заказов, а более богатое представление поведения пользователя. Именно так оконные функции начинают работать как инструмент feature engineering.
Пример SQL: накопительная выручка по клиенту
Теперь представим более динамическую задачу. Нас интересует, как накапливается выручка клиента по времени. Это полезно для анализа LTV, поведения после первой покупки и ранних продуктовых сценариев.
SELECT
user_id,
order_date,
amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_revenue
FROM orders;
Этот запрос уже работает с траекторией. Для каждой строки он показывает, сколько денег пользователь принес к текущему моменту. В Data Science такая логика часто нужна перед тем, как построить датасет для модели прогноза ценности пользователя или вероятности повторной покупки.
Пример на Python: та же логика через pandas
Хотя оконные функции принадлежат SQL, их идеи постоянно переходят в Python. В pandas почти все то же самое выражается через groupby, transform, rank, shift и cumsum. Для Data Scientist полезно видеть эту связь, потому что часть подготовки может жить в SQL, а часть уже в ноутбуке.
import pandas as pd # Подключаем pandas для работы с таблицами.
orders = pd.DataFrame({ # Создаем маленький учебный датасет заказов.
'user_id': [1, 1, 1, 2, 2], # Указываем идентификаторы пользователей.
'order_id': [101, 102, 103, 201, 202], # Добавляем идентификаторы заказов.
'order_date': pd.to_datetime(['2026-01-01', '2026-01-10', '2026-01-15', '2026-01-03', '2026-01-20']), # Переводим даты заказов в datetime.
'amount': [1200, 800, 1500, 500, 700] # Записываем суммы заказов.
})
orders = orders.sort_values(['user_id', 'order_date']) # Сортируем строки так, чтобы внутри пользователя был правильный временной порядок.
orders['avg_user_amount'] = orders.groupby('user_id')['amount'].transform('mean') # Считаем средний чек пользователя без потери строк.
orders['order_num'] = orders.groupby('user_id').cumcount() + 1 # Нумеруем заказы внутри каждого пользователя.
orders['prev_amount'] = orders.groupby('user_id')['amount'].shift(1) # Берем сумму предыдущего заказа, как аналог LAG().
orders['cumulative_revenue'] = orders.groupby('user_id')['amount'].cumsum() # Считаем накопительную выручку по пользователю.
orders['amount_rank'] = orders.groupby('user_id')['amount'].rank(method='dense', ascending=False) # Строим ранг заказа по сумме внутри пользователя.
print(orders) # Печатаем итоговую таблицу с контекстными признаками.Этот пример полезен не только как перенос синтаксиса. Он показывает, что оконные функции — это не локальный трюк конкретного SQL-движка, а общий способ мышления о данных. Мы сохраняем строки, но добавляем им контекст группы и времени.
Где чаще всего ошибаются начинающие специалисты
Первая ошибка — путать GROUP BY и оконные функции. Если нужен контекст для каждой строки, группировка часто слишком груба. Вторая ошибка — забывать про порядок при работе с LAG, LEAD и накопительными метриками. Без корректного ORDER BY ответ может быть формально вычислен, но аналитически бессмыслен. Третья ошибка — не проверять зерно таблицы. Если одна строка означает событие, а аналитик случайно интерпретирует ее как пользователя, признаки быстро теряют смысл.
Есть и более тонкая ошибка: считать, что оконные функции нужны только аналитикам. На самом деле они полезны и для ML-инженеров, и для Data Scientists, потому что именно через них часто строятся временные и относительные признаки. В 2026 году, когда качество feature engineering по-прежнему определяет большую часть практического результата, это знание нельзя считать факультативным.
Как изучать оконные функции в 2026 году
Лучший маршрут изучения не начинается с заучивания полного синтаксиса. Сначала стоит освоить различие между GROUP BY и окном. Затем — понять, что делает PARTITION BY. Потом — что происходит, когда добавляется ORDER BY. После этого уже можно спокойно изучать ROW_NUMBER, RANK, LAG, LEAD и накопительные суммы.
Если смотреть прагматично, для сильной прикладной работы достаточно уверенно владеть ограниченным набором оконных функций и понимать, где они дают более корректное представление данных, чем грубая агрегация. Этого уже хватает, чтобы писать зрелые аналитические запросы, строить хорошие признаки и не тонуть в хаосе подзапросов.
Kaggle notebook по теме:
https://www.kaggle.com/code/jyotijoshi0/sql-advanced-2-analytic-functions