Frame子句决定窗口函数每行的计算范围,有ORDER BY时默认ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,无ORDER BY时默认GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING;UNBOUNDED指分区边界不可达而非数学无穷,且ROWS帧中二者不可共存。

Frame子句决定“当前行能看到哪些行”
窗口函数不是对整张表算一次,而是为每一行定义一个局部计算范围——这个范围就是由FRAME子句控制的。没写FRAME时,数据库会按规则自动补默认值,但这个默认值在PARTITION BY和无ORDER BY场景下完全不同:有ORDER BY时默认是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW;没ORDER BY时默认是GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING(等价于整个分区)。实际写错或漏掉FRAME,常导致累计和变成全分区总和、移动平均只算一行,甚至查询结果与预期完全相反。
UNBOUNDED PRECEDING和UNBOUNDED FOLLOWING不是“无限”,而是“边界不可达”
UNBOUNDED这个词容易让人误解为数学意义上的无穷大,其实它只是SQL标准里对窗口边界的占位符表达:表示“从分区开头一直延伸到此处”或“从此处一直延伸到分区末尾”,不涉及具体行数计算,也不触发额外扫描。它的关键作用是锚定动态窗口的起点或终点。
-
UNBOUNDED PRECEDING:永远指向当前分区的第一行(不是整张表第一行) -
UNBOUNDED FOLLOWING:永远指向当前分区的最后一行 - 二者不能同时出现在同一
ROWS帧中(语法报错:ERROR 3578 (HY000): Window frame 'rows' cannot have both UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING) - 在
RANGE帧中,UNBOUNDED PRECEDING可能因排序键重复而包含多行,行为比ROWS更难预测
什么时候必须显式写FRAME子句?
三个典型场景下,依赖默认帧会出问题,必须手动指定:
- 要算“最近3天销售额均值”,但日期字段有重复值 → 必须用
RANGE BETWEEN INTERVAL '3' DAY PRECEDING AND CURRENT ROW,不能靠ROWS - 做移动平均时想包含当前行+前两行+后一行 →
ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING,默认帧完全不满足 - 计算“部门内薪资中位数”,需先
ORDER BY salary再用ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING确保覆盖全分区,否则PERCENTILE_CONT类函数可能返回NULL
MySQL 8.0 vs PostgreSQL vs SQL Server的UNBOUNDED兼容性差异
虽然UNBOUNDED是SQL标准关键词,但不同数据库对它的解析粒度不同:
- MySQL 8.0+ 支持
ROWS和RANGE帧,但不支持GROUPS帧;UNBOUNDED在RANGE中对DATE类型行为稳定,对TIMESTAMP微秒级可能截断 - PostgreSQL 允许
UNBOUNDED与EXCLUDE子句共用(如EXCLUDE CURRENT ROW),适合剔除自身参与计算 - SQL Server 2012+ 要求
ORDER BY存在才允许UNBOUNDED PRECEDING,否则报错Window aggregate functions cannot be used in the context of a window without an ORDER BY clause - 所有主流引擎都不支持
UNBOUNDED用于OFFSET或FETCH,那是另一个语法域
真正容易被忽略的是:当你在CTE里嵌套窗口函数,并在外部再套一层ORDER BY时,内层UNBOUNDED PRECEDING仍只锚定其所在窗口的分区边界,不会受外层排序影响——这个边界隔离性,既是安全机制,也是调试时最常误判的点。










