MySQL 源码|38 - 语法解析:窗口函数
源码位置(版本 = MySQL 8.0.37):
前置文档:
在 “37 - 语法解析:聚集函数” 中,我们梳理了聚集函数的相关逻辑,其中所有聚集函数都带有 opt_windowing_clause
规则,支持可选的窗口函数子句。
在 simple_expr
中,有两个备选方案与窗口函数有关,分别是 set_function_specification
规则和 window_func_call
规则,这两个规则的结果可以直接成为 simple_expr
规则的结果。
涉及窗口函数的规则如下图所示:其中绿色节点为本章节梳理,蓝色节点为之前章节已梳理,红色节点为后续章节梳理。
opt_windowing_clause
规则
查看在 sum_expr
中使用的 opt_windowing_clause
规则。其中有两种备选方案,分别为 %empty
(空)和 windowing_clause
规则的结果。Bison 语法如下:
opt_windowing_clause:
%empty
{
$$= nullptr;
}
| windowing_clause
{
$$= $1;
}
;
set_function_specification
规则
查看 set_function_specification
规则的逻辑,其中有两种备选方案,分别是 sum_expr
规则和 grouping_operation
规则,sum_expr
规则即聚集函数,详见 MySQL 源码|37 - 语法解析:聚集函数,而 grouping_operation
规则为 GROUPING
子句。Bison 语法如下:
set_function_specification:
sum_expr
| grouping_operation
;
grouping_operation
规则
查看 set_function_specification
规则中用到的 grouping_operation
规则,其中只有 GROUPING(expr_list)
一个备选方案,用于返回 GROUPING()
函数超聚合结果。Bison 语法如下:
grouping_operation:
GROUPING_SYM '(' expr_list ')'
{
$$= NEW_PTN Item_func_grouping(@$, $3);
}
;
window_func_call
规则
在 window_func_call
规则中,包括所有不是聚合函数的窗口函数。对于窗口函数标准语法的描述,引用自 MySQL 官方手册:14.20.1 Window Function Descriptions。
ROW_NUMBER()
函数
标准语法:ROW_NUMBER() over_clause
用于计算当前记录在当前窗口分区中的顺序号,此序号从 1 开始;当排序字段相同时,会导致非确定的顺序,但不会出现相同序号。只有 ROW_NUMBER() over_clause
这一种备选语法。Bison 语法如下:
ROW_NUMBER_SYM '(' ')' windowing_clause
{
$$= NEW_PTN Item_row_number(@$, $4);
}
RANK()
函数
标准语法:RANK() over_clause
用于计算当前记录在当前窗口分区中的排名,此排名从 1 开始;当排序字段相同时,所有字段值相同的字段会使用相同的排名,后续记录会跳过这些记录中未出现的排名。例如两条记录并列第 1 名,那么下一条记录将是第 3 名。Bison 语法如下:
| RANK_SYM '(' ')' windowing_clause
{
$$= NEW_PTN Item_rank(@$, false, $4);
}
DENSE_RANK
函数
标准语法:DENSE_RANK() over_clause
用于计算当前记录在当前窗口分区中的排名,此排名从 1 开始;与 RANK()
函数不同的是,当出现相同排名时,后续记录不会跳过这些记录中未出现的排名。例如两条记录并列第 1 名,那么下一条记录将是第 2 名。Bison 语法如下:
| DENSE_RANK_SYM '(' ')' windowing_clause
{
$$= NEW_PTN Item_rank(@$, true, $4);
}
CUME_DIST()
函数
标准语法:CUME_DIST() over_clause
用于计算当前记录在当前窗口分区中的累积概率分布,即当前记录之前(含当前记录和与当前记录排序字段相同的记录)的记录数除以窗口分区中的总行数。Bison 语法如下:
| CUME_DIST_SYM '(' ')' windowing_clause
{
$$= NEW_PTN Item_cume_dist(@$, $4);
}
PERCENT_RANK()
函数
标准语法:PERCENT_RANK() over_clause
用于计算当前记录在当前窗口分区中的排名百分比,即除最大值外,当前记录之前(不含当前记录)的记录数除以窗口分区中的总记录数,计算公式为 ( r a n k − 1 ) / ( r o w s − 1 ) (rank - 1) / (rows - 1) (rank−1)/(rows−1)。Bison 语法如下:
| PERCENT_RANK_SYM '(' ')' windowing_clause
{
$$= NEW_PTN Item_percent_rank(@$, $4);
}
NTILE()
函数
标准语法:NTILE(N) over_clause
将当前窗口分区分为 N 组(桶),并返回当前记录在当前窗口分区中分到的桶编号,返回值范围为 [ 1 , N ] [1, N] [1,N]。N 必须为正整数。Bison 语法如下:
| NTILE_SYM '(' stable_integer ')' windowing_clause
{
$$=NEW_PTN Item_ntile(@$, $3, $5);
}
LEAD()
函数
标准语法:LEAD(expr [, N[, default]]) [null_treatment] over_clause
获取在当前窗口分区中,当前记录之后的第 n 条记录;如果这条记录不存在,则返回 default。N 必须是非负整数。N 的默认值为 1,default 的默认值为 NULL。Bison 语法如下:
| LEAD_SYM '(' expr opt_lead_lag_info ')' opt_null_treatment windowing_clause
{
PT_item_list *args= NEW_PTN PT_item_list(@expr, @opt_lead_lag_info);
if (args == nullptr || args->push_back($3))
MYSQL_YYABORT; // OOM
if ($4.offset != nullptr && args->push_back($4.offset))
MYSQL_YYABORT; // OOM
if ($4.default_value != nullptr && args->push_back($4.default_value))
MYSQL_YYABORT; // OOM
$$= NEW_PTN Item_lead_lag(@$, true, args, $6, $7);
}
LAG()
函数
标准语法:LAG(expr [, N[, default]]) [null_treatment] over_clause
获取在当前窗口分区中,当前记录之前的第 n 条记录;如果这条记录不存在,则返回 default。N 必须是非负整数。N 的默认值为 1,default 的默认值为 NULL。Bison 语法如下:
| LAG_SYM '(' expr opt_lead_lag_info ')' opt_null_treatment windowing_clause
{
PT_item_list *args= NEW_PTN PT_item_list(@expr, @opt_lead_lag_info);
if (args == nullptr || args->push_back($3))
MYSQL_YYABORT; // OOM
if ($4.offset != nullptr && args->push_back($4.offset))
MYSQL_YYABORT; // OOM
if ($4.default_value != nullptr && args->push_back($4.default_value))
MYSQL_YYABORT; // OOM
$$= NEW_PTN Item_lead_lag(@$, false, args, $6, $7);
}
FIRST_VALUE()
函数
标准语法:FIRST_VALUE(expr) [null_treatment] over_clause
用于获取当前分区窗口中的第 1 条记录。Bison 语法如下:
| FIRST_VALUE_SYM '(' expr ')' opt_null_treatment windowing_clause
{
$$= NEW_PTN Item_first_last_value(@$, true, $3, $5, $6);
}
LAST_VALUE()
函数
标准语法:LAST_VALUE(expr) [null_treatment] over_clause
用于获取当前分区窗口中的最后 1 条记录。Bison 语法如下:
| LAST_VALUE_SYM '(' expr ')' opt_null_treatment windowing_clause
{
$$= NEW_PTN Item_first_last_value(@$, false, $3, $5, $6);
}
NTH_VALUE()
函数
标准语法:NTH_VALUE(expr, N) [from_first_last] [null_treatment] over_clause
用于获取当前分区窗口中的第 N 条记录。Bison 语法如下:
| NTH_VALUE_SYM '(' expr ',' simple_expr ')' opt_from_first_last opt_null_treatment windowing_clause
{
PT_item_list *args= NEW_PTN PT_item_list(@expr, @simple_expr);
if (args == nullptr ||
args->push_back($3) ||
args->push_back($5))
MYSQL_YYABORT;
$$= NEW_PTN Item_nth_value(@$, args, $7 == NFL_FROM_LAST, $8, $9);
}
;
在 window_func_call
规则中,引用了 windowing_clause
规则、stable_integer
规则、opt_lead_lag_info
规则、expr
规则、opt_lead_lag_info
规则、simple_expr
规则和 opt_from_first_last
规则。其中 expr
规则为一般表达式,simple_expr
规则为基础表达式,我们在梳理 expr
规则时一起处理。下面我们具体梳理其他涉及的规则。
stable_integer
规则
stable_integer
规则的结果实际上并不是常量,而不是在执行期间保持常量状态。stable_integer
规则有 int64_literal
规则和 param_or_var
规则两种备选方案。Bison 语法如下:
/*
The stable_integer nonterminal symbol is not really constant, but constant
for the duration of an execution.
*/
stable_integer:
int64_literal { $$ = $1; }
| param_or_var
;
int64_literal
规则
int64_literal
规则有 NUM
、LONG_NUM
、ULONGLONG_NUM
三种备选方案,分别对应 int 类型数值、long 类型数值和 u long long 类型数值。Bison 语法如下:
/*
int64_literal if for unsigned exact integer literals in a range of
[0 .. 2^64-1].
*/
int64_literal:
NUM { $$ = NEW_PTN Item_int(@$, $1); }
| LONG_NUM { $$ = NEW_PTN Item_int(@$, $1); }
| ULONGLONG_NUM { $$ = NEW_PTN Item_uint(@$, $1.str, $1.length); }
;
param_or_var
规则
param_or_var
规则有 param_marker
规则、ident
规则以及 @ {规则:ident_or_text}
三种备选方案。其中 ident
和 ident_or_text
规则属于语法元素的一部分,待梳理语法元素时梳理。Bison 语法如下:
param_or_var:
param_marker { $$ = $1; }
| ident { $$ = NEW_PTN PTI_int_splocal(@$, to_lex_cstring($1)); }
| '@' ident_or_text { $$ = NEW_PTN PTI_user_variable(@$, $2); }
;
param_marker
规则
param_marker
规则只有 ?
开头的参数值一种备选方案(PARAM_MARKER
)。Bison 语法如下:
param_marker:
PARAM_MARKER
{
auto *i= NEW_PTN Item_param(@$, YYMEM_ROOT,
(uint) (@1.raw.start - YYLIP->get_buf()));
if (i == nullptr)
MYSQL_YYABORT;
auto *lex= Lex;
/*
If we are not re-parsing a CTE definition, this is a
real parameter, so add it to param_list.
*/
if (!lex->reparse_common_table_expr_at &&
lex->param_list.push_back(i))
MYSQL_YYABORT;
$$= i;
}
;
opt_lead_lag_info
规则
标准语法:[, N[, default]]
在 LEAD()
函数和 LAG()
函数中用到了 opt_lead_lag_info
规则。
opt_lead_lag_info
规则有空(%empty
)和 , {stable_integer} {opt_ll_default}
两种备选方案。Bison 语法如下:
opt_lead_lag_info:
%empty
{
$$.offset= nullptr;
$$.default_value= nullptr;
}
| ',' stable_integer opt_ll_default
{
$$.offset= $2;
$$.default_value= $3;
}
;
opt_ll_default
规则
标准语法:[, default]
opt_ll_default
规则有空(%empty
)和 , expr
两种备选方案。Bison 语法如下:
opt_ll_default:
%empty
{
$$= nullptr;
}
| ',' expr
{
$$= $2;
}
;
opt_from_first_last
规则
标准语法:[from_first_last]
在 NTH_VALUE()
窗口函数中用到了 from_first_last
规则。from_first_last
规则有如下 3 种备选方案:
- 不匹配(
%empty
) FROM FIRST
(FROM FIRST_SYM
)FROM LAST
(FROM LAST_SYM
)
Bison 语法如下:
opt_from_first_last:
%empty
{
$$= NFL_NONE;
}
| FROM FIRST_SYM
{
$$= NFL_FROM_FIRST;
}
| FROM LAST_SYM
{
$$= NFL_FROM_LAST;
}
;
windowing_clause
规则
标准语法来源:MySQL 参考手册 - 14.20.2 Window Function Concepts and Syntax
标准语法(over_clause
):{OVER (window_spec) | OVER window_name}
只有如下 1 种备选方案: OVER window_name_or_spec
。Bison 语法如下:
windowing_clause:
OVER_SYM window_name_or_spec
{
$$= $2;
}
;
window_name_or_spec
规则
window_name_or_spec
规则包含如下 2 种备选方案:
window_name
规则window_spec
规则
Bison 语法如下:
window_name_or_spec:
window_name
{
$$= NEW_PTN PT_window(@$, $1);
}
| window_spec
{
$$= $1;
}
;
window_name
规则
window_name
规则只有 ident
这 1 种备选方案,即只有一个语法元素作为名称。Bison 语法如下:
window_name:
ident
{
$$= NEW_PTN Item_string($1.str, $1.length, YYTHD->charset());
}
;
window_spec
规则
window_spec
规则只有 (window_spec_details)
这 1 种备选方案,即实现了窗口从句中的括号。Bison 语法如下:
window_spec:
'(' window_spec_details ')'
{
$$= $2;
if ($$ != nullptr) $$->m_pos = @$;
}
;
window_spec_details
规则
标准语法来源:MySQL 参考手册 - 14.20.2 Window Function Concepts and Syntax
标准语法(window_spec
):[window_name] [partition_clause] [order_clause] [frame_clause]
window_spec_details
规则只有一种备选方案,其中的 opt_existing_window_name
对应标准语法 [window_name]
,opt_partition_clause
对应标准语法 [partition_clause]
,opt_window_order_by_clause
对应标准语法 [order_clause]
,opt_window_frame_clause
对应标准语法 [frame_clause]
。Bison 语法如下:
window_spec_details:
opt_existing_window_name
opt_partition_clause
opt_window_order_by_clause
opt_window_frame_clause
{
auto frame= $4;
if (!frame) // build an equivalent frame spec
{
auto start_bound= NEW_PTN PT_border(POS(), WBT_UNBOUNDED_PRECEDING);
auto end_bound= NEW_PTN PT_border(POS(), $3 ? WBT_CURRENT_ROW :
WBT_UNBOUNDED_FOLLOWING);
auto bounds= NEW_PTN PT_borders(POS(), start_bound, end_bound);
frame= NEW_PTN PT_frame(POS(), WFU_RANGE, bounds, nullptr);
frame->m_originally_absent= true;
}
$$= NEW_PTN PT_window(POS(), $2, $3, frame, $1);
}
;
opt_existing_window_name
规则
标准语法:[window_name]
opt_existing_window_name
规则有如下 2 种备选方案:
- 不匹配
window_name
:即使用一个语法元素作为已存在的窗口名称
Bison 语法如下:
opt_existing_window_name:
%empty
{
$$= nullptr;
}
| window_name
{
$$= $1;
}
;
opt_partition_clause
规则
标准语法:[partition_clause]
,其中 partition_clause: PARTITION BY expr [, expr] ...
opt_partition_clause
规则有如下 2 种备选方案:
- 不匹配
PARTITION BY {group_list}
:即使用group_list
规则的匹配结果分区。group_list
待我们梳理GROUP BY
子句时梳理。
Bison 语法如下:
opt_partition_clause:
%empty
{
$$= nullptr;
}
| PARTITION_SYM BY group_list
{
$$= $3;
if ($$ != nullptr) $$->m_pos = @$;
}
;
opt_window_order_by_clause
规则
标准语法:[order_clause]
,其中 order_clause: ORDER BY expr [ASC|DESC] [, expr [ASC|DESC]] ...
opt_window_order_by_clause
规则有如下 2 种备选方案:
- 不匹配
ORDER BY {order_list}
:即使用group_list
规则的匹配结果排序。order_list
待我们梳理ORDER BY
子句时梳理。
Bison 语法如下:
opt_window_order_by_clause:
%empty
{
$$= nullptr;
}
| ORDER_SYM BY order_list
{
$$= $3;
if ($$ != nullptr) $$->m_pos = @$;
}
;
opt_window_frame_clause
规则
标准语法来源:MySQL 参考手册 - 14.20.3 Window Function Frame Specification
标准语法:[frame_clause]
,其中 frame_clause: frame_units frame_extent
opt_window_frame_clause
规则有不匹配或连续匹配 window_frame_units
规则、window_frame_extent
规则和 opt_window_frame_exclusion
规则两种备选方案。Bison 语法如下:
opt_window_frame_clause:
%empty
{
$$= nullptr;
}
| window_frame_units
window_frame_extent
opt_window_frame_exclusion
{
$$= NEW_PTN PT_frame(@$, $1, $2, $3);
}
;
window_frame_units
规则
标准语法:frame_units: {ROWS | RANGE}
window_frame_units
规则有匹配 ROWS
关键字,匹配 RANGE
关键字和匹配 GROUPS
关键字三种备选方案。Bison 语法如下:
window_frame_units:
ROWS_SYM { $$= WFU_ROWS; }
| RANGE_SYM { $$= WFU_RANGE; }
| GROUPS_SYM { $$= WFU_GROUPS; }
;
window_frame_extent
规则
标准语法:frame_extent: {frame_start | frame_between}
window_frame_extent
规则有匹配 window_frame_start
规则结果和匹配 window_frame_between
规则结果两种备选方案。Bison 语法如下:
window_frame_extent:
window_frame_start
{
auto end_bound= NEW_PTN PT_border(@$, WBT_CURRENT_ROW);
$$= NEW_PTN PT_borders(@$, $1, end_bound);
}
| window_frame_between
{
$$= $1;
}
;
window_frame_start
规则
标准语法:{CURRENT ROW | UNBOUNDED PRECEDING | UNBOUNDED FOLLOWING | expr PRECEDING | expr FOLLOWING}
window_frame_start
规则有如下 5 种备选方案:
- 依次匹配
UNBOUNDED
关键字和PRECEDING
关键字 - 依次匹配
NUM_literal
规则匹配结果和PRECEDING
关键字 - 依次匹配
param_marker
规则匹配结果和PRECEDING
关键字 - 依次匹配
INTERVAL
关键字、expr
规则匹配结果、interval
规则匹配结果和PRECEDING
关键字 - 依次匹配
CURRENT
关键字和ROW
关键字
其中 NUM_literal
规则和 interval
规则待数量数值类规则时梳理。
Bison 语法如下:
window_frame_start:
UNBOUNDED_SYM PRECEDING_SYM
{
$$= NEW_PTN PT_border(@$, WBT_UNBOUNDED_PRECEDING);
}
| NUM_literal PRECEDING_SYM
{
$$= NEW_PTN PT_border(@$, WBT_VALUE_PRECEDING, $1);
}
| param_marker PRECEDING_SYM
{
$$= NEW_PTN PT_border(@$, WBT_VALUE_PRECEDING, $1);
}
| INTERVAL_SYM expr interval PRECEDING_SYM
{
$$= NEW_PTN PT_border(@$, WBT_VALUE_PRECEDING, $2, $3);
}
| CURRENT_SYM ROW_SYM
{
$$= NEW_PTN PT_border(@$, WBT_CURRENT_ROW);
}
;
window_frame_between
规则
标准语法:BETWEEN frame_start AND frame_end
window_frame_between
规则只有依次匹配 BETWEEN
关键字、window_frame_bound
规则匹配结果、AND
关键字和 window_frame_bound
规则匹配结果这一种备选方案。Bison 语法如下:
window_frame_between:
BETWEEN_SYM window_frame_bound AND_SYM window_frame_bound
{
$$= NEW_PTN PT_borders(@$, $2, $4);
}
;
window_frame_bound
规则
标准语法:{CURRENT ROW | UNBOUNDED PRECEDING | UNBOUNDED FOLLOWING | expr PRECEDING | expr FOLLOWING}
与 window_frame_start
规则类似,window_frame_bound
规则有如下 5 种备选方案:
- 匹配
window_frame_start
规则的匹配结果 - 依次匹配
UNBOUNDED
关键字和FOLLOWING
关键字 - 依次匹配
NUM_literal
规则匹配结果和FOLLOWING
关键字 - 依次匹配
param_marker
规则匹配结果和FOLLOWING
关键字 - 依次匹配
INTERVAL
关键字、expr
规则匹配结果、interval
规则匹配结果和FOLLOWING
关键字
Bison 语法如下:
window_frame_bound:
window_frame_start
{
$$= $1;
}
| UNBOUNDED_SYM FOLLOWING_SYM
{
$$= NEW_PTN PT_border(@$, WBT_UNBOUNDED_FOLLOWING);
}
| NUM_literal FOLLOWING_SYM
{
$$= NEW_PTN PT_border(@$, WBT_VALUE_FOLLOWING, $1);
}
| param_marker FOLLOWING_SYM
{
$$= NEW_PTN PT_border(@$, WBT_VALUE_FOLLOWING, $1);
}
| INTERVAL_SYM expr interval FOLLOWING_SYM
{
$$= NEW_PTN PT_border(@$, WBT_VALUE_FOLLOWING, $2, $3);
}
;
opt_window_frame_exclusion
规则
opt_window_frame_exclusion
有如下 5 种备选方案:
- 不匹配
- 依次匹配
EXCLUDE
关键字、CURRENT
关键字和ROW
关键字 - 依次匹配
EXCLUDE
关键字和GROUP
关键字 - 依次匹配
EXCLUDE
关键字和TIES
关键字 - 依次匹配
EXCLUDE
关键字、NO
关键字和OTHERS
关键字
Bison 语法如下:
opt_window_frame_exclusion:
%empty
{
$$= nullptr;
}
| EXCLUDE_SYM CURRENT_SYM ROW_SYM
{
$$= NEW_PTN PT_exclusion(@$, WFX_CURRENT_ROW);
}
| EXCLUDE_SYM GROUP_SYM
{
$$= NEW_PTN PT_exclusion(@$, WFX_GROUP);
}
| EXCLUDE_SYM TIES_SYM
{
$$= NEW_PTN PT_exclusion(@$, WFX_TIES);
}
| EXCLUDE_SYM NO_SYM OTHERS_SYM
{ $$= NEW_PTN PT_exclusion(@$, WFX_NO_OTHERS);
}
;