一个窗口函数会对一组与当前行相关的表行执行计算。这类似于可以使用聚合函数完成的计算类型。然而,窗口函数不会像非窗口聚合调用那样导致行被分组为单个输出行。相反,行保留其独立的身份。在后台,窗口函数能够访问查询结果中除当前行以外的内容。
下面是一个示例,展示了如何将每个员工的薪资与其部门的平均薪资进行比较
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, row_number() OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsalary;
depname | empno | salary | row_number -----------+-------+--------+------------ develop | 8 | 6000 | 1 develop | 10 | 5200 | 2 develop | 11 | 5200 | 3 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 | 3 (10 rows)
如图所示,row_number
窗口函数根据ORDER BY
子句定义的顺序为每个分区内的行分配顺序编号(具有相同值的行按未指定顺序编号)。row_number
不需要显式参数,因为其行为完全由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, row_number() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos FROM empsalary ) AS ss WHERE pos < 3;
上面的查询仅显示内部查询中row_number
小于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);
如果您在文档中看到任何不正确的内容、与您对特定功能的经验不符或需要进一步澄清的内容,请使用此表格报告文档问题。