窗口函数是一种SQL函数,其中输入值是从SELECT语句的结果集中的一个或多个行的“窗口”中获取的。
通过OVER子句,窗口函数与其他SQL函数有所区别。如果一个函数具有OVER子句,则它是一个窗口函数。如果它缺少OVER子句,则它是一个普通的聚合或标量函数。窗口函数在函数和OVER子句之间也可能有FILTER子句。
窗口函数的语法如下:
与普通函数不同,窗口函数不能使用DISTINCT关键字。而且,Window函数只能出现在结果集中和SELECT语句的ORDER BY子句中。
窗口函数有两种: 聚合窗口函数和 内置窗口函数。每个聚合窗口函数也可以像普通的聚合函数一样工作,只需省略OVER和FILTER子句即可。此外,通过添加适当的OVER子句,SQLite的所有内置 聚合函数都可以用作聚合窗口函数。应用程序可以使用sqlite3_create_window_function()接口注册新的聚合窗口函数。但是,内置的窗口函数需要在查询计划程序中进行特殊处理,因此应用程序无法添加新的窗口函数,这些新的窗口函数显示出内置窗口函数中发现的特殊属性。
这是使用内置的row_number()窗口函数的示例:
创建表t0(x整数主键,y文本); 插入t0值(1,'aaa'),(2,'ccc'),(3,'bbb'); -下面的SELECT语句返回: - - X | y | row_number ----------------------- -1 | aaa | 1 - 2 | ccc | 3 - 3 | bbb | 2 - SELECT x,y,row_number()OVER(ORDER BY y)AS row_number FROM t0 ORDER BY x;
row_number()窗口函数按窗口定义中“ ORDER BY”子句的顺序(在本例中为“ ORDER BY y”)为每行分配连续的整数 。请注意,这不会影响从整体查询返回结果的顺序。最终输出的顺序仍然由附加到SELECT语句的ORDER BY子句控制(在本例中为“ ORDER BY x”)。
也可以使用WINDOW子句将命名的window-defn子句添加到SELECT语句中,然后在窗口函数调用中通过名称进行引用。例如,以下SELECT语句包含两个命名的window-defs子句“ win1”和“ win2”:
选择x,y,row_number()超过win1,rank()超过win2 从t0开始 窗口win1 AS(在无界前导和当前行之间按y顺序排列), win2 AS(按y顺序按x排列) 按x订购;
WINDOW子句(如果存在)位于任何HAVING子句之后和任何ORDER BY之前。
本节中的所有示例均假定数据库的填充如下:
创建表t1(a整数主键,b,c); 插入t1值(1,'A','one'), (2,'B','two'), (3,'C','三个'), (4,'D','one'), (5,'E','two'), (6,'F','三个'), (7,'G','one');
聚合窗口函数类似于 普通的聚合函数,不同之处在于将其添加到查询中不会更改返回的行数。相反,对于每一行,聚合窗口函数的结果就好像相应的聚合在OVER子句指定的“窗口框架”中的所有行上运行一样。
-下面的SELECT语句返回: - - A | b | group_concat ------------------------- -1 | A | AB -2 | B | ABC -3 | C | BCD -4 | D | CDE -5 | E | 防御 -6 | F | EFG -7 | G | FG - 选择a,b,group_concat(b,'。')OVER( 按行顺序在1个以下和1个以下之间进行排序 )AS group_concat FROM t1;
在上面的示例中,窗口框架由上一行(“ 1 PRECEDING”)和下一行(“ 1 FOLLOWING”)之间的所有行(包括首尾)组成,其中各行根据window-defn中的ORDER BY子句进行 排序(在这种情况下为“ ORDER BY a”)。例如,(a = 3)的行的帧由(2,'B','two'),(3,'C','three')和(4,'D','one ')。因此,该行的group_concat(b,'。')的结果为'BCD'。
所有SQLite的聚合函数都可以用作聚合窗口函数。也可以 创建用户定义的聚合窗口函数。
为了计算窗口函数,将查询的结果集划分为一个或多个“分区”。分区由window-defn中的PARTITION BY子句的所有项具有相同值的所有行组成。如果没有PARTITION BY子句,则查询的整个结果集是单个分区。对每个分区分别执行窗口功能处理。
例如:
-下面的SELECT语句返回: - - Ç| 一个| b | group_concat --------------------------------- -一| 1 | A | ADG- 一| 4 | D | DG- 一| 7 | G | G- 三| 3 | C | CF- 三| 6 | F | F- 两个| 2 | B | BE- 两个| 5 | E | Ë - 选择c,a,b,group_concat(b,'。')OVER( 在当前行和无界以下之间按顺序进行分区 )AS group_concat 从t1到c,a;
在上面的查询中,“ PARTITION BY c”子句将结果集分为三个分区。第一个分区具有三行,其中c =='one'。第二个分区有两行,其中c =='three',第三个分区有两行,其中c =='two'。
在上面的示例中,每个分区的所有行在最终输出中分组在一起。这是因为PARTITION BY子句是整个查询上ORDER BY子句的前缀。但这不是必须的。分区可以由在结果集中随意散布的行组成。例如:
-下面的SELECT语句返回: - - Ç| 一个| b | group_concat --------------------------------- -一| 1 | A | ADG- 两| 2 | B | BE- 三| 3 | C | CF- 一| 4 | D | DG- 两| 5 | E | E- 三| 6 | F | F- 一| 7 | G | 摹 - 选择c,a,b,group_concat(b,'。')OVER( 在当前行和无界以下之间按顺序进行分区 )AS group_concat 从t1订购a;
所述帧规格确定哪个输出行由聚合窗函数读取。所述 帧规格由四个部分组成:
以下是语法详细信息:
可以省略结束帧边界(如果也省略围绕开始帧边界的BETWEEN和AND关键字),在这种情况下,结束帧边界默认为CURRENT ROW。
如果帧类型是RANGE或GROUPS,则所有ORDER BY表达式具有相同值的行将被视为“对等”。或者,如果没有ORDER BY术语,则所有行都是对等体。对等点始终在同一帧内。
缺省的框架规格为:
无限制的前移和当前行之间的范围不排除其他
默认值表示聚合窗口函数从分区的开始直到当前行及其对等行都读取所有行。这意味着对于所有ORDER BY表达式具有相同值的行,其窗口函数的结果也将具有相同的值(因为窗口框架相同)。例如:
-下面的SELECT语句返回: - - A | b | c | group_concat ----------------------------- -1 | A | 一| ADG -2 | B | 二| ADGCFBE -3 | C | 三| ADGCF -4 | D | 一| ADG -5 | E | 二| ADGCFBE -6 | F | 三| ADGCF -7 | G | 一| ADG - 选择a,b,c, group_concat(b,'。')OVER(ORDER BY c)AS group_concat 从t1订购a;
共有三种帧类型:ROWS,GROUPS和RANGE。帧类型决定如何测量帧的开始和结束边界。
GROUPS:GROUPS帧类型表示通过对相对于当前组的“组”进行计数来确定开始和结束边界。“组”是一组行,它们对窗口ORDER BY子句的所有所有术语都具有相等的值。(“等效”表示比较两个值时,IS运算符为true。)换句话说,组由一行的所有对等组成。
RANGE:RANGE帧类型要求窗口的ORDER BY子句仅具有一个术语。将该术语称为“ X”。对于RANGE帧类型,通过计算分区中所有行的表达式X的值,并对那些X值在当前行的X值的特定范围内的行进行取景,来确定帧的元素。 。有关详细信息,请参见下面的“ <expr> PRECEDING ”边界规范中的描述。
ROWS和GROUPS帧类型的相似之处在于,它们都通过相对于当前行进行计数来确定帧的范围。区别在于,ROWS计算单个行,而GROUPS计算对等组。RANGE帧类型不同。RANGE帧类型通过查找相对于当前行的某个值带内的表达式值来确定帧的范围。
有五种描述开始和结束帧边界的方法:
无
边界的前缀框架边界是分区中的第一行。
<expr> PRECEDING
<expr>必须是非负常数数值表达式。边界是当前行之前<expr>“单位”的行。这里“单位”的含义取决于帧类型:
ROWS→ 帧边界是当前行之前<expr>行的行,如果当前行之前少于<expr>行,则为分区的第一行。<expr>必须为整数。
GROUPS→ “组”是一组对等行-对ORDER BY子句中的每个术语都具有相同值的行。帧边界是包含当前行的组之前的<expr>组的组,如果当前行之前的<expr>组少于该组,则为分区的第一组。对于帧的开始边界,使用组的第一行,对于帧的结束边界,使用组的最后行。<expr>必须为整数。
RANGE→ 对于这种形式,window-defn的ORDER BY子句 必须有一个术语。将该ORDER BY术语称为“ X”。令X i为分区中第i行的X表达式的值,令X c为当前行中X的值。非正式地,RANGE边界是X i在X c的<expr>内的第一行。更确切地说:
CURRENT ROW
当前行。对于RANGE和GROUPS帧类型,除非EXCLUDE子句明确排除,否则当前行的对等项也包括在该帧中。无论是否将CURRENT ROW用作开始或结束帧边界,这都是正确的。
<expr>跟进
这与“ <expr> PRECEDING”相同,除了边界是当前行之后而不是当前行之前的<expr>个单位。
无
边界跟随框架边界是分区中的最后一行。
结束帧边界可能不采用上面列表中看起来比开始帧边界更高的形式。
在下面的示例中,每一行的窗口框架都包括从当前行到集合末尾的所有行,其中行根据“ ORDER BY a”排序。
-下面的SELECT语句返回: - - Ç| 一个| b | group_concat --------------------------------- -一| 1 | A | ADGCFBE- 一| 4 | D | DGCFBE- 一| 7 | G | GCFBE- 三| 3 | C | CFBE- 三| 6 | F | FBE- 两| 2 | B | BE- 两个| 5 | E | Ë - 选择c,a,b,group_concat(b,'。')OVER( 按c排序,当前行和无界以下行之间的行 )AS group_concat 从t1到c,a;
可选的EXCLUDE子句可以采用以下四种形式中的任何一种:
不排除其他:这是默认设置。在这种情况下,没有任何窗口从窗口框架的开始和结束边界定义的行中排除。
EXCLUDE CURRENT ROW:在这种情况下,当前行将从窗口框架中排除。当前行的对等项保留在GROUPS和RANGE帧类型的帧中。
EXCLUDE GROUP:在这种情况下,当前行和作为当前行的对等方的所有其他行将从框架中排除。在处理EXCLUDE子句时,即使帧类型为ROWS,具有ORDER BY值相同的所有行或分区中的所有行(如果没有ORDER BY子句)也被视为对等。
排除的关系:在这种情况下,当前行是框架的一部分,但当前行的对等点被排除在外。
下面的示例演示EXCLUDE子句各种形式的效果:
-下面的SELECT语句返回: - - Ç| 一个| b | no_others | current_row | grp | 领带 -一| 1 | A | ADG | DG | | A- 一个| 4 | D | ADG | AG | | D- 一| 7 | G | ADG | 广告| | G- 三| 3 | C | ADGCF | ADGF | ADG | ADGC- 三| 6 | F | ADGCF | ADGC | ADG | ADGF- 两个| 2 | B | ADGCFBE | ADGCFE | ADGCF | ADGCFB- 两个| 5 | E | ADGCFBE | ADGCFB | ADGCF | ADGCFE - 选择c,a,b, group_concat(b,'。')OVER( 在不受限制的前缀和当前行之间按c组排序 )AS no_others, group_concat(b,'。')OVER( 未绑定的前导和当前行之间按c组排序的行排除当前行 )作为current_row, group_concat(b,'。')OVER( 在未绑定的前缀和当前行排除组之间按c组排序 )AS grp, group_concat(b,'。')OVER( 在未绑定的前缀和当前行排除关系之间按c组排序 )AS关系 从t1到c,a;
如果提供了FILTER子句,则窗口框架中仅包含expr为true的行。聚合窗口仍然为每行返回一个值,但是FILTER表达式得出的结果不是true的值不包含在任何行的窗口框架中。例如:
-下面的SELECT语句返回: - - Ç| 一个| b | group_concat --------------------------------- -一| 1 | A | A- 两个| 2 | B | A- 三| 3 | C | AC- 一| 4 | D | ACD- 两个| 5 | E | ACD- 三| 6 | F | ACDF- 一| 7 | G | ACDFG - SELECT c,a,b,group_concat(b,'。')FILTER(WHERE c!='two')OVER( 订购 )AS group_concat 从t1订购a;
除了聚合窗口函数外,SQLite还具有一组基于 PostgreSQL支持的内置窗口函数 。
内置窗口函数采用与聚合窗口函数相同的方式支持任何PARTITION BY子句-每个选定的行都分配给一个分区,并且每个分区都被单独处理。下面介绍任何ORDER BY子句影响每个内置窗口函数的方式。某些窗口函数(rank(),densage_rank(),percent_rank()和ntile())使用“对等组”的概念(同一分区内的行对所有ORDER BY表达式具有相同的值)。在这些情况下,帧规范是否指定ROWS,GROUPS或RANGE都没有关系。出于内置窗口函数处理的目的,所有ORDER BY表达式具有相同值的行被视为对等体,而与帧类型无关。
大多数内置窗口函数会忽略 帧规范,但first_value(),last_value()和nth_value()除外。在内置窗口函数调用中指定FILTER子句是语法错误。
SQLite支持以下11种内置窗口函数:
row_number()
当前分区内的行号。行以窗口定义中ORDER BY子句定义的顺序从1开始编号,否则以任意顺序编号。
秩()
每个组中第一个对等点的row_number()-当前行的间隔。如果没有ORDER BY子句,则所有行均被视为对等,并且此函数始终返回1。
density_rank()
当前行在其分区内的对等组的编号-当前行的级别,不带空格。分区按窗口定义中ORDER BY子句定义的顺序从1开始编号。如果没有ORDER BY子句,则所有行均被视为对等,并且此函数始终返回1。
percent_rank()
尽管有名称,此函数始终返回介于0.0和1.0之间的值,该值等于(rank -1)/(partition-rows -1),其中 rank是内置窗口函数rank()返回的值,而partition-rows是分区中的总行数。如果分区仅包含一行,则此函数返回0.0。
cume_dist()
累积分布。计算为 row-number / partition-rows,其中row-number是row_number()返回的组中最后一个对等方的值,partition-rows表示分区中的行数。
ntile(N)
参数N作为整数处理。此函数将分区尽可能均匀地划分为N个组,并按照ORDER BY子句定义的顺序或其他任意顺序为每个组分配1到N之间的整数。如有必要,首先会出现较大的群体。此函数返回分配给当前行所属组的整数值。
lag(expr)
lag(expr,offset)
滞后(expr,offset,default)
lag()函数的第一种形式返回针对分区中的前一行评估表达式expr的结果。或者,如果没有上一行(因为当前行是第一行),则为NULL。
如果提供了offset参数,则它必须是非负整数。在这种情况下,返回的值是针对分区中当前行之前的行偏移行评估expr的结果。如果offset为0,则 针对当前行评估expr。如果当前行之前没有行 偏移行,则返回NULL。
如果还提供了default,则如果由offset标识的行不存在,则返回它而不是NULL 。
线索(expr)
线索(expr,偏移量)
线索(expr,偏移量,默认值)
lead()函数的第一种形式返回针对分区中的下一行评估表达式expr的结果。或者,如果没有下一行(因为当前行是最后一行),则为NULL。
如果提供了offset参数,则它必须是非负整数。在这种情况下,返回的值是针对分区中当前行之后的行偏移行评估expr的结果。如果offset为0,则 针对当前行评估expr。如果当前行之后没有行 偏移行,则返回NULL。
如果还提供了default,则如果由offset标识的行不存在,则返回它而不是NULL 。
first_value(expr)
此内置的窗口函数以与聚合窗口函数相同的方式计算每一行的窗口框架。它返回针对每一行在窗口框架中针对第一行评估的expr的值。
last_value(expr)
此内置的窗口函数以与聚合窗口函数相同的方式计算每一行的窗口框架。它返回针对每行在窗口框架中的最后一行评估的expr的值。
nth_value(expr,N)
此内置的窗口函数以与聚合窗口函数相同的方式计算每一行的窗口框架。它返回针对窗口框架的第N行评估的expr的值。在窗口框架中,行从1开始按ORDER BY子句定义的顺序编号(如果存在的话),否则以任意顺序编号。如果分区中没有第N行,则返回NULL。
本节中的所有示例均假设以下数据:
创建表t2(a,b); 插入t2值('a','one'), (“ a”,“ two”), (“ a”,“三个”), (“ b”,“四个”), (“ c”,“五个”), (“ c”,“六个”);
下面的示例说明了五个排名函数的行为-row_number(),rank(),densage_rank(),percent_rank()和cume_dist()。
-下面的SELECT语句返回: - - A | row_number | 等级| density_rank | percent_rank | cume_dist -------------------------------------------------- ---------------- -一个| 1 | 1 | 1 | 0.0 | 0.5 -a | 2 | 1 | 1 | 0.0 | 0.5 -a | 3 | 1 | 1 | 0.0 | 0.5 -b | 4 | 4 | 2 | 0.6 | 0.66 -c | 5 | 5 | 3 | 0.8 | 1.0 -c | 6 | 5 | 3 | 0.8 | 1.0 - 选择一个AS row_number()胜出AS row_number, rank()胜出AS排名, density_rank()胜出AS density_rank, percent_rank()胜出AS percent_rank, cume_dist()胜出cume_dist 从t2开始 窗口赢得AS(按a排序);
下面的示例使用ntile()将六行分为两组(调用ntile(2))和四组(调用ntile(4))。对于ntile(2),每组分配三行。对于ntile(4),有两组(每组两个)和两组(每组一个)。两个较大的组首先出现。
-下面的SELECT语句返回: - - A | b | ntile_2 | ntile_4 ---------------------------------- -一个| 一| 1 | 1 -a | 二| 1 | 1 -a | 三| 1 | 2 -b | 四| 2 | 2 -c | 五| 2 | 3 -c | 六| 2 | 4 - 选择一个AS b AS b, ntile(2)以ntile_2优势获胜, ntile(4)胜出ntile_4 从t2开始 窗口赢得AS(按a排序);
下一个示例演示了lag(),lead(),first_value(),last_value()和nth_value()。lag()和Lead()都忽略帧规范,但first_value(),last_value()和nth_value()会尊重该帧规范。
-下面的SELECT语句返回: - - B | 铅| 滞后 first_value | last_value | nth_value_3 -------------------------------------------------- ----------- -A | C | NULL | A | A | NULL -B | D | A | A | B | NULL -C | E | B | A | C | C – D | F | C | A | D | C -E | G | D | A | E | C -F | 不适用 E | A | F | C -G | 不适用 F | A | G | ç - 选择b AS b, 领先(b,2,'n / a')胜出AS领先, 滞后(b)胜出作为滞后, first_value(b)作为first_value获胜, last_value(b)作为last_value胜出, nth_value(b,3)胜出AS nth_value_3 从t1开始 窗口获胜(按无限制的先行和当前行之间的b行)
窗口链接是一种快捷方式,可以根据另一个窗口定义一个窗口。具体来说,速记允许新窗口隐式复制基础窗口的PARTITION BY和ORDER BY子句。例如,在以下内容中:
选择group_concat(b,'。')OVER( 赢得无限制的前奏和当前行之间的行 ) 从t1开始 窗口获胜(按c的顺序排列)
group_concat()函数使用的窗口等效于“在未绑定的前导和当前行之间按c的行排列”。为了使用窗口链接,必须满足以下所有条件:
新窗口定义不得包含PARTITION BY子句。基本窗口规范必须提供PARTITION BY子句(如果有的话)。
如果基础窗口具有ORDER BY子句,则将其复制到新窗口中。在这种情况下,新窗口不能指定ORDER BY子句。如果基本窗口没有ORDER BY子句,则可以将其指定为新窗口定义的一部分。
基本窗口可能未指定框架规范。框架规范只能在新窗口规范中给出。
下面的两个SQL片段是相似的,但不完全相同,因为如果窗口“ win”的定义包含框架规范,则后者将失败。
SELECT group_concat(b,'。')胜出... SELECT group_concat(b,'。')OVER(win)...
可以使用sqlite3_create_window_function()API创建用户定义的聚合窗口函数 。实现聚合窗口功能与普通聚合功能非常相似。任何用户定义的聚合窗口函数也可以用作普通聚合。要实现用户定义的聚合窗口函数,应用程序必须提供四个回调函数:
打回来 | 描述 |
---|---|
xStep | 窗口聚合和旧式聚合功能实现均需要此方法。调用它可以在当前窗口中添加一行。与要添加的行相对应的函数参数(如果有)将传递到xStep的实现。 |
最终版 | 窗口聚合和旧式聚合功能实现均需要此方法。调用它以返回聚合的当前值(由当前窗口的内容确定),并释放由先前对xStep的调用分配的任何资源。 |
值 | 此方法仅是必需的窗口聚合函数,而不是旧式聚合函数实现。调用它以返回聚合的当前值。与xFinal不同,该实现不应删除任何上下文。 |
逆 | 此方法仅是必需的窗口聚合函数,而不是旧式聚合函数实现。调用它可以从当前窗口中删除一行。函数参数(如果有)对应于要删除的行。 |
下面的C代码实现了一个简单的名为sumint()的窗口聚合函数。这与内置sum()函数的工作方式相同,不同之处在于,如果传递的参数不是整数值,则会引发异常。
/ * ** xStep用于sumint()。 ** **将参数的值添加到聚合上下文(整数)。 * / 静态void sumintStep( sqlite3_context * ctx, int nArg, sqlite3_value * apArg [] ){ sqlite3_int64 * pInt; assert(nArg == 1); if(sqlite3_value_type(apArg [0])!= SQLITE_INTEGER){ sqlite3_result_error(ctx,“无效参数”,-1); 返回; } pInt =(sqlite3_int64 *)sqlite3_aggregate_context(ctx,sizeof(sqlite3_int64)); if(pInt){ * pInt + = sqlite3_value_int64(apArg [0]); } } / * ** xInverse为sumint()。 ** **这与xStep()的作用相反- 从当前上下文值中 减去参数**的值。可以从**此函数中 省略错误检查,因为只有在xStep()之后才调用错误检查(因此已经分配 了**上下文),并且已经将** 的值传递给xStep(),而没有错误(因此它必须是整数)。* / 静态void sumintInverse( sqlite3_context * ctx, int nArg, sqlite3_value * apArg [] ){ sqlite3_int64 * pInt; 断言(sqlite3_value_type(apArg [0])== SQLITE_INTEGER); pInt =(sqlite3_int64 *)sqlite3_aggregate_context(ctx,sizeof(sqlite3_int64)); * pInt-= sqlite3_value_int64(apArg [0]); } / * ** xFinal用于sumint()。 ** **返回聚合窗口函数的当前值。由于 此实现不会分配超出 sqlite3_aggregate_context返回 的缓冲区之外的任何资源,缓冲区会被系统自动释放,因此没有资源可释放。因此,此方法 与xValue()相同。 * / static void sumintFinal(sqlite3_context * ctx){ sqlite3_int64 res = 0; sqlite3_int64 * pInt; pInt =(sqlite3_int64 *)sqlite3_aggregate_context(ctx,0); if(pInt)res = * pInt; sqlite3_result_int64(ctx,res); } / * ** sumint()的xValue。 ** **返回聚合窗口函数的当前值。 * / 静态void sumintValue(sqlite3_context * ctx){ sqlite3_int64 res = 0; sqlite3_int64 * pInt; pInt =(sqlite3_int64 *)sqlite3_aggregate_context(ctx,0); if(pInt)res = * pInt; sqlite3_result_int64(ctx,res); } / * **用数据库句柄db注册sumint()窗口聚合。 * / int register_sumint(sqlite3 * db){ 返回sqlite3_create_window_function(db,“ sumint”,1,SQLITE_UTF8,0, sumintStep,sumintFinal,sumintValue,sumintInverse,0 ); }
下面的示例使用上述C代码实现的sumint()函数。对于每一行,窗口由上一行(如果有),当前行和下一行(同样,如果有)组成:
创建表t3(x,y); 插入t3值('a',4), ('b',5), ('c',3), ('d',8), ('e',1); -假设数据库使用的是上面的脚本填充, -下面的SELECT语句返回: - - X | sum_y -------------- -一个| 9 -b | 12 -c | 16 -d | 12 -e | 9 - 选择x,sumint(y)超过( 在1个前置和1个后续之间按x行排序 )AS sum_y 从t3订购x;
在处理上面的查询时,SQLite如下调用sumint回调:
窗口功能支持最初是在SQLite 版本3.25.0(2018-09-15)中添加的。SQLite开发人员使用PostgreSQL窗口函数文档作为窗口函数应如何工作的主要参考。针对PostgreSQL运行了许多测试用例,以确保窗口功能在SQLite和PostgreSQL中以相同的方式运行。
在SQLite版本3.28.0(2019-04-16)中,对Windows函数的支持进行了扩展,以包括EXCLUDE子句,GROUPS框架类型,窗口链接以及对以下版本中的“ <expr> PRECEDING”和“ <expr> FOLLOWING”边界的支持。 RANGE帧。