unbounded preceding 必须配合 order by 才生效,否则窗口帧退化为 current row,导致累加失效;其起点受 partition by 限制,且不可用 0 preceding 替代。

漏写 ORDER BY 会导致 UNBOUNDED PRECEDING 完全失效
UNBOUNDED PRECEDING 不是独立生效的关键词,它必须依赖 ORDER BY 才能定义“起点”在哪。没有 ORDER BY,SQL 标准强制窗口帧退化为 ROWS BETWEEN CURRENT ROW AND CURRENT ROW——此时无论写不写 UNBOUNDED PRECEDING,每行都只算自己。
常见错误现象:查询不报错,但 SUM(col) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 每行结果都等于 col 本身,看起来像没累加。
- PostgreSQL / BigQuery / Spark SQL 会静默降级,行为不可靠
- MySQL 8.0+ 直接报错:
Window 'w' with no ORDER BY is not allowed - SQL Server 默认用
RANGE,即使写了ORDER BY,若没显式声明ROWS,仍可能因重复值聚合出错
ROWS BETWEEN 和 RANGE BETWEEN 的区别直接影响结果
UNBOUNDED PRECEDING 必须搭配 ROWS BETWEEN 或 RANGE BETWEEN 使用,但二者语义完全不同:
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:严格按物理行序累加,从分区第一行到当前行(含),排序键重复也不合并 -
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:按排序列的值归组,所有ORDER BY值 ≤ 当前行的行都会被纳入——如果order_time有重复,多行会被捆进同一帧,导致“提前拉高”累计值
例如:两笔订单同为 '2026-09-01',RANGE 下它们会同时参与彼此的 MAX() 计算;而 ROWS 下只要排序稳定(如补 order_id),就能保证逐行推进。
PARTITION BY 改变 UNBOUNDED PRECEDING 的“起点”范围
UNBOUNDED PRECEDING 永远不是整张表的第一行,而是当前分区内的第一行(按 ORDER BY 排序后)。这个点极易被忽略,尤其在跨时间窗口或用户分组场景中。
- 写
PARTITION BY user_id ORDER BY order_time→ 每个用户的首单时间就是该分区的“起点” - 写
PARTITION BY DATE(order_time) ORDER BY order_time→ 每天内部从当天最早一单开始累加,不跨天 - 完全不写
PARTITION BY→ 才是全表范围,但数据量大时性能风险陡增
注意:分区字段和排序字段逻辑要自洽。比如按 region 分区却按全局 create_time 排序,会导致每个分区内排序混乱,UNBOUNDED PRECEDING 起点不可控。
别把 0 PRECEDING 当成 UNBOUNDED PRECEDING 的简写
0 PRECEDING 不是语法糖,它是非法或未定义行为:
- PostgreSQL 报错:
frame starting offset must not be negative(即使写 0,也被视为负偏移) - SQL Server 直接拒绝解析,提示
Incorrect syntax near 'PRECEDING' - 某些引擎(如旧版 Hive)可能容忍,但实际等价于
CURRENT ROW,造成逻辑错误
真正需要“从头开始”,唯一合法写法只有 UNBOUNDED PRECEDING。它不是数值,而是 SQL 标准中不可替换的关键字标记——就像 NULL 不能写成 0 一样。










