Функции окон ColumnStore
Введение
MariaDB ColumnStore поддерживает функции окон, в основном следуя спецификации SQL 2003. Функция окна позволяет выполнять вычисления, относящиеся к окну данных, окружающему текущую строку в наборе результатов. Эта возможность упрощает запросы, отвечая на общие бизнес-вопросы, такие как кумулятивные суммы, скользящие средние и списки из 10 лучших.
Функции агрегации используются для функций окон, однако их поведение отличается от запросов с группировкой по, потому что строки остаются не сгруппированными. Это обеспечивает поддержку, например, кумулятивных сумм и скользящих средних.
Два ключевых понятия для функций окон — это Разделение и Фрейм:
- Разделение — это группа строк, или окно, которые имеют одинаковое значение для определенного столбца, например, Разделение может быть создано по периодам времени, таким как квартал или значениям поиска.
- Фрейм для каждой строки — это подмножество разделения строки. Фрейм обычно динамичный, позволяющий скользящему фрейму строк внутри разделения. Фрейм определяет диапазон строк для функции окна. Фрейм можно определить как последние X строк и следующие Y строк до всего Разделения.
Функции окон применяются после вычисления условий объединения, группировки и имеющих.
Синтаксис
Функция окна применяется в предложении select, используя следующий синтаксис:
function_name ([expression [, expression ... ]]) OVER ( window_definition )
где определение_окна определяется как:
[ PARTITION BY expression [, ...] ]
[ ORDER BY expression [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ frame_clause ]
PARTITION BY:
- Разделяет набор результатов окна на группы на основе одного или нескольких выражений.
- Выражение может быть константой, столбцом и выражениями, не являющимися функциями окон.
- Запрос не ограничивается одним предложением partition by. Различные предложения partition by могут использоваться в разных применениях функций окон.
- Столбцы partition by не должны быть в списке select, но должны быть доступны в наборе результатов запроса.
- Если предложение PARTITION BY отсутствует, все строки набора результатов определяют группу.
ORDER BY
- Определяет порядок значений внутри разделения.
- Может быть отсортирован по нескольким ключам, которые могут быть константой, столбцом или выражением, не являющимся функцией окна.
- Столбцы order by не должны быть в списке select, но должны быть доступны в наборе результатов запроса.
- Использование псевдонима столбца select из запроса не поддерживается.
- Опции ASC (по умолчанию) и DESC позволяют сортировать по возрастанию или убыванию.
- Опции NULLS FIRST и NULL_LAST указывают, появляются ли нулевые значения вначале или в конце последовательности сортировки. NULLS_FIRST — по умолчанию для ASC, а NULLS_LAST — по умолчанию для DESC.
и необязательное frame_clause определяется как:
{ RANGE | ROWS } frame_start
{ RANGE | ROWS } BETWEEN frame_start AND frame_end
и необязательные frame_start и frame_end определяются как (значение — числовое выражение):
UNBOUNDED PRECEDING value PRECEDING CURRENT ROW value FOLLOWING UNBOUNDED FOLLOWING
RANGE/ROWS:
- Определяет предложение окна для расчета набора строк, к которым функция применяется для вычисления результата функции окна для данной строки.
- Требуется предложение ORDER BY для определения порядка строк для окна.
- ROWS задают окно в физических единицах, т. е. строки набора результатов и должны быть константой или выражением, вычисляющим положительное числовое значение.
- RANGE задает окно как логическое смещение. Если выражение вычисляет числовое значение, то выражение ORDER BY должно быть числового или типа DATE. Если выражение вычисляет значение интервала, то выражение ORDER BY должно быть типа DATE.
- UNBOUNDED PRECEDING указывает, что окно начинается с первой строки раздела.
- UNBOUNDED FOLLOWING указывает, что окно заканчивается последней строкой раздела.
- CURRENT ROW указывает, что окно начинается или заканчивается на текущей строке или значении.
- Если опущено, по умолчанию — ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
Поддерживаемые функции
| Функция | Описание |
|---|---|
| AVG() | Среднее значение всех входных значений. |
| COUNT() | Количество входных строк. |
| CUME_DIST() | Вычисляет кумулятивное распределение или относительный ранг текущей строки по отношению к другим строкам в том же разделе. Количество строк-собратьев или предшествующих строк / количество строк в разделе. |
| DENSE_RANK() | Присваивает ранг элементам в группе, не оставляя пробелов в последовательности ранга при совпадениях. |
| FIRST_VALUE() | Значение, вычисленное в строке, которая является первой строкой фрейма окна (считая с 1); NULL, если такой строки нет. |
| LAG() | Значение, вычисленное в строке, смещенной на количество строк перед текущей строкой в рамках раздела; если такой строки нет, возвращает значение по умолчанию. Смещение и значение по умолчанию вычисляются относительно текущей строки. Если опущено, смещение по умолчанию равно 1, а значение по умолчанию — NULL. LAG обеспечивает доступ к более чем одной строке таблицы в одно и то же время без самосоединения. Учитывая последовательность строк, возвращаемых запросом, и положение курсора, LAG предоставляет доступ к строке с заданным физическим смещением перед этим положением. |
| LAST_VALUE() | Значение, вычисленное в строке, которая является последней строкой фрейма окна (считая с 1); NULL, если такой строки нет. |
| LEAD() | Обеспечивает доступ к строке с заданным физическим смещением после текущего положения. Возвращает значение, вычисленное в строке, смещенной на количество строк после текущей строки в рамках раздела; если такой строки нет, возвращает значение по умолчанию. Смещение и значение по умолчанию вычисляются относительно текущей строки. Если опущено, смещение по умолчанию равно 1, а значение по умолчанию — NULL. |
| MAX() | Максимальное значение выражения для всех входных значений. |
| MEDIAN() | Обратная функция распределения, предполагающая непрерывную модель распределения. Принимает числовое или временное значение и возвращает среднее значение или интерполированное значение, которое было бы средним значением после сортировки значений. NULL игнорируются при вычислении. |
| MIN() | Минимальное значение выражения для всех входных значений. |
| NTH_VALUE() | Значение, вычисленное в строке, которая является n-й строкой фрейма окна (считая с 1); NULL, если такой строки нет. |
| NTILE() | Разделяет упорядоченный набор данных на количество ведер, указанное выражением expr, и присваивает соответствующий номер ведра каждой строке. Номера ведер варьируются от 1 до expr. Значение expr должно разрешаться в положительную константу для каждого раздела. Целочисленное значение от 1 до значения аргумента, делящее раздел как можно равномернее. |
| PERCENT_RANK() | Относительный ранг текущей строки: (ранг - 1) / (общее количество строк - 1). |
| PERCENTILE_CONT() | Обратная функция распределения, предполагающая непрерывную модель распределения. Принимает процентное значение и спецификацию сортировки и возвращает интерполированное значение, которое попадает в это процентное значение относительно спецификации сортировки. NULL игнорируются при вычислении. |
| PERCENTILE_DISC() | Обратная функция распределения, предполагающая дискретную модель распределения. Принимает процентное значение и спецификацию сортировки и возвращает элемент из набора. NULL игнорируются при вычислении. |
| RANK() | Ранг текущей строки со смещениями; такой же, как номер строки его первого соседа. |
| ROW_NUMBER() | Номер текущей строки в рамках раздела, считая с 1. |
| STDDEV() STDDEV_POP() | Вычисляет стандартное отклонение генеральной совокупности и возвращает квадратный корень из дисперсии генеральной совокупности. |
| STDDEV_SAMP() | Вычисляет кумулятивное выборочное стандартное отклонение и возвращает квадратный корень из дисперсии выборки. |
| SUM() | Сумма выражения для всех входных значений. |
| VARIANCE() VAR_POP() | Дисперсия генеральной совокупности входных значений (квадрат стандартного отклонения генеральной совокупности). |
| VAR_SAMP() | Дисперсия выборки входных значений (квадрат стандартного отклонения выборки). |
Примеры
Пример схемы
Все примеры основаны на следующей упрощенной таблице возможностей продаж:
create table opportunities ( id int, accountName varchar(20), name varchar(128), owner varchar(7), amount decimal(10,2), closeDate date, stageName varchar(11) ) engine=columnstore;
Некоторые примеры значений (спасибо https://www.mockaroo.com за генерацию тестовых данных):
| id | accountName | name | owner | amount | closeDate | stageName |
|---|---|---|---|---|---|---|
| 1 | Browseblab | Многосторонние исполнительные функции | Bob | 26444,86 | 2016-10-20 | Переговоры |
| 2 | Mita | Органическая ориентированная на спрос эталонная модель | Maria | 477878,41 | 2016-11-28 | ClosedWon |
| 3 | Miboo | Деинженеренная гибридная группа программного обеспечения | Olivier | 80181,78 | 2017-01-05 | ClosedWon |
| 4 | Youbridge | Корпоративный графический интерфейс, обеспечивающий основополагающую прибыль | Chris | 946245,29 | 2016-07-02 | ClosedWon |
| 5 | Skyba | Реинженерная свежая, ориентированная на мышление стандартизация | Maria | 696241,82 | 2017-02-17 | Переговоры |
| 6 | Eayo | Фундаментальное хорошо модулированное искусственное интеллектуальное устройство | Bob | 765605,52 | 2016-08-27 | Prospecting |
| 7 | Yotz | Расширенная вторичная инфраструктура | Chris | 319624,20 | 2017-01-06 | ClosedLost |
| 8 | Oloo | Конфигурируемый веб-интегрированный хранилище данных | Chris | 321016,26 | 2017-03-08 | ClosedLost |
| 9 | Kaymbo | Многостороннее веб-интегрированное определение | Bob | 690881,01 | 2017-01-02 | Разработка |
| 10 | Rhyloo | Открытый ключ, согласованная инфраструктура | Chris | 965477,74 | 2016-11-07 | Prospecting |
Схема, пример данных и запросы доступны в качестве вложения к этой статье.
Пример кумулятивной суммы и максимального значения
Функции окон могут использоваться для достижения кумулятивных/текущих вычислений в отчете по деталям. В данном случае отчет о сделках с победами за 7-дневный период добавляет столбцы, чтобы показать накопленную сумму побед, а также текущее максимальное значение возможностей в предыдущих строках.
select owner, accountName, CloseDate, amount, sum(amount) over (order by CloseDate rows between unbounded preceding and current row) cumeWon, max(amount) over (order by CloseDate rows between unbounded preceding and current row) runningMax from opportunities where stageName='ClosedWon' and closeDate >= '2016-10-02' and closeDate <= '2016-10-09' order by CloseDate;
с примерами результатов:
| владелец | название_счета | ДатаЗакрытия | сумма | накопленная_выручка | текущий_макс |
|---|---|---|---|---|---|
| Bill | Babbleopia | 2016-10-02 | 437636.47 | 437636.47 | 437636.47 |
| Bill | Thoughtworks | 2016-10-04 | 146086.51 | 583722.98 | 437636.47 |
| Olivier | Devpulse | 2016-10-05 | 834235.93 | 1417958.91 | 834235.93 |
| Chris | Linkbridge | 2016-10-07 | 539977.45 | 2458738.65 | 834235.93 |
| Olivier | Trupe | 2016-10-07 | 500802.29 | 1918761.20 | 834235.93 |
| Bill | Latz | 2016-10-08 | 857254.87 | 3315993.52 | 857254.87 |
| Chris | Avamm | 2016-10-09 | 699566.86 | 4015560.38 | 857254.87 |
Пример разбиения кумулятивной суммы и текущего максимума
Приведенный выше пример можно разбить, чтобы функции окна применялись к определенной группе полей, например, владельцу, и накапливались внутри этой группы. Это достигается добавлением синтаксиса "разбиение по <колонки>" в предложение функции окна.
select owner, accountName, CloseDate, amount, sum(amount) over (partition by owner order by CloseDate rows between unbounded preceding and current row) cumeWon, max(amount) over (partition by owner order by CloseDate rows between unbounded preceding and current row) runningMax from opportunities where stageName='ClosedWon' and closeDate >= '2016-10-02' and closeDate <= '2016-10-09' order by owner, CloseDate;
с результатами примера:
| владелец | название_счета | ДатаЗакрытия | сумма | накопленная_выручка | текущий_макс |
|---|---|---|---|---|---|
| Bill | Babbleopia | 2016-10-02 | 437636.47 | 437636.47 | 437636.47 |
| Bill | Thoughtworks | 2016-10-04 | 146086.51 | 583722.98 | 437636.47 |
| Bill | Latz | 2016-10-08 | 857254.87 | 1440977.85 | 857254.87 |
| Chris | Linkbridge | 2016-10-07 | 539977.45 | 539977.45 | 539977.45 |
| Chris | Avamm | 2016-10-09 | 699566.86 | 1239544.31 | 699566.86 |
| Olivier | Devpulse | 2016-10-05 | 834235.93 | 834235.93 | 834235.93 |
| Olivier | Trupe | 2016-10-07 | 500802.29 | 1335038.22 | 834235.93 |
Ранжирование/Топ-результаты
Функция окна ранжирования позволяет ранжировать или присваивать порядковый номер на основе определения функции окна. При использовании функции Rank() одинаковые значения будут иметь одинаковый рейтинг, а следующий рейтинг будет пропущен. Функция Dense_Rank() ведет себя аналогично, за исключением того, что после совпадения используется следующее последовательное число, а не пропускается. Функция Row_Number() обеспечит уникальный порядковый номер. В приведенном примере функции Rank() используется для ранжирования торговых представителей по количеству возможностей для 4-го квартала 2016 года.
select owner, wonCount, rank() over (order by wonCount desc) rank from ( select owner, count(*) wonCount from opportunities where stageName='ClosedWon' and closeDate >= '2016-10-01' and closeDate < '2016-12-31' group by owner ) t order by rank;
с результатами примера (обратите внимание, что запрос технически неверен, используя closeDate < '2016-12-31', но это создает сценарий ничьей для целей иллюстрации):
| владелец | число_выигрышей | ранг |
|---|---|---|
| Bill | 19 | 1 |
| Chris | 15 | 2 |
| Maria | 14 | 3 |
| Bob | 14 | 3 |
| Olivier | 10 | 5 |
Если используется функция dense_rank, значения ранга будут 1, 2, 3, 3, 4, а для функции row_number - 1, 2, 3, 4, 5.
Первые и последние значения
Функции first_value и last_value позволяют определить первое и последнее значения заданного диапазона. В сочетании с группировкой по группам это позволяет подытожить начальные и конечные значения. Пример показывает более сложный случай, где представлена подробная информация о первой и последней возможности по кварталу.
select a.year,
a.quarter,
f.accountName firstAccountName,
f.owner firstOwner,
f.amount firstAmount,
l.accountName lastAccountName,
l.owner lastOwner,
l.amount lastAmount
from (
select year,
quarter,
min(firstId) firstId,
min(lastId) lastId
from (
select year(closeDate) year,
quarter(closeDate) quarter,
first_value(id) over (partition by year(closeDate), quarter(closeDate) order by closeDate rows between unbounded preceding and current row) firstId,
last_value(id) over (partition by year(closeDate), quarter(closeDate) order by closeDate rows between current row and unbounded following) lastId
from opportunities where stageName='ClosedWon'
) t
group by year, quarter order by year,quarter
) a
join opportunities f on a.firstId = f.id
join opportunities l on a.lastId = l.id
order by year, quarter;
с результатами примера:
| год | квартал | первое_название_счета | первый_владелец | первая_сумма | последнее_название_счета | последний_владелец | последняя_сумма |
|---|---|---|---|---|---|---|---|
| 2016 | 3 | Skidoo | Bill | 523295.07 | Skipstorm | Bill | 151420.86 |
| 2016 | 4 | Skimia | Chris | 961513.59 | Avamm | Maria | 112493.65 |
| 2017 | 1 | Yombu | Bob | 536875.51 | Skaboo | Chris | 270273.08 |
Предыдущий и следующий пример
Иногда полезно понимать предыдущие и последующие значения в контексте данной строки. Функции окна lag и lead предоставляют эту возможность. По умолчанию смещение равно одному, предоставляя предыдущее или следующее значение, но его также можно указать, чтобы получить большее смещение. Пример запроса представляет собой отчет о возможностях по названию счета, показывающий сумму возможности и сумму предыдущей и следующей возможности для этого счета по дате закрытия.
select accountName, closeDate, amount currentOppAmount, lag(amount) over (partition by accountName order by closeDate) priorAmount, lead(amount) over (partition by accountName order by closeDate) nextAmount from opportunities order by accountName, closeDate limit 9;
с результатами примера:
| название_счета | ДатаЗакрытия | текущая_сумма_возможности | предыдущая_сумма | следующая_сумма |
|---|---|---|---|---|
| Abata | 2016-09-10 | 645098.45 | NULL | 161086.82 |
| Abata | 2016-10-14 | 161086.82 | 645098.45 | 350235.75 |
| Abata | 2016-12-18 | 350235.75 | 161086.82 | 878595.89 |
| Abata | 2016-12-31 | 878595.89 | 350235.75 | 922322.39 |
| Abata | 2017-01-21 | 922322.39 | 878595.89 | NULL |
| Abatz | 2016-10-19 | 795424.15 | NULL | NULL |
| Agimba | 2016-07-09 | 288974.84 | NULL | 914461.49 |
| Agimba | 2016-09-07 | 914461.49 | 288974.84 | 176645.52 |
| Agimba | 2016-09-20 | 176645.52 | 914461.49 | NULL |
Пример квартилей
Функция окна NTile позволяет разделить набор данных на части, каждой из которых присваивается числовое значение. NTile(4) разбивает данные на четыре части (4 набора). В примере запроса представлен отчет обо всех возможностях, в котором суммированы граничные значения квартилей для значений суммы.
select t.quartile, min(t.amount) min, max(t.amount) max from ( select amount, ntile(4) over (order by amount asc) quartile from opportunities where closeDate >= '2016-10-01' and closeDate <= '2016-12-31' ) t group by quartile order by quartile;
С результатами примера:
| квартиль | мин | макс |
|---|---|---|
| 1 | 6337.15 | 287634.01 |
| 2 | 288796.14 | 539977.45 |
| 3 | 540070.04 | 748727.51 |
| 4 | 753670.77 | 998864.47 |
Пример процентилей
Функции процентилей имеют немного другой синтаксис, чем другие функции окон, как видно в примере ниже. Эти функции могут применяться только к числовым значениям. Аргументом функции является процентиль для оценки. После 'внутри группы' следует выражение сортировки, которое указывает столбец сортировки и, необязательно, порядок. Наконец, после 'над' находится необязательная часть "разбиение по", если часть "разбиение по" отсутствует, используется 'над ()'. В примере ниже используется значение 0,5 для вычисления медианы суммы возможностей в строках. Иногда значения отличаются, потому что percentile_cont вернет среднее значение 2 средних строк для четного набора данных, а percentile_desc вернет первое встреченное значение в сортировке.
select owner, accountName, CloseDate, amount, percentile_cont(0.5) within group (order by amount) over (partition by owner) pct_cont, percentile_disc(0.5) within group (order by amount) over (partition by owner) pct_disc from opportunities where stageName='ClosedWon' and closeDate >= '2016-10-02' and closeDate <= '2016-10-09' order by owner, CloseDate;
С результатами примера:
| владелец | название_счета | ДатаЗакрытия | сумма | pct_cont | pct_disc |
|---|---|---|---|---|---|
| Bill | Babbleopia | 2016-10-02 | 437636.47 | 437636.4700000000 | 437636.47 |
| Bill | Thoughtworks | 2016-10-04 | 146086.51 | 437636.4700000000 | 437636.47 |
| Bill | Latz | 2016-10-08 | 857254.87 | 437636.4700000000 | 437636.47 |
| Chris | Linkbridge | 2016-10-07 | 539977.45 | 619772.1550000000 | 539977.45 |
| Chris | Avamm | 2016-10-09 | 699566.86 | 619772.1550000000 | 539977.45 |
| Olivier | Devpulse | 2016-10-05 | 834235.93 | 667519.1100000000 | 500802.29 |
| Olivier | Trupe | 2016-10-07 | 500802.29 | 667519.1100000000 | 500802.29 |
© 2023 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/window-functions-columnstore-window-functions/