带有窗口函数的查询
窗口表达式让应用开发者能够更轻松地使用标准 SQL 语句编写复杂的在线分析处理(OLAP)查询。例如,使用窗口表达式可以计算滑动平均值或区间总和,在特定列值发生变化时重置聚合和排名,以及用更简洁的方式表达复杂的比值计算逻辑。
窗口表达式表示将一个 <窗口函数>
应用于一个 <窗口帧>
,窗口帧通过 OVER()
子句定义。这 类似于配合 GROUP BY
子句使用聚合函数进行的计算。不过与聚合函数为每个分组返回一个结果值不同,窗口函数会为每一行返回一个结果值,这个结果是基于该行所属的窗口帧中所有行计算得出的。OVER()
子句可以将所有行划分为多个 分区,还可以进一步限制窗口帧的范围,指定当前行之前或之后哪些行应包含在计算中。
Apache Cloudberry 不支持将一个窗口函数作为另一个窗口函数的参数使用。
窗口表达式的语法如下:
<window_function> ( [<expression> [, ...]] ) [ FILTER ( WHERE <filter_clause> ) ] OVER ( <window_specification> )
其中 <window_function>
可以是用户定义的窗口函数,<expression>
是任意不包含窗口表达式的值表达式,<window_specification>
的结构如下:
[<window_name>]
[PARTITION BY <expression> [, ...]]
[[ORDER BY <expression> [ASC | DESC | USING operator] [NULLS {FIRST | LAST}] [, ...]
[ <frame_clause> ]
可选的 <frame_clause>
有以下两种形式:
{ RANGE | ROWS | GROUPS } <frame_start> [ <frame_exclusion> ]
{ RANGE | ROWS | GROUPS } BETWEEN <frame_start> AND <frame_end> [ <frame_exclusion> ]
其中 <frame_start>
和 <frame_end>
可以是以下选项之一:
UNBOUNDED PRECEDING
<offset> PRECEDING
CURRENT ROW
<offset> FOLLOWING
UNBOUNDED FOLLOWING
而 <frame_exclusion>
可以是以下选项之一:
EXCLUDE CURRENT ROW
EXCLUDE GROUP
EXCLUDE TIES
EXCLUDE NO OTHERS
窗口表达式只能出现在 SELECT
语句的查询列中。例如:
SELECT count(*) OVER(PARTITION BY customer_id), * FROM sales;
如果指定了 FILTER
,那么只有满足 <filter_clause>
条件为 true 的输入行才会传递给窗口函数,其他行会被忽略。在窗口表达式中,FILTER
子句只能与聚合函数类型的 <window_function>
搭配使用。
窗口表达式中必须包含 OVER
子句。OVER
子句指定了窗口帧——即窗口函数要处理的行集合。这个语法结构将窗口函数与普通函数或聚合函数区分开来。
当窗口表达式中的窗口聚合函数有多个输入表达式时,Apache Cloudberry 不支持使用 DISTINCT
子句。
一个窗口定义具有以下特征:
-
PARTITION BY
子句定义了窗口函数作用的分区。如果没有指定该子句,则整个结果集被视为一个分区。 -
ORDER BY
子句定义了在每个窗口分区内的排序方式。窗口定义中的ORDER BY
子句与普通查询中的ORDER BY
是两个独立的概念。对于计算排名的窗口函数,ORDER BY
是必需的,它指定了排名值的计算依据。对于 OLAP 聚合,要使用窗口帧(如ROWS
、RANGE
或GROUPS
),也必须指定ORDER BY
。
对于没有明确顺序的数据类型,例如 time
,不建议在窗口定义的 ORDER BY
中使用。这类类型即使指定了时区,也缺乏明确的顺序性,因为加法和减法运算不会产生预期的效果。例如,下面这种写法通常不成立:x::time < x::time + '2 hour'::interval
-
<frame_clause>
用于指定<window frame>
中的行集合,它是当前分区的一个子集,仅适用于那些基于窗口帧而非整个分区执行的窗口函数。帧中的行集合可能会随当前行而变化。帧可以用RANGE
、ROWS
或GROUPS
模式定义,在每种模式下,帧的范围从<frame_start>
到<frame_end>
。如果省略了<frame_end>
,则默认结束位置是CURRENT ROW
。 -
如果
<frame_start>
是UNBOUNDED PRECEDING
,表示帧从分区的第一行开始;如果<frame_end>
是UNBOUNDED FOLLOWING
,表示帧直到分区的最后一行结束 。 -
在
RANGE
或GROUPS
模式中,CURRENT ROW
作为<frame_start>
表示帧从当前行的首个“同行”(根据ORDER BY
排序等价的行)开始;作为<frame_end>
则表示帧结束于当前行的最后一个“同行”。在ROWS
模式中,CURRENT ROW
仅表示当前这一行。 -
使用
<offset> PRECEDING
或<offset> FOLLOWING
作为帧边界时,<offset>
必须是一个不包含变量、聚合函数或窗口函数的表达式。其含义因帧模式而异:-
在
ROWS
模式中,<offset>
必须是非空且非负的整数,表示帧从当前行的前或后指定行数开始或结束。 -
在
GROUPS
模式中,<offset>
同样必须是非空且非负的整数,表示帧从当前行所在“同行组”之前或之后的若干个同行组开始或结束(同行组是指在ORDER BY
中排序相同的一组行)。要使用GROUPS
模式,窗口定义中必须有ORDER BY
。 -
在
RANGE
模式中,这些选项要求ORDER BY
子句只能指定一列。<offset>
表示当前行的该列值与帧中其他行的该列值之间的最大差值。<offset>
的数据类型取决于排序列的数据类型。对于数值类型,通常与排序列一致;对于日期时间类型,则是interval
类型。例如,如果排序列是date
或timestamp
类型,可以写成:RANGE BETWEEN '1 day' PRECEDING AND '10 days' FOLLOWING
。<offset>
仍然必须是非空且非负的,只不过“非负”的含义取决于数据类型。
无论哪种情况,帧的结束位置不 会超出分区的末尾,因此靠近分区边界的行,其帧可能包含的行会比其他位置少。
-
-
注意,在
ROWS
和GROUPS
模式中,0 PRECEDING
和0 FOLLOWING
与CURRENT ROW
等效。在RANGE
模式中也通常成立,但其“零”的含义取决于具体数据类型。 -
<frame_exclusion>
选项允许排除当前行及其周围的部分行,即使这些行原本应包含在帧中。EXCLUDE CURRENT ROW
排除当前行;EXCLUDE GROUP
排除当前行及其所有“同行”;EXCLUDE TIES
排除所有同行,但保留当前行;EXCLUDE NO OTHERS
明确表示不排除任何行,是默认行为。 -
默认的帧选项是
RANGE UNBOUNDED PRECEDING
,等价于RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
。如果指定了ORDER BY
,这个设置表示帧包括从分区起始行到当前行最后一个“同行”为止的所有行。如果未指定ORDER BY
,则分区内的所有行都被视为当前行的同行,整个分区都会包含在窗口帧中。 -
有以下限制:
<frame_start>
不能是UNBOUNDED FOLLOWING
,<frame_end>
不能是UNBOUNDED PRECEDING
,并且<frame_end>
在上文所列的顺序中不能排在<frame_start>
之前。例如:RANGE BETWEEN CURRENT ROW AND value PRECEDING
是不允许的。
窗口函数示例
以下示例演示了如何在分区和窗口帧中使用窗口函数。