窗口函数 对与当前行以某种方式相关的表行集合执行计算。这与可以使用聚合函数完成的计算类型类似。但是,窗口函数不会像非窗口聚合调用那样导致行被分组为单个输出行。相反,行保留其单独的标识。在幕后,窗口函数能够访问的不仅仅是查询结果的当前行。
这是一个示例,显示如何将每个员工的工资与其部门的平均工资进行比较
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);
如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步澄清的内容,请使用此表单报告文档问题。