窗口函数对与当前行以某种方式相关的表行集合执行计算。这类似于可以使用聚合函数执行的计算类型。但是,窗口函数不会像非窗口聚合调用那样导致行分组到单个输出行中。相反,这些行保留其各自的身份。实际上,窗口函数不仅能够访问查询结果的当前行。
以下是一个示例,演示如何比较各员工的薪水与其所在部门的平均薪水
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 参考资料中找到。