3.5. Оконные функции#

3.5. Оконные функции

3.5. Оконные функции #

Оконная функция выполняет вычисление по набору строк таблицы, которые каким-то образом связаны с текущей строкой. Действие функции сравнимо с типом вычисления, которое выполняется с помощью агрегатной функции. Однако оконные функции не приводят к группировке строк в одну выходную строку, как это делают не оконные агрегатные функции. Вместо этого строки сохраняют свои отдельные идентификаторы. За кулисами оконная функция может получить доступ не к только текущей строке результата запроса.

Вот пример, который показывает, как сравнить зарплату каждого сотрудника со средней зарплатой в отделе:

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)

Выше, поскольку в предложении OVER не указано ORDER BY, рамка окна совпадает с секцией, который, вследствие отсутствия 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);

Более подробную информацию по оконным функциям можно найти в Раздел 4.2.8, Раздел 9.22, Раздел 7.2.5 и на странице SELECT.



[5] Существуют варианты определения оконной рамки другими способами, но этот документ не рассматривает их. См. Раздел 4.2.8 для получения подробной информации.