受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

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子句定义的顺序,为每个分区内的各行分配连续编号(并列行的编号顺序未指定)。rank不需要显式参数,因为它的行为完全由OVER子句决定。

窗口函数所考虑的行,是查询的FROM子句产生并经WHEREGROUP BYHAVING子句(如果有)过滤后的那个虚拟表中的行。例如,由于不满足WHERE条件而被删除的行,不会被任何窗口函数看到。一个查询可以包含多个窗口函数,它们可以通过不同的OVER子句以不同方式划分数据,但它们都作用于这个虚拟表所定义的同一组行。

我们已经看到,如果行的顺序并不重要,就可以省略ORDER BYPARTITION 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);

关于窗口函数的更多细节可以在Section 4.2.8Section 9.22Section 7.2.5以及SELECT参考页中找到。



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

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。