Window функции выполняют вычисления для набора строк таблицы, которые так или иначе связаны с текущей строкой. Это сравнимо с типом вычислений, которые можно выполнить с помощью агрегатной функции. Однако оконные функции не группируют строки в одну строку вывода, как это сделали бы неоконные агрегатные вызовы. Вместо этого строки сохраняют свои отдельные идентификаторы. За кулисами оконная функция может получить доступ не только к текущей строке результата запроса.
Вот пример, который показывает, как сравнить зарплату каждого сотрудника со средней зарплатой в его или ее отделе:
SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;
depname | empno | salary | avg
-----------+-------+--------+-----------------------
develop | 11 | 5200 | 5020.0000000000000000
develop | 7 | 4200 | 5020.0000000000000000
develop | 9 | 4500 | 5020.0000000000000000
develop | 8 | 6000 | 5020.0000000000000000
develop | 10 | 5200 | 5020.0000000000000000
personnel | 5 | 3500 | 3700.0000000000000000
personnel | 2 | 3900 | 3700.0000000000000000
sales | 3 | 4800 | 4866.6666666666666667
sales | 1 | 5000 | 4866.6666666666666667
sales | 4 | 4800 | 4866.6666666666666667
(10 rows)
Первые три выходных столбца берутся непосредственно из таблицы empsalary
, и для каждой строки таблицы имеется одна выходная строка. Четвертый столбец представляет среднее значение, взятое по всем строкам таблицы, которые имеют то же depname
значение, что и текущая строка. (На самом деле это та же самая функция, что и неоконная avg
агрегатная функция, но OVER
благодаря предложению она рассматривается как оконная функция и вычисляется по оконному фрейму.)
Вызов оконной функции всегда содержит OVER
предложение, непосредственно следующее за именем и аргументом(ами) оконной функции. Это то, что синтаксически отличает ее от обычной функции или неоконной совокупности. Предложение OVER
точно определяет, как строки запроса разбиваются для обработки оконной функцией. Предложение PARTITION BY
внутри OVER
делит строки на группы или разделы, которые имеют одни и те же значения PARTITION BY
выражений. Для каждой строки оконная функция вычисляется для строк, которые попадают в тот же раздел, что и текущая строка.
Вы также можете управлять порядком, в котором строки обрабатываются оконными функциями, используя ORDER BY
внутри OVER
. (Окну ORDER BY
даже не обязательно соответствовать порядку, в котором выводятся строки.) Вот пример:
SELECT depname, empno, salary,
rank() OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;
depname | empno | salary | rank
-----------+-------+--------+------
develop | 8 | 6000 | 1
develop | 10 | 5200 | 2
develop | 11 | 5200 | 2
develop | 9 | 4500 | 4
develop | 7 | 4200 | 5
personnel | 2 | 3900 | 1
personnel | 5 | 3500 | 2
sales | 1 | 5000 | 1
sales | 4 | 4800 | 2
sales | 3 | 4800 | 2
(10 rows)
Как показано здесь, rank
функция создает числовой ранг для каждого отдельного ORDER BY
значения в разделе текущей строки, используя порядок, определенный ORDER BY
предложением. rank
не нуждается в явном параметре, потому что его поведение полностью определяется OVER
предложением.
Строки, рассматриваемые оконной функцией, являются строками « виртуальной таблицы » , созданной предложением запроса, FROM
отфильтрованным его предложениями WHERE
, GROUP BY
, и, HAVING
если таковые имеются. Например, строка, удаленная из-за того, что она не соответствует WHERE
условию, не видна ни одной оконной функции. Запрос может содержать несколько оконных функций, которые разбивают данные по-разному, используя разные OVER
предложения, но все они действуют на один и тот же набор строк, определенный этой виртуальной таблицей.
Мы уже видели, что ORDER BY
его можно опустить, если порядок строк не важен. Также можно опустить PARTITION BY
, и в этом случае будет один раздел, содержащий все строки.
Существует еще одна важная концепция, связанная с оконными функциями: для каждой строки существует набор строк в ее разделе, который называется рамкой окна . Некоторые оконные функции действуют только на строки оконной рамы, а не на весь раздел. По умолчанию, если ORDER BY
указано, кадр состоит из всех строк от начала раздела до текущей строки, а также любых последующих строк, равных текущей строке в соответствии с ORDER BY
предложением. Когда ORDER BY
этот параметр опущен, кадр по умолчанию состоит из всех строк раздела. [5] Вот пример использования sum
:
SELECT salary, sum(salary) OVER () FROM empsalary;
salary | sum
--------+-------
5200 | 47100
5000 | 47100
3500 | 47100
4800 | 47100
3900 | 47100
4200 | 47100
4500 | 47100
4800 | 47100
6000 | 47100
5200 | 47100
(10 rows)
Выше, так как ORDER BY
в OVER
пункте нет, рамка окна такая же, как и раздел, которым за неимением PARTITION BY
является вся таблица; другими словами, каждая сумма берется по всей таблице, поэтому мы получаем одинаковый результат для каждой выходной строки. Но если мы добавим ORDER BY
предложение, мы получим совсем другие результаты:
SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary;
salary | sum
--------+-------
3500 | 3500
3900 | 7400
4200 | 11600
4500 | 16100
4800 | 25700
4800 | 25700
5000 | 30700
5200 | 41100
5200 | 41100
6000 | 47100
(10 rows)
Здесь сумма берется от первой (самой низкой) зарплаты до текущей, включая любые дубликаты текущей (обратите внимание на результаты для дублированных зарплат).
Оконные функции разрешены только в SELECT
списке и ORDER BY
предложении запроса. Они запрещены в других местах, например, в пунктах GROUP BY
, HAVING
и . WHERE
Это потому, что они логически выполняются после обработки этих предложений. Кроме того, оконные функции выполняются после неоконных агрегатных функций. Это означает, что допустимо включать вызов агрегатной функции в аргументы оконной функции, но не наоборот.
Если есть необходимость отфильтровать или сгруппировать строки после выполнения расчетов окна, вы можете использовать вложенный выбор. Например:
SELECT depname, empno, salary, enroll_date
FROM
(SELECT depname, empno, salary, enroll_date,
rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
FROM empsalary
) AS ss
WHERE pos < 3;
Приведенный выше запрос показывает только строки из внутреннего запроса, имеющие rank
менее 3.
Когда запрос включает несколько оконных функций, каждую из них можно записать в отдельном OVER
предложении, но это дублирует и чревато ошибками, если для нескольких функций требуется одинаковое оконное поведение. Вместо этого каждое оконное поведение можно назвать в WINDOW
предложении, а затем сослаться на него в OVER
. Например:
SELECT sum(salary) OVER w, avg(salary) OVER w
FROM empsalary
WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
0 комментариев