2024 年 9 月 26 日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:开发
不支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4

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子句精确地确定了查询行如何被划分为窗口函数处理的组。 OVER中的PARTITION BY子句将行划分为组,或分区,这些组共享PARTITION BY表达式相同的的值。对于每一行,窗口函数都在包含在与当前行相同分区中的行上计算。

您还可以使用OVER中的ORDER BY控制窗口函数处理行的顺序。(窗口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子句生成的虚拟表的行,并通过其WHEREGROUP BYHAVING子句(如果有)进行过滤。例如,由于不满足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 BYHAVINGWHERE子句中。这是因为它们在逻辑上是在这些子句处理之后执行的。此外,窗口函数在非窗口聚合函数之后执行。这意味着在窗口函数的参数中包含聚合函数调用是有效的,反之则不然。

如果需要在窗口计算执行后过滤或分组行,您可以使用子查询。例如

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 节

提交更正

如果您在文档中发现任何不正确的内容,与您对特定功能的体验不符,或者需要进一步说明,请使用此表格报告文档问题。