默认帧是隐性逻辑陷阱:有order by时自动补range between unbounded preceding and current row,遇重复值会错误累加;无order by时补rows between unbounded preceding and unbounded following,导致sum变全分区总和;last_value等函数不显式声明帧则必返回当前行值。

窗口函数的默认帧范围不是“安全兜底”,而是隐性逻辑陷阱——不显式声明,90% 的累计、移动、末值类计算都会出错。
为什么没写 ROWS/RANGE 就会算错
数据库不会因为没写 ROWS 或 RANGE 就拒绝执行,而是按规则自动补默认值,但这个默认值和你的直觉往往相反:
- 有
ORDER BY时,默认是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:SUM 变成累加,但遇到重复排序值(如多个同薪员工),RANGE会把所有同值行全拉进来,实际窗口远超预期 - 没
ORDER BY时,默认是ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING:SUM 直接变成整分区总和,根本不是你想要的“逐行累计” -
LAST_VALUE()默认只返回当前行值,因为它的默认帧就是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,压根看不到后面的数据
哪些函数必须显式写 FRAME 才可靠
不是所有窗口函数都受帧影响,但以下三类只要逻辑涉及“范围”或“顺序”,就绝不能依赖默认:
-
SUM()、AVG()、MIN()、MAX()等聚合类:是否累计、是否滑动、是否覆盖全组,全由帧决定 -
LAG()、LEAD()、FIRST_VALUE()、LAST_VALUE():它们的“前/后/首/末”是相对于当前帧而言的,帧不对,“前1行”可能根本不存在或指向错误位置 - 移动统计类(如 3 日均值、最近 5 行中位数):
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW是唯一可控写法;用默认帧或RANGE会导致行数不稳定、结果漂移
ROWS 和 RANGE 到底该选哪个
关键看你要“数行”还是“比值”:
- 想严格控制参与计算的物理行数(比如“前2行+当前行”),必须用
ROWS:它只看行号偏移,不关心字段值是否重复,性能也更好 - 想按真实业务区间切片(比如“过去7天内所有订单”),才考虑
RANGE,且仅限支持INTERVAL的数据库(PostgreSQL / Oracle),MySQL 8.0+ 不支持RANGE中的INTERVAL - 时间字段有重复(如同一天多笔订单)、数字主键有空缺、枚举字段基数低——一律优先用
ROWS,RANGE在这些场景下极易膨胀窗口
最容易被忽略的细节:UNBOUNDED 不是“无穷”,也不能乱配
UNBOUNDED PRECEDING 和 UNBOUNDED FOLLOWING 是分区边界锚点,不是数学概念:
- 它们永远指向当前
PARTITION BY分区的第一行或最后一行,不是整张表的头尾 - 在
ROWS帧中,UNBOUNDED PRECEDING和UNBOUNDED FOLLOWING不能同时出现,否则报错:Window frame 'rows' cannot have both UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING - 写
LAST_VALUE(x) OVER (PARTITION BY id ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)才真能取到分区末值;漏掉UNBOUNDED FOLLOWING,就还是当前行
真正麻烦的不是语法写不出来,而是错误结果看起来“合理”——SUM 累加变全量、LAST_VALUE 总等于当前值、移动平均突然跳变,这些都不会报错,只会悄悄污染下游逻辑。显式写帧不是多此一举,是把控制权从数据库默认规则手里抢回来的必要动作。










