Почему оконные функции ощущаются как переход на следующий уровень SQL
До знакомства с window functions многие задачи решают через `group by`, подзапросы и самодельные join-цепочки. Это работает, но часто делает запросы тяжелыми и нечитаемыми. Оконные функции меняют сам способ мышления: теперь можно считать агрегаты по группе, ранги и накопительные значения, не уничтожая исходные строки. То есть каждая запись сохраняет свою детализацию, но при этом получает контекст своего окна.
Для аналитики и Data Science это особенно полезно, потому что много признаков строится именно так: среднее по пользователю, ранг заказа внутри категории, накопительная сумма, значение предыдущей даты и так далее.
Что значит окно в SQL
$$ window\_metric_i = f(x_1, \ldots, x_i, \ldots, x_n) $$Оконная функция считает метрику не для всей таблицы целиком и не только после схлопывания в группы, а в рамках выбранного окна строк, сохраняя каждую исходную запись.
window\_metric_i— значение метрики для текущей строки if— агрегирующее правило, например сумма, среднее или рангx_1 \ldots x_n— строки, попавшие в текущее окно по partition и order
Интуитивно окно — это способ сказать: «посмотри на текущую строку не изолированно, а в контексте других строк из той же группы или последовательности».
Чем это отличается от group by
`group by` схлопывает строки в агрегированную таблицу. Это отлично, когда нам нужен итог по группе. Но если после этого мы хотим вернуть агрегат обратно к каждой строке, начинается лишняя механика. Оконные функции позволяют сразу получить и исходную строку, и агрегатное окружение вокруг нее. За счет этого проще строить признаки и диагностические отчеты.
Например, можно для каждого заказа посчитать средний чек пользователя, ранг заказа по времени и накопительную сумму покупок — и все это в одном табличном представлении.
Как показать идею на Python-аналоге
import pandas as pd # создаем таблицу заказов для демонстрации оконной логики
orders = pd.DataFrame({ # задаем пользователя, дату и сумму заказа
'user_id': [1, 1, 1, 2, 2], # ключ пользователя
'day': [1, 3, 5, 2, 4], # порядок событий во времени
'amount': [100, 250, 180, 300, 150], # сумма конкретного заказа
})
orders = orders.sort_values(['user_id', 'day']) # имитируем order by внутри окна
orders['rank_in_user'] = orders.groupby('user_id').cumcount() + 1 # считаем ранг заказа в истории пользователя
orders['cum_amount'] = orders.groupby('user_id')['amount'].cumsum() # накапливаем сумму заказов по пользователю
orders['user_mean_amount'] = orders.groupby('user_id')['amount'].transform('mean') # даем каждой строке средний чек пользователя
print(orders) # смотрим итоговую таблицу с оконными метрикамиПосле такого примера window functions перестают казаться магией. Становится видно, что они нужны не ради красивого синтаксиса, а ради очень конкретного преимущества: считать контекстные метрики, не теряя строковую детализацию данных.