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