Оконные (аналитические) функции
Согласно SQL спецификации оконные функции (также известные как аналитические функции) являются своего рода агрегатными функциями, не уменьшающими степень детализации. При этом агрегированные данные выводятся вместе с неагрегированными.
Синтаксически вызов оконной функции это указание её имени, за которым всегда следует
ключевое слово OVER()
с возможными аргументами внутри скобок. В этом и заключается её
синтаксическое отличие от обычной функции или агрегатной функции. Оконные функции могут
находиться только в списке SELECT
и предложении ORDER BY
.
Предложение OVER
может содержать указания выполнить действия с разбивкой по группам
(«секционирование») и сортировку.
Начнем с простого примера, который показывает как меняются результаты в зависимости от указаний внутри OVER
.
SELECT A, B, C,
SUM(C) OVER(),
SUM(C) OVER(ORDER BY A, B),
SUM(C) OVER(PARTITION BY A),
SUM(C) OVER(PARTITION BY A ORDER BY B)
FROM T
Потенциальные результаты могут быть следующими.
A |
B |
C |
SUM |
SUM1 |
SUM2 |
SUM3 |
---|---|---|---|---|---|---|
1 |
1 |
30 |
141 |
30 |
60 |
30 |
1 |
2 |
20 |
141 |
50 |
60 |
50 |
1 |
3 |
10 |
141 |
60 |
60 |
60 |
2 |
1 |
25 |
141 |
85 |
40 |
25 |
2 |
2 |
15 |
141 |
100 |
40 |
40 |
3 |
1 |
41 |
141 |
141 |
41 |
41 |
Столбцы A
, B
, C
содержат некие данные. В запросе использована оконная функция SUM
по столбцу C
, но с разными указаниям к выполнению суммирования. Посмотрим на них более внимательно.
SUM(C) OVER()
не содержит никаких указаний и для каждой строки выдает один и тот же результат
(столбец SUM в результате)):
сумму всех значений столбца C
.
Такое же значение мы бы получили при простом использовании функции SUM
как аггрегатной и
без предложения GROUP BY
.
Но заметьте что здесь количество строк не уменьшается и не равно количеству групп.
Значение выдается для каждом строки.
SUM(C) OVER(ORDER BY A, B)
(столбец SUM1
) содержит указание к порядку использования значений C в соответствие со
значениями столбцов A
и B
. В таком случае для каждой строки результат SUM(C)
будет выдавать сумму значений
столбца C
с первого до текущего по указанному порядку суммирования. Последнее значение как и следует ожидать
будет равно сумме всех значений C
.
SUM(C) OVER(PARTITION BY A)
(столбец SUM2
) снова не содержит указания к порядку выполнения и там мы видим
снова одинаковые суммы, но разбитые на группы. Это потому что есть указания к сегментированию,
разделению общего набора данных на части. Суммируются значения столбца C
всех строк,
в которых значения поля A
равно значению поля A
текущей строки.
SUM(C) OVER(PARTITION BY A ORDER BY B)
(столбец SUM3) содержит указания и к сегментированию, и к порядку.
В таком случае сумма вычисляется по значениям, входящим в указанную группу начиная с первого до текущего, в указанном порядке.
Суммы, вычисленные по порядку еще называют сумма, вычисленная нарастающим итогом.
Оконные функции могут быть использованы в выражениях списка выбора.
Допустим, у нас есть таблица EMPLOYEE
со столбцами ID
, NAME
и SALARY
. Нам необходимо
показать для каждого сотрудника, соответствующую ему заработную плату и процент от фонда заработной платы.
Простым запросом это решается следующим образом:
SELECT ID, DEPARTMENT, SALARY,
100 * SALARY / (SELECT SUM(SALARY) FROM EMPLOYEE) PERCENTAGE
FROM EMPLOYEE
ORDER BY ID
Результат:
id |
department |
salary |
percentage |
---|---|---|---|
1 |
R&D |
10.00 |
20.40 |
2 |
SALES |
12.00 |
24.48 |
3 |
SALES |
8.00 |
16.32 |
4 |
R&D |
9.00 |
18.36 |
5 |
R&D |
10.00 |
20.40 |
Запрос повторяется и может работать довольно долго, особенно если EMPLOYEE
является
сложным представлением.
Этот запрос может быть переписан в более быстрой и элегантной форме с использованием оконных функций:
SELECT ID, DEPARTMENT, SALARY,
100 * SALARY / SUM(SALARY) OVER () PERCENTAGE
FROM EMPLOYEE
ORDER BY ID
Здесь SUM(SALARY) OVER ()
вычисляет сумму всех зарплат из запроса (таблицы сотрудников).
Секционирование
Как и для агрегатных функций, которые могут работать отдельно или по отношению к группе, оконные функции тоже могут работать для групп, которые называются «секциями» (partition) или разделами.
Синтаксис:
<window function>(...) OVER (PARTITION BY <expr> [, <expr> ...])
Для каждой строки, оконная функция обсчитывает только строки, которые попадают в то же самую секцию, что и текущая строка.
Агрегирование над группой может давать более одной строки, таким образом, к результирующему набору, созданному секционированием, присоединяются результаты из основного запроса, используя тот же список выражений, что и для секции. Продолжая пример с сотрудниками, вместо того чтобы считать процент зарплаты каждого сотрудника от суммарной зарплаты сотрудников, посчитаем процент от суммарной зарплаты сотрудников того же отдела:
SELECT ID, DEPARTMENT, SALARY,
100 * SALARY / SUM(SALARY) OVER (PARTITION BY DEPARTMENT) PERCENTAGE
FROM EMPLOYEE
ORDER BY ID
Результат:
id |
department |
salary |
percentage |
---|---|---|---|
1 |
R&D |
10.00 |
34.48 |
2 |
SALES |
12.00 |
60.00 |
3 |
SALES |
8.00 |
40.00 |
4 |
R&D |
9.00 |
31.03 |
5 |
R&D |
10.00 |
34.48 |
Сортировка
Предложение ORDER BY
может быть использовано с секционированием или без него.
Предложение ORDER BY
внутри OVER
задаёт порядок, в котором оконная функция будет
обрабатывать строки. Этот порядок не обязан совпадать с порядком вывода строк.
Есть ещё одно важное понятие, связанное с оконными функциями: для каждой строки
существует набор строк в её разделе, называемый рамкой окна (кадры окна). По умолчанию, с
указанием ORDER BY
рамка состоит из всех строк от начала раздела до текущей строки и строк,
равных текущей по значению выражения ORDER BY
. Без ORDER BY
рамка по умолчанию
состоит из всех строк раздела.
Таким образом, для стандартных агрегатных функций, предложение ORDER BY
заставляет
возвращать частичные результаты агрегации по мере обработки записей.
Пример:
SELECT ID, SALARY,
SUM(SALARY) OVER (ORDER BY SALARY) AS CUMUL_SALARY
FROM EMPLOYEE
ORDER BY SALARY;
Результат:
ID |
SALARY |
CUMUL_SALARY |
---|---|---|
3 |
8.00 |
8.00 |
4 |
9.00 |
17.00 |
1 |
10.00 |
37.00 |
5 |
10.00 |
37.00 |
2 |
12.00 |
49.00 |
В этом случае CUMUL_SALARY
возвращает частичную/накопительную агрегацию (функции SUM
).
Может показаться странным, что значение 37,00 повторяется для идентификаторов 1 и 5, но так
и должно быть. Сортировка (ORDER BY
) ключей группирует их вместе, и агрегат вычисляется
единожды (но суммируя сразу два значения 10,00). Чтобы избежать этого, вы можете добавить
поле ID в конце предложения ORDER BY
.
Вы можете использовать несколько окон с различными сортировками, и дополнять
предложение ORDER BY
опциями ASC/DESC
и NULLS FIRST/LAST
.
С секциями предложение ORDER BY
работает таким же образом, но на границе каждой секции
агрегаты сбрасываются.
Все агрегатные функции могут использовать предложение ORDER BY
, за исключением LIST()
.