1. Основы оконных функций: OVER, PARTITION BY, ORDER BY
Основы оконных функций: OVER, PARTITION BY, ORDER BY
Оконные функции (window functions) — один из самых важных инструментов SQL для аналитика данных. Они позволяют считать метрики по строкам, не «схлопывая» результат в одну строку на группу, как это делает GROUP BY.
В этой статье разберём базовый синтаксис и смысл трёх ключевых элементов:
OVER — «включает режим окна»PARTITION BY — задаёт группы строк внутри окнаORDER BY — задаёт порядок строк внутри окнаВ следующих материалах курса обычно переходят к рамкам окна (frame: ROWS/RANGE) и более сложным приёмам.
Что такое «окно» в SQL
Окно — это набор строк, которые связаны с текущей строкой и доступны для вычисления.
Главная идея:
GROUP BY уменьшает число строк (агрегация возвращает по строке на группу)Пример на уровне смысла:
GROUP BY department даст по одной строке на отделSUM(salary) OVER (PARTITION BY department) оставит каждую строку сотрудника, но добавит колонку с суммой зарплат по отделуБазовый синтаксис
Типовой шаблон выглядит так:
Важные замечания:
OVER (...) — обязательная часть, которая говорит SQL: «считай как оконную функцию»PARTITION BY и ORDER BY внутри OVER — опциональны (могут отсутствовать)FROM/WHERE и до финального ORDER BY запроса (это важно для корректного понимания результата)Официальные справки (для сверки синтаксиса в вашем диалекте SQL):
Учебный пример данных
Будем использовать такую «мысленную» таблицу sales:
| order_id | dt | manager | region | amount | |---------:|------------|---------|--------|-------:| | 1 | 2025-01-01 | Anna | East | 100 | | 2 | 2025-01-03 | Anna | East | 50 | | 3 | 2025-01-02 | Boris | East | 80 | | 4 | 2025-01-01 | Boris | West | 40 | | 5 | 2025-01-04 | Anna | East | 120 |
OVER: что меняется, когда он появляется
OVER задаёт контекст вычисления для функции.
Сравним два запроса.
Агрегация с GROUP BY (результат станет короче):
Оконная сумма (результат останется построчным):
Интерпретация:
manager_sum повторяется для всех заказов одного менеджераPARTITION BY: как задаются группы внутри окна
PARTITION BY делит строки на независимые «корзины» (partition). Оконная функция считается отдельно внутри каждой корзины.
Примеры:
PARTITION BY manager — расчёты отдельно по каждому менеджеруPARTITION BY region — отдельно по каждому регионуPARTITION BY manager, region — отдельно по каждой паре «менеджер-регион»Частая аналитическая задача: доля строки в группе
Например, хотим получить долю каждой продажи в сумме продаж менеджера:
Замечания:
1.0 часто используют, чтобы избежать целочисленного деления в некоторых СУБДNULLIF), но в данном наборе данных суммы не нулевыеЕсли PARTITION BY не указан
Если написать SUM(amount) OVER (), то окно — это все строки результата запроса.
total_sum будет одинаковым для каждой строки.
ORDER BY внутри OVER: порядок строк внутри окна
ORDER BY в OVER задаёт порядок строк для вычисления оконной функции. Это не то же самое, что финальный ORDER BY в конце запроса.
Ключевое следствие:
ROW_NUMBER, LAG, LEAD) требуют осмысленного порядкаSUM, AVG) наличие ORDER BY часто превращает метрику в «накопительную» (подробнее и строго — через рамку окна, но базовую интуицию можно получить уже здесь)ROW_NUMBER: нумерация строк внутри группы
Нумеруем заказы каждого менеджера по дате:
Смысл rn:
manager нумерация начинается с 1dtРейтинг: чем отличается от ROW_NUMBER
В аналитике часто нужен «ранг» с учётом одинаковых значений. Для этого обычно используют RANK() или DENSE_RANK().
Пример: ранжируем продажи менеджера по сумме заказа (от большего к меньшему):
Интуитивное различие:
RANK() может «пропускать» номера после одинаковых значенийDENSE_RANK() не пропускает номераКак вместе работают PARTITION BY и ORDER BY
Комбинация PARTITION BY + ORDER BY — самая частая в аналитике:
PARTITION BY отвечает на вопрос: «внутри каких групп мы считаем?»ORDER BY отвечает на вопрос: «в каком порядке мы смотрим строки внутри группы?»Пример бизнес-задачи: для каждого менеджера показать накопительный итог продаж по времени (упрощённо, без углубления в рамки окна):
Как это читать:
manager строки упорядочены по dtrunning_sum растёт по мере продвижения по датамORDER BY manager, dt добавлен только для удобного отображения результата (он не задаёт логику окна)> В разных СУБД детали «накопительной суммы» с SUM(...) OVER (ORDER BY ...) могут зависеть от настроек рамки окна по умолчанию. На практике для воспроизводимого результата рамку явно задают через ROWS BETWEEN .... Это будет отдельной темой следующих статей.
Типичные ошибки начинающих
ORDER BY внутри OVER и финальный ORDER BY запроса.GROUP BY.ROW_NUMBER() без понятного ORDER BY (тогда нумерация может быть нестабильной).PARTITION BY, когда нужна независимая логика «по клиенту / по менеджеру / по товару».Практический чек-лист аналитика
Перед тем как написать оконную функцию, проговорите:
PARTITION BY.ORDER BY в OVER.GROUP BY.Что дальше по курсу
Дальнейшие темы обычно строятся на этой базе:
ROWS и RANGE, UNBOUNDED PRECEDING, CURRENT ROWLAG, LEAD, FIRST_VALUE, LAST_VALUE