2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 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,
       row_number() OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;
  depname  | empno | salary | row_number
-----------+-------+--------+------------
 develop   |     8 |   6000 |          1
 develop   |    10 |   5200 |          2
 develop   |    11 |   5200 |          3
 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 |          3
(10 rows)

如图所示,row_number窗口函数根据ORDER BY子句定义的顺序为每个分区内的行分配顺序编号(具有相同值的行按未指定顺序编号)。row_number不需要显式参数,因为其行为完全由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,
     row_number() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
     FROM empsalary
  ) AS ss
WHERE pos < 3;

上面的查询仅显示内部查询中row_number小于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 节

提交更正

如果您在文档中看到任何不正确的内容、与您对特定功能的经验不符或需要进一步澄清的内容,请使用此表格报告文档问题。