欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

postgres之窗口函数

程序员文章站 2024-03-21 11:57:52
...

本博客内容来自于官方文档:http://www.postgres.cn/docs/10/functions-window.html#FUNCTIONS-WINDOW-TABLE

一、窗口函数介绍

一个窗口函数在一系列与当前行有某种关联的表行上执行一种计算。这与一个聚集函数所完成的计算有可比之处。但是窗口函数并不会使多行被聚集成一个单独的输出行,这与通常的非窗口聚集函数不同。取而代之,行保留它们独立的标识。在这些现象背后,窗口函数可以访问的不仅仅是查询结果的当前行。

下面是一个例子用于展示如何将每一个员工的薪水与他/她所在部门的平均薪水进行比较: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;

epname | 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子句产生并通过WHEREGROUP BYHAVING过滤的“虚拟表”。例如,一个由于不满足WHERE条件被删除的行是不会被任何窗口函数所见的。在一个查询中可以包含多个窗口函数,每个窗口函数都可以用不同的OVER子句来按不同方式划分数据,但是它们都作用在由虚拟表定义的同一个行集上。

我们已经看到如果行的顺序不重要时ORDER BY可以忽略。PARTITION BY同样也可以被忽略,在这种情况下会产生一个包含所有行的分区。

这里有一个与窗口函数相关的重要概念:对于每一行,在它的分区中的行集被称为它的窗口帧。 一些窗口函数只作用在窗口帧中的行上,而不是整个分区。默认情况下,如果使用ORDER BY,则帧包括从分区开始到当前行的所有行,以及后续任何与当前行在ORDER BY子句上相等的行。如果ORDER BY被忽略,则默认帧包含整个分区中所有的行。 [4] 下面是使用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 BYHAVINGWHERE子句中。这是因为窗口函数的执行逻辑是在处理完这些子句之后。另外,窗口函数在非窗口聚集函数之后执行。这意味着可以在窗口函数的参数中包括一个聚集函数,但反过来不行。

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

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);

二、窗口函数调用

一个窗口函数调用表示在一个查询选择的行的某个部分上应用一个聚合类的函数。和非窗口聚合调用不同,这不会被约束为将被选择的行分组为一个单一的输出行 — 在查询输出中每一个行仍保持独立。 但是,根据窗口函数调用的分组规范(PARTITION BY列表), 窗口函数可以访问将成为当前行组的一部分的所有行。 一个窗口函数调用的语法是下列之一:

function_name ([expression [, expression ... ]]) [ FILTER ( WHERE filter_clause ) ] OVER window_name
function_name ([expression [, expression ... ]]) [ FILTER ( WHERE filter_clause ) ] OVER ( window_definition )
function_name ( * ) [ FILTER ( WHERE filter_clause ) ] OVER window_name
function_name ( * ) [ FILTER ( WHERE filter_clause ) ] OVER ( window_definition )

其中window_definition的语法是

[ existing_window_name ]
[ PARTITION BY expression [, ...] ]
[ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ frame_clause ]

而可选的frame_clause是下列之一

{ RANGE | ROWS } frame_start
{ RANGE | ROWS } BETWEEN frame_start AND frame_end

其中frame_startframe_end可以是下面形式中的一种

UNBOUNDED PRECEDING
value PRECEDING
CURRENT ROW
value FOLLOWING
UNBOUNDED FOLLOWING

 

这里,expression表示任何自身不含有窗口函数调用的值表达式。

window_name是对定义在查询的WINDOW子句中的一个命名窗口声明的引用。还可以使用在WINDOW子句中定义命名窗口的相同语法在圆括号内给定一个完整的window_definition,详见SELECT参考页。值得指出的是,OVER wname并不严格地等价于OVER (wname ...),后者表示复制并修改窗口定义,并且在被引用窗口声明包括一个帧子句时会被拒绝。

PARTITION BY子句将查询的行分组成为分区,窗口函数会独立地处理它们。PARTITION BY工作起来类似于一个查询级别的GROUP BY子句,不过它的表达式总是只是表达式并且不能是输出列的名称或编号。如果没有PARTITION BY,该查询产生的所有行被当作一个单一分区来处理。ORDER BY子句决定被窗口函数处理的一个分区中的行的顺序。它工作起来类似于一个查询级别的ORDER BY子句,但是同样不能使用输出列的名称或编号。如果没有ORDER BY,行将被以未指定的顺序被处理。

frame_clause指定构成窗口帧的行集合,它是当前分区的一个子集,窗口函数将作用在该帧而不是整个分区。 帧可以被指定为RANGEROWS模式,在两种情况中它都从frame_start运行到frame_end。如果frame_end被忽略,它默认运行到CURRENT ROW

UNBOUNDED PRECEDING的一个frame_start表示该帧开始于分区的第一行,类似地UNBOUNDED FOLLOWING的一个frame_end表示该帧结束于分区的最后一行。

RANGE模式下, CURRENT ROW的一个frame_start表示该帧开始于当前行的第一个平级行(一个被ORDER BY认为与当前行等效的行),而CURRENT ROW的一个frame_end表示该帧结束于最后一个等效的ORDER BY平级行。在ROWS模式下,CURRENT ROW仅表示当前行。

value PRECEDINGvalue FOLLOWING情况当前只在ROWS模式中被允许。它们指示帧开始或结束于当前行之前或之后的指定数量的行。value必须是一个不包含任何变量、聚合函数或窗口函数的整数表达式。该值不能为空或负,但是可以为零,零表示只选择当前行。

默认的帧选项是RANGE UNBOUNDED PRECEDING,它和RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW相同。如果使用ORDER BY,这会把该帧设置为从分区开始一直到当前行的最后一个ORDER BY平级行的所有行。如果不使用ORDER BY,分区中所有的行都被包括在窗口帧中,因为所有行都成为了当前行的平级行。

限制是frame_start不能为UNBOUNDED FOLLOWINGframe_end不能为UNBOUNDED PRECEDING并且在上述列表中frame_end的选择不能早于frame_start的选择出现 — 例如RANGE BETWEEN CURRENT ROW AND valuePRECEDING是不被允许的。

如果指定了FILTER,那么只有对filter_clause计算为真的输入行会被交给该窗口函数,其他行会被丢弃。只有是聚合的窗口函数才接受FILTER 。

内建的窗口函数在表 9.57中描述。 用户可以增加其他的窗口函数。还有,任何内建或用户定义的通用或统计聚合 函数可以被用作窗口函数。(有序聚合和假设集聚合目前不能用作窗口函数。)

使用*的语法被用来把参数较少的聚合函数当作窗口函数调用, 例如count(*) OVER (PARTITION BY x ORDER BY y)。 星号(*)通常通常不用于窗口特定的函数。窗口特定的函数不允许在函数参数列表中使用DISTINCTORDER BY

只有在SELECT列表和查询的ORDER BY子句中才允许窗口函数调用。

三、窗口函数处理

如果查询包含任何窗口函数(见第 3.5 节第 9.21 节第 4.2.8 节),这些函数将在任何分组、聚集和HAVING过滤被执行之后被计算。也就是说如果查询使用了任何聚集、GROUP BYHAVING,则窗口函数看到的行是分组行而不是来自于FROM/WHERE的原始表行。

当多个窗口函数被使用,所有在窗口定义中有句法上等效的PARTITION BYORDER BY子句的窗口函数被保证在数据上的同一趟扫描中计算。因此它们将会看到相同的排序顺序,即使ORDER BY没有唯一地决定一个顺序。但是,对于具有不同PARTITION BYORDER BY定义的函数的计算没有这种保证(在这种情况中,在多个窗口函数计算之间通常要求一个排序步骤,并且并不保证保留行的顺序,即使它的ORDER BY把这些行视为等效的)。

目前,窗口函数总是要求排序好的数据,并且这样查询的输出总是被根据窗口函数的PARTITION BY/ORDER BY子句的一个或者另一个排序。但是,我们不推荐依赖于此。如果你希望确保结果以特定的方式排序,请显式使用顶层的ORDER BY子句。

四、通用窗口函数

函数 返回类型 描述
row_number() bigint 当前行在其分区中的行号,从1计
rank() bigint 带间隙的当前行排名; 与该行的第一个同等行的row_number相同
dense_rank() bigint 不带间隙的当前行排名; 这个函数计数同等组
percent_rank() double precision 当前行的相对排名: (rank- 1) / (总分区行数 - 1)
cume_dist() double precision 累积分配: (当前行前面的分区行数 或 与当前行同等的行的分区行数)/(总分区行数)
ntile(num_buckets integer) integer 从1到参数值的整数范围,尽可能等分分区
lag(value anyelement [, offsetinteger [, default anyelement ]]) value的类型相同 返回value, 它在分区内当前行的之前offset个位置的行上计算;如果没有这样的行,返回default替代。 (作为value必须是相同类型)。offsetdefault都是根据当前行计算的结果。如果忽略它们,则offset默认是1,default默认是空值
lead(value anyelement [, offsetinteger [, default anyelement ]]) value类型相同 返回value,它在分区内当前行的之后offset个位置的行上计算;如果没有这样的行,返回default替代。(作为value必须是相同类型)。offsetdefault都是根据当前行计算的结果。如果忽略它们,则offset默认是1,default默认是空值
first_value(value any) same type as value 返回在窗口帧中第一行上计算的value
last_value(value any) value类型相同 返回在窗口帧中最后一行上计算的value
nth_value(value anynth integer) value类型相同 返回在窗口帧中第nth行(行从1计数)上计算的value;没有这样的行则返回空值

表 9.57中列出的所有函数都依赖于相关窗口定义的ORDER BY子句指定的排序顺序。当仅考虑ORDER BY列时,不能区分的行被称为是同等行。定义的这四个排名函数(包括cume_dist) ,对于所有同等行的答案相同。

注意first_valuelast_valuenth_value只考虑“窗口帧”内的行,它默认情况下包含从分区的开始行直到当前行的最后一个同等行。这对last_value可能不会给出有用的结果,有时对nth_value也一样。你可以通过向OVER子句增加一个合适的帧声明(RANGEROWS)来重定义帧。关于帧声明的更多信息请参考第 4.2.8 节

当一个聚集函数被用作窗口函数时,它将在当前行的窗口帧内的行上聚集。 一个使用ORDER BY和默认窗口帧定义的聚集产生一种“运行时求和”类型的行为,这可能是或者不是想要的结果。为了获取在整个分区上的聚集,忽略ORDER BY或者使用ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。 其它窗口帧声明可以用来获得其它的效果。

 

 

 

相关标签: postgres