OVER, PARTITION BY и WINDOW
Механизм оконных функций, появившийся в стандарте SQL:2003 и расширенный в стандарте SQL:2011, позволяет выполнять вычисления над набором строк таблицы, который некоторым образом соотносится с текущей строкой.
В отличие от агрегатных функций при этом не происходит группировка нескольких строк в одну – после применения оконных функций число строк в результирующей таблице всегда совпадает с числом строк в исходной.
При наличии в запросе агрегатных и оконных функций сначала производится группировка и вычисляются значения агрегатных функций. Вычисленные значения агрегатных функций могут использоваться в качестве аргументов оконных (но не наоборот). Порядок, в котором вычисляются оконные функции относительно других элементов запроса, описан в разделе SELECT.
Синтаксис
Общий синтаксис вызова оконной функции имеет вид
function_name([expression [, expression ...]]) OVER (window_definition)
или
function_name([expression [, expression ...]]) OVER window_name
Здесь window_name (имя окна) – произвольный идентификатор, уникальный в рамках запроса, expression – произвольное выражение не содержащее вызова оконных функций.
В запросе каждому имени окна должно быть сопоставлено определение окна (window_definition):
SELECT
F0(...) OVER (window_definition_0),
F1(...) OVER w1,
F2(...) OVER w2,
...
FROM my_table
WINDOW
w1 AS (window_definition_1),
...
w2 AS (window_definition_2)
;
Здесь window_definition записывается в виде
[ PARTITION BY (expression AS column_identifier | column_identifier) [, ...] ]
[ ORDER BY expression [ASC | DESC] ]
[ frame_definition ]
Необязательное определение рамки (frame_definition) может быть задано одним из следующих способов:
ROWS frame_beginROWS BETWEEN frame_begin AND frame_endRANGE frame_beginRANGE BETWEEN frame_begin AND frame_end
Примечание
Режим RANGE доступен начиная с версии языка 2026.01.
В Query Tracker для использования рамок RANGE выберите версию языка 2026.01 или новее.
Начало рамки (frame_begin) и конец рамки (frame_end) задаются одним из следующих способов:
UNBOUNDED PRECEDINGoffset PRECEDINGCURRENT ROWoffset FOLLOWINGUNBOUNDED FOLLOWING
Здесь смещение рамки (offset) — неотрицательный литерал. Если конец рамки не задан, подразумевается CURRENT ROW.
В режиме ROWS смещение всегда целочисленное. В режиме RANGE со смещением ORDER BY должен содержать ровно один столбец; тип смещения определяется типом этого столбца и должен поддерживать с ним сложение, вычитание и сравнение. Для числовых типов, включая Decimal, используется числовое смещение; для типов даты и времени — Interval или Interval64. PostgreSQL-типы поддерживаются аналогично. Значения NaN и Inf использовать нельзя.
Все выражения внутри определения окна не должны содержать вызовов оконных функций.
Алгоритм вычисления
Разбиение
Указание PARTITION BY группирует строки исходной таблицы в разделы, которые затем обрабатываются независимо друг от друга. Если PARTITION BY не указан, то все строки исходной таблицы попадают в один раздел. Указание ORDER BY определяет порядок строк в разделе.
В PARTITION BY, как и в GROUP BY можно использовать алиасы и SessionWindow.
При отсутствии ORDER BY порядок строк в разделе не определён.
Рамка
Определение рамки frame_definition задаёт множество строк раздела, попадающих в рамку окна, связанную с текущей строкой.
В режиме ROWS в рамку окна попадают строки с указанными смещениями относительно текущей строки раздела. Например, для ROWS BETWEEN 3 PRECEDING AND 5 FOLLOWING в рамку окна попадут три строки перед текущей, текущая строка и пять строк после неё.
В режиме RANGE со смещением диапазон задаётся значениями единственного столбца ORDER BY, а не числом строк. Например, RANGE BETWEEN 3 PRECEDING AND 5 FOLLOWING включает строки со значением от «текущее минус 3» до «текущее плюс 5». RANGE CURRENT ROW включает все строки с тем же значением сортировки. UNBOUNDED PRECEDING и UNBOUNDED FOLLOWING имеют тот же смысл, что в режиме ROWS.
Множество строк в рамке окна может меняться в зависимости от того, какая строка является текущей. Например, для первой строки раздела в рамку окна ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING не попадет ни одной строки.
Указание UNBOUNDED PRECEDING в качестве начала рамки означает "от первой строки раздела", UNBOUNDED FOLLOWING в качестве конца рамки – "до последней строки раздела", CURRENT ROW – "от/до текущей строки".
Если определение_рамки не указано, то в множество строк попадающих в рамку окна определяется наличием ORDER BY в определении_окна.
А именно, при наличии ORDER BY неявно подразумевается ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, а при отсутствии – ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
Далее, в зависимости от конкретной оконной функции производится ее вычисление либо на множестве строк раздела, либо на множестве строк рамки окна.
Список доступных оконных функций
SELECT
ts,
AVG(value) OVER w AS moving_avg
FROM my_table
WINDOW w AS (
ORDER BY ts
RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING
);
Примеры
SELECT
COUNT(*) OVER w AS rows_count_in_window,
some_other_value -- доступ к текущей строке
FROM `my_table`
WINDOW w AS (
PARTITION BY partition_key_column
ORDER BY int_column
);
SELECT
LAG(my_column, 2) OVER w AS row_before_previous_one
FROM `my_table`
WINDOW w AS (
PARTITION BY partition_key_column
);
SELECT
-- AVG (как и все агрегатные функции, используемые в качестве оконных)
-- вычисляется на рамке окна
AVG(some_value) OVER w AS avg_of_prev_current_next,
some_other_value -- доступ к текущей строке
FROM my_table
WINDOW w AS (
PARTITION BY partition_key_column
ORDER BY int_column
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
);
SELECT
-- LAG не зависит от положения рамки окна
LAG(my_column, 2) OVER w AS row_before_previous_one
FROM my_table
WINDOW w AS (
PARTITION BY partition_key_column
ORDER BY my_column
);
Особенности реализации
-
Функции на рамке
ROWS/RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGлибоROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWвычисляются за O(размер раздела) без дополнительной памяти.RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWтребует памяти для строк с одинаковым значениемORDER BY. -
Для рамки окна
ROWS/RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGможно выбрать стратегию выполнения в памяти, указав хинтCOMPACTпослеPARTITION.Например:
PARTITION /*+ COMPACT() */ BY keyилиPARTITION /*+ COMPACT() */ BY ()(в случае еслиPARTITION BYизначально отсутствовал).При наличии хинта
COMPACTпотребуется дополнительная память в размере O(размер раздела), но при этом не возникнет дополнительнойJOINоперации. -
Если рамка окна не начинается с
UNBOUNDED PRECEDING, то для вычисления оконных функций на таком окне потребуется дополнительная память в размере O(максимальное расстояние от границ окна до текущей строки), а время вычисления будет равно O(число_строк_в_разделе * размер_окна). -
Для рамки, начинающейся с
UNBOUNDED PRECEDINGи заканчивающейся наN, гдеNне равенCURRENT ROWилиUNBOUNDED FOLLOWING, потребуется O(N) дополнительной памяти, а время вычисления будет O(размер раздела). -
Функции
LEAD(expr, N)иLAG(expr, N)всегда потребуют O(N) памяти.
Запрос с ROWS/RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING по возможности стоит переделать в ROWS/RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, поменяв порядок ORDER BY на обратный.
В терминах MapReduce оконные функции физически выполняются через Reduce по ключам PARTITION BY, что может означать длительное выполнение для разделов большого размера, а также жёсткий лимит в 200Гб на раздел для основных кластеров YTsaurus.