支持的版本: 当前 (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,
       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 节

提交更正

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