↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 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 子句产生并经 WHERE、GROUP BY 和 HAVING 子句(如果有)过滤后的那个“虚拟表”中的行。例如,由于不满足 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 BY、HAVING 和 WHERE 子句中。这是因为从逻辑上讲,它们在这些子句处理完成之后才执行。另外,窗口函数在非窗口聚合函数之后执行。这意味着在窗口函数的参数中包含聚合函数调用是合法的,反过来则不行。

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

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 节。

报告文档问题

阅读 上游文档. 通过 PostgreSQL 文档反馈表单.