Окна функций
DuckDB поддерживает функции окон, которые могут использовать несколько строк для вычисления значения для каждой строки. Функции окон являются блокирующими операторами, т.е. они требуют буферизации всего своего входного потока, что делает их одними из самых ресурсоёмких операторов в SQL.
Функции окон доступны в SQL начиная с SQL:2003 и поддерживаются основными системами баз данных SQL.
Примеры
Сгенерировать столбец row_number с возрастающими идентификаторами для каждой строки:
SELECT row_number() OVER () FROM sales;
Сгенерировать столбец row_number по порядку времени:
SELECT row_number() OVER (ORDER BY time) FROM sales;
Сгенерировать столбец row_number по порядку времени, разделяя по региону:
SELECT row_number() OVER (PARTITION BY region ORDER BY time) FROM sales;
Вычислить разницу между текущей суммой и предыдущей суммой по порядку времени:
SELECT amount - lag(amount) OVER (ORDER BY time) FROM sales;
Вычислить процент от общей суммы продаж по каждому региону для каждой строки:
SELECT amount / sum(amount) OVER (PARTITION BY region) FROM sales;
Синтаксис
Функции окон могут быть использованы только в SELECT предложении. Для совместноиспользования OVER спецификаций между функциями, используйте WINDOW предложение и используйте OVER ⟨window-name⟩ синтаксис.
Функции окон общего назначения
В таблице ниже представлены доступные функции окон общего назначения.
| Имя | Описание |
|---|---|
cume_dist() | Кумулятивное распределение: (количество строк в группе до текущей или равное текущей) / общее количество строк в группе. |
dense_rank() | Ранг текущей строки *без разрывов*; эта функция учитывает группы равных значений. |
first_value(expr[ IGNORE NULLS]) | Возвращает значение expr, вычисленное для строки, которая является первой строкой (со значением expr отличным от NULL, если IGNORE NULLS задано) в окне. |
lag(expr[, offset[, default]][ IGNORE NULLS]) | Возвращает значение expr для строки, которая находится на offset строках (среди строк с ненулевым значением expr если IGNORE NULLS задано) до текущей строки в окне; если такой строки нет, возвращает значение default (которое должно быть того же типа, что и expr). Оба offset и default вычисляются относительно текущей строки. Если не указано, offset по умолчанию равно 1, и NULL по умолчанию. |
last_value(expr[ IGNORE NULLS]) | Возвращает значение expr для строки, которая является последней строкой (среди строк с ненулевым значением expr если IGNORE NULLS задано) в окне. |
lead(expr[, offset[, default]][ IGNORE NULLS]) | Возвращает значение expr для строки, которая находится на offset строках после текущей строки (среди строк с ненулевым значением expr если IGNORE NULLS задано) в окне; если такой строки нет, возвращает значение default (которое должно быть того же типа, что и expr). Оба offset и default вычисляются относительно текущей строки. Если не указано, offset по умолчанию равно 1, и NULL по умолчанию. |
nth_value(expr, nth[ IGNORE NULLS]) | Возвращает значение expr для n-ой строки (среди строк с ненулевым значением expr если IGNORE NULLS задано) в окне (считая с 1); NULL если такой строки нет. |
ntile(num_buckets) | Целое число от 1 до num_buckets, делящее группу на равные части. |
percent_rank() | Относительный ранг текущей строки: (rank() - 1) / (total partition rows - 1). |
rank_dense() | Ранг текущей строки *без разрывов*. |
rank() | Ранг текущей строки *с разрывами*; такой же, как row_number для первой строки в группе равных значений. |
row_number() | Номер текущей строки в группе, считая с 1. |
cume_dist()
| Описание | Кумулятивное распределение: (количество строк в группе до текущей или равное текущей) / общее количество строк в группе. |
| Тип возвращаемого значения | DOUBLE |
| Пример | cume_dist() |
dense_rank()
| Описание | Ранг текущей строки *без разрывов*; эта функция учитывает группы равных значений. |
| Тип возвращаемого значения | BIGINT |
| Пример | dense_rank() |
| Псевдонимы | rank_dense() |
first_value(expr[ IGNORE NULLS])
| Описание | Возвращает expr для первой строки (со значением expr отличным от NULL, если IGNORE NULLS задано) в окне. |
| Тип возвращаемого значения | Тип, совпадающий с типом expr
|
| Пример | first_value(column) |
lag(expr[, offset[, default]][ IGNORE NULLS])
| Описание | Возвращает expr для строки, которая находится на offset строках до текущей строки в окне (среди строк с ненулевым значением expr если IGNORE NULLS задано); если такой строки нет, возвращает default (которое должно быть того же типа, что и expr). Оба offset и default вычисляются относительно текущей строки. Если не указано, offset по умолчанию равно 1, и NULL по умолчанию. |
| Тип возвращаемого значения | Тип, совпадающий с типом expr
|
| Псевдонимы | lag(column, 3, 0) |
last_value(expr[ IGNORE NULLS])
| Описание | Возвращает expr для последней строки (среди строк с ненулевым значением expr если IGNORE NULLS задано) в окне. |
| Тип возвращаемого значения | Тип, совпадающий с типом expr
|
| Пример | last_value(column) |
lead(expr[, offset[, default]][ IGNORE NULLS])
| Описание | Возвращает expr для строки, которая находится на offset строках после текущей строки в окне (среди строк с ненулевым значением expr если IGNORE NULLS задано); если такой строки нет, возвращает default (которое должно быть того же типа, что и expr). Оба offset и default вычисляются относительно текущей строки. Если не указано, offset по умолчанию равно 1, и NULL по умолчанию. |
| Тип возвращаемого значения | Тип, совпадающий с типом expr
|
| Псевдонимы | lead(column, 3, 0) |
nth_value(expr, nth[ IGNORE NULLS])
| Описание | Возвращает expr для n-ой строки (среди строк с ненулевым значением expr если IGNORE NULLS задано) в окне (считая с 1); NULL если такой строки нет. |
| Тип возвращаемого значения | Тип, совпадающий с типом expr
|
| Псевдонимы | nth_value(column, 2) |
ntile(num_buckets)
| Описание | Целое число от 1 до num_buckets, делящее группу на равные части. |
| Тип возвращаемого значения | BIGINT |
| Пример | ntile(4) |
percent_rank()
| Описание | Относительный ранг текущей строки: (rank() - 1) / (total partition rows - 1). |
| Тип возвращаемого значения | DOUBLE |
| Пример | percent_rank() |
rank_dense()
| Описание | Номер строки текущей записи без пропусков. |
| Тип возвращаемого значения | BIGINT |
| Пример | rank_dense() |
| Псевдонимы | dense_rank() |
rank()
| Описание | Номер строки текущей записи с пропусками; совпадает с номером строки первой записи в группе. |
| Тип возвращаемого значения | BIGINT |
| Пример | rank() |
row_number()
| Описание | Номер текущей строки в рамках раздела, считая с 1. |
| Тип возвращаемого значения | BIGINT |
| Пример | row_number() |
Агрегатные функции окон
Все агрегатные функции могут использоваться в контексте окон, включая необязательную FILTER. Функции first и last затенены соответствующими универсальными оконными функциями, с незначительным последствием, что FILTER недоступно для них, но IGNORE NULLS есть.
Пропуски (Nulls)
Все универсальные оконные функции, которые принимают IGNORE NULLS, по умолчанию учитывают пропуски. Это поведение по умолчанию можно дополнительно сделать явным с помощью RESPECT NULLS.
В отличие от этого, все агрегатные оконные функции (за исключением list и её псевдонимов, которые можно настроить на игнорирование пропусков с помощью FILTER) игнорируют пропуски и не принимают RESPECT NULLS. Например, sum(column) OVER (ORDER BY time) AS cumulativeColumn вычисляет кумулятивную сумму, где строки со значением NULL column имеют то же значение cumulativeColumn , что и предшествующая им строка.
Вычисление
Оконные функции работают, разбивая отношение на независимые разделы, сортируя эти разделы и затем вычисляя для каждой строки новый столбец как функцию от близлежащих значений. Некоторые оконные функции зависят только от границ раздела и сортировки, но некоторые (включая все агрегаты) также используют кадр. Кадры задаются как количество строк по обе стороны (предшествующие или последующие) от текущей строки. Расстояние можно задать как число строк или диапазон значений, используя значение сортировки раздела и расстояние.
Полный синтаксис показан на диаграмме в верхней части страницы, и эта диаграмма визуально иллюстрирует вычислительную среду:
Разделение и Сортировка
Разделение разбивает отношение на независимые, несвязанные части. Разделение необязательно, и если оно не указано, то всё отношение обрабатывается как один раздел. Окна функций не могут получить доступ к значениям за пределами раздела, содержащего строку, для которой они вычисляются.
Сортировка также необязательна, но без неё результаты не определены. Каждый раздел сортируется с использованием той же сортировки.
Ниже приведена таблица данных по выработке электроэнергии, доступная в формате CSV (power-plant-generation-history.csv). Для загрузки данных выполните:
CREATE TABLE "Generation History" AS
FROM 'power-plant-generation-history.csv'; После разделения по заводу и сортировки по дате, она будет иметь такой вид:
| Завод | Дата | MWh |
|---|---|---|
| Boston | 2019-01-02 | 564337 |
| Boston | 2019-01-03 | 507405 |
| Boston | 2019-01-04 | 528523 |
| Boston | 2019-01-05 | 469538 |
| Boston | 2019-01-06 | 474163 |
| Boston | 2019-01-07 | 507213 |
| Boston | 2019-01-08 | 613040 |
| Boston | 2019-01-09 | 582588 |
| Boston | 2019-01-10 | 499506 |
| Boston | 2019-01-11 | 482014 |
| Boston | 2019-01-12 | 486134 |
| Boston | 2019-01-13 | 531518 |
| Worcester | 2019-01-02 | 118860 |
| … | … | … |
В дальнейшем мы будем использовать эту таблицу (или небольшие её части) для иллюстрации различных аспектов вычисления оконных функций.
Самая простая оконная функция — row_number(). Эта функция просто вычисляет номер строки в рамках раздела, начиная с 1, используя запрос:
SELECT
"Plant",
"Date",
row_number() OVER (PARTITION BY "Plant" ORDER BY "Date") AS "Row"
FROM "Generation History"
ORDER BY 1, 2; Результат будет следующим:
| Завод | Дата | НомерСтроки |
|---|---|---|
| Boston | 2019-01-02 | 1 |
| Boston | 2019-01-03 | 2 |
| … | … | … |
Обратите внимание, что даже если функция вычисляется с ORDER BY запросом, результат не обязательно должен быть отсортирован, поэтому SELECT также должен быть явно отсортирован, если это необходимо.
Кадрирование
Кадрирование определяет набор строк относительно каждой строки, где функция оценивается. Расстояние от текущей строки даётся как выражение, либо PRECEDING или FOLLOWING текущей строки. Это расстояние может быть задано как целое число строк или как выражение смещения диапазона от значения выражения упорядочивания. Для спецификации RANGE должно быть только одно выражение упорядочивания, и оно должно поддерживать сложение и вычитание (т.е. числа или INTERVAL). Значения по умолчанию для кадров от UNBOUNDED PRECEDING до CURRENT ROW. Недопустимо, чтобы кадр начинался позже, чем заканчивается. С помощью EXCLUDE строки вокруг текущей строки можно исключить из кадра.
Кадрирование строк
Вот простой запрос с кадрированием строк, использующий агрегатную функцию:
SELECT points,
sum(points) OVER (
ROWS BETWEEN 1 PRECEDING
AND 1 FOLLOWING) we
FROM results; Этот запрос вычисляет sum каждой точки и точек по обе стороны от неё:
Обратите внимание, что на границе раздела суммируются только две точки. Это потому, что кадры обрезаются до границы раздела.
Кадрирование диапазона
Вернёмся к данным по электростанциям. Предположим, что данные шумят. Возможно, мы хотим вычислить 7-дневное скользящее среднее для каждого завода, чтобы сгладить шум. Для этого мы можем использовать следующий оконный запрос:
SELECT "Plant", "Date",
avg("MWh") OVER (
PARTITION BY "Plant"
ORDER BY "Date" ASC
RANGE BETWEEN INTERVAL 3 DAYS PRECEDING
AND INTERVAL 3 DAYS FOLLOWING)
AS "MWh 7-day Moving Average"
FROM "Generation History"
ORDER BY 1, 2; Этот запрос разделяет данные по Plant (чтобы разделять данные разных электростанций), упорядочивает каждое разбиение электростанции по Date (чтобы разместить измерения энергии рядом друг с другом) и использует RANGE кадр из трёх дней по обе стороны от каждого дня для avg (чтобы обработать любые пропущенные дни). Вот результат:
| Электростанция | Дата | 7-дневное скользящее среднее МВт·ч |
|---|---|---|
| Бостон | 2019-01-02 | 517450.75 |
| Бостон | 2019-01-03 | 508793.20 |
| Бостон | 2019-01-04 | 508529.83 |
| … | … | … |
| Бостон | 2019-01-13 | 499793.00 |
| Уорчестер | 2019-01-02 | 104768.25 |
| Уорчестер | 2019-01-03 | 102713.00 |
| Уорчестер | 2019-01-04 | 102249.50 |
| … | … | … |
EXCLUDE Оператор
Оператор EXCLUDE позволяет исключать строки вокруг текущей строки из кадра. У него есть следующие параметры:
-
EXCLUDE NO OTHERS: ничего не исключать (по умолчанию) -
EXCLUDE CURRENT ROW: исключить текущую строку из кадра -
EXCLUDE GROUP: исключить текущую строку и все её аналоги (согласно столбцам, указанным вORDER BY) из кадра -
EXCLUDE TIES: исключить только аналоги текущей строки из кадра
WINDOW Операторы
Несколько разных OVER операторов могут быть указаны в одном SELECT, и каждый из них будет вычислен отдельно. Однако часто нам нужно использовать один и тот же макет для нескольких оконных функций. Оператор WINDOW позволяет определить именованное окно, которое можно использовать совместно между несколькими оконными функциями:
SELECT "Plant", "Date",
min("MWh") OVER seven AS "MWh 7-day Moving Minimum",
avg("MWh") OVER seven AS "MWh 7-day Moving Average",
max("MWh") OVER seven AS "MWh 7-day Moving Maximum"
FROM "Generation History"
WINDOW seven AS (
PARTITION BY "Plant"
ORDER BY "Date" ASC
RANGE BETWEEN INTERVAL 3 DAYS PRECEDING
AND INTERVAL 3 DAYS FOLLOWING)
ORDER BY 1, 2; Три оконные функции также будут использовать один и тот же макет данных, что улучшит производительность.
Несколько окон можно определить в одном операторе WINDOW с помощью запятой между ними:
SELECT "Plant", "Date",
min("MWh") OVER seven AS "MWh 7-day Moving Minimum",
avg("MWh") OVER seven AS "MWh 7-day Moving Average",
max("MWh") OVER seven AS "MWh 7-day Moving Maximum",
min("MWh") OVER three AS "MWh 3-day Moving Minimum",
avg("MWh") OVER three AS "MWh 3-day Moving Average",
max("MWh") OVER three AS "MWh 3-day Moving Maximum"
FROM "Generation History"
WINDOW
seven AS (
PARTITION BY "Plant"
ORDER BY "Date" ASC
RANGE BETWEEN INTERVAL 3 DAYS PRECEDING
AND INTERVAL 3 DAYS FOLLOWING),
three AS (
PARTITION BY "Plant"
ORDER BY "Date" ASC
RANGE BETWEEN INTERVAL 1 DAYS PRECEDING
AND INTERVAL 1 DAYS FOLLOWING)
ORDER BY 1, 2; В представленных запросах не используется ряд операторов, обычно встречающихся в операторах SELECT, например, WHERE, GROUP BY, и т.д. Для более сложных запросов вы можете узнать, где операторы WINDOW находятся в стандартном порядке операторов SELECT statement.
Фильтрация результатов оконных функций с помощью QUALIFY
Оконные функции выполняются после того, как операторы WHERE и HAVING уже были оценены, поэтому использовать эти операторы для фильтрации результатов оконных функций нельзя. Оператор QUALIFY избавляет от необходимости использовать подзапрос или оператор WITH для этой фильтрации.
Запросы с ящиковой диаграммой
Все агрегатные функции могут использоваться как оконные функции, включая сложные статистические функции. Эти реализации функций оптимизированы для оконных функций, и мы можем использовать синтаксис окон для написания запросов, которые генерируют данные для построения скользящих ящиковых диаграмм:
SELECT "Plant", "Date",
min("MWh") OVER seven AS "MWh 7-day Moving Minimum",
quantile_cont("MWh", [0.25, 0.5, 0.75]) OVER seven
AS "MWh 7-day Moving IQR",
max("MWh") OVER seven AS "MWh 7-day Moving Maximum",
FROM "Generation History"
WINDOW seven AS (
PARTITION BY "Plant"
ORDER BY "Date" ASC
RANGE BETWEEN INTERVAL 3 DAYS PRECEDING
AND INTERVAL 3 DAYS FOLLOWING)
ORDER BY 1, 2;
© Copyright 2018–2024 Stichting DuckDB Foundation
Licensed under the MIT License.
https://duckdb.org/docs/sql/functions/window_functions.html