选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.0 已于 2015 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

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表达式值的行划分为组,也就是分区。对于每一行,窗口函数都是在与当前行处于同一分区的那些行上计算的。

尽管avg无论以何种顺序处理分区中的行都会产生相同的结果,但并非所有窗口函数都是如此。必要时,你可以使用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子句产生并经WHEREGROUP BYHAVING子句(如果有)过滤后的那个虚拟表中的行。例如,由于不满足WHERE条件而被删除的行,不会被任何窗口函数看到。一个查询可以包含多个窗口函数,它们可以通过不同的OVER子句以不同方式划分数据,但它们都作用于这个虚拟表所定义的同一组行。

我们已经看到,如果行的顺序并不重要,就可以省略ORDER BYPARTITION BY也可以省略,这时所有行构成一个分区。

与窗口函数相关的另一个重要概念是:对于每一行,在其所在分区内有一组行,称为它的窗口帧。许多(但不是全部)窗口函数只作用于窗口帧中的行,而不是整个分区中的所有行。默认情况下,如果提供了ORDER BY,则窗口帧包含从分区起始处直到当前行的所有行,再加上根据ORDER BY子句与当前行相等的所有后续行。如果省略ORDER BY,默认窗口帧则包含该分区中的所有行。 [4] 下面是一个使用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.19 节第 7.2.4 节以及SELECT参考页中找到。



[4] 还可以用其他方式定义窗口帧,但本教程不涉及这些选项。细节见第 4.2.8 节

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。