Оконные функции SQL
Также: window functions, оконные функции, over partition by, аналитические функции sql
Оконные функции SQL считают агрегат по группе строк, но не схлопывают их в одну: каждая строка сохраняется и получает рядом значение по своему «окну». Это то, чем считают нарастающий итог, ранг внутри группы, долю от общего и разницу с предыдущей строкой.
Оконные функции (window functions) — способ посчитать агрегат по группе строк, не схлопывая их. Обычный GROUP BY из десяти строк делает одну; оконная функция оставляет все десять и к каждой приписывает значение, посчитанное по её «окну» — набору соседних строк.
Отсюда весь класс задач, который без них решается через подзапросы и самосоединения: нарастающий итог, место внутри категории, доля строки от общего, разница с предыдущим периодом. В продуктовой аналитике это ежедневный инструмент — когорты, воронки по шагам, первое и последнее событие пользователя.
Как устроен синтаксис?
ФУНКЦИЯ() OVER (PARTITION BY ... ORDER BY ... ROWS ...)
- OVER — превращает функцию в оконную. Пустой
OVER ()означает «окно — вся таблица». - PARTITION BY — делит строки на группы. Аналог
GROUP BY, но без схлопывания. - ORDER BY — порядок внутри окна. Обязателен для ранжирования и нарастающих итогов: без него понятие «предыдущая строка» не определено.
- ROWS / RANGE — граница окна: например,
ROWS BETWEEN 6 PRECEDING AND CURRENT ROWдля скользящего среднего за 7 дней.
Какие функции чаще всего нужны?
| Функция | Что делает | Типичная задача |
|---|---|---|
ROW_NUMBER() | Сквозной номер, дубли не учитывает | Первый заказ пользователя |
RANK() / DENSE_RANK() | Ранг с пропуском / без пропуска мест | Топ-3 товара в категории |
LAG() / LEAD() | Значение из предыдущей / следующей строки | Прирост к прошлому месяцу |
SUM() OVER (ORDER BY ...) | Нарастающий итог | Накопленная выручка с начала года |
AVG() OVER (ROWS ...) | Скользящее среднее | Сглаживание дневных колебаний |
SUM() OVER (PARTITION BY ...) | Итог группы рядом со строкой | Доля товара в выручке категории |
Пример с числами
Задача: для каждого месяца показать выручку, накопленный итог и прирост к прошлому месяцу.
SELECT
month,
revenue,
SUM(revenue) OVER (ORDER BY month) AS running_total,
revenue - LAG(revenue) OVER (ORDER BY month) AS mom_change
FROM monthly_revenue
ORDER BY month;
| Месяц | Выручка | Накопленным итогом | К прошлому месяцу |
|---|---|---|---|
| Январь | 4,2 млн | 4,2 млн | — |
| Февраль | 3,9 млн | 8,1 млн | −0,3 млн |
| Март | 5,1 млн | 13,2 млн | +1,2 млн |
| Апрель | 5,4 млн | 18,6 млн | +0,3 млн |
Тот же результат через подзапросы занял бы вчетверо больше строк и читался бы вдвое хуже. Классический пример на собеседовании — «второй по величине заказ каждого клиента»: через ROW_NUMBER() с PARTITION BY это четыре строки, через самосоединение — пятнадцать и ошибка при дублях.
Где чаще всего ошибаются?
- Фильтр по окну в
WHERE.WHERE ROW_NUMBER() OVER (...) = 1не работает. Оберните запрос в CTE и фильтруйте снаружи. RANK()там, где нуженROW_NUMBER(). При одинаковых значенияхRANK()вернёт несколько первых мест, и «взять первую строку» перестанет давать одну строку.- Забыть
ORDER BYвнутри окна. Нарастающий итог без порядка вернёт общую сумму по всем строкам — тихо и без ошибки. - Путать
ROWSиRANGE. При одинаковых значениях вORDER BYони дают разный результат:RANGEвключает все строки-дубликаты,ROWS— строго заданное количество. - Тяжёлое окно на больших таблицах.
PARTITION BYпо полю высокой кардинальности без подходящего индекса приводит к сортировке всей таблицы.
Связанные термины
- Когортный анализ — задача, которую на SQL почти всегда пишут через оконные функции.
- Медиана — считается оконной
PERCENTILE_CONT. - Процентиль — то же семейство функций для распределений.
Продуктовая аналитика 2.0
Углубленный курс продуктовой аналитики с Python, ML и продвинутыми техниками анализа
Посмотреть курс