order by 决定 sum() over() 的窗口起点和终点,缺之则默认全分区求和;加上后默认为 unbounded preceding 到 current row,实现逐行累计。

ORDER BY 直接决定 SUM() OVER() 的窗口起点和终点
没写 ORDER BY 的 SUM() OVER() 默认用 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,也就是整个分区全量求和;加上 ORDER BY 后,默认变成 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW——从分区第一行加到当前行。这不是“额外功能”,而是窗口范围定义的根本切换。
常见错误现象:
-
SUM(amount) OVER (PARTITION BY user_id)每行都显示该用户全部金额,不是累计 - 结果数值不递增、跳变、或与业务时间顺序对不上
本质是:数据库必须知道“哪一行先算、哪一行后算”,才能定义“当前行之前有哪些行”。ORDER BY 就是这个依据。
为什么重复值会让累计结果“卡住”?
默认帧模式在不同数据库中行为不一致:PostgreSQL 和 SQL Server 默认用 RANGE,MySQL 8.0+ 默认用 ROWS。如果排序字段有重复(比如多个订单同秒创建),RANGE 会把所有相同值的行视为同一组,导致这组内所有行的累计值完全一样。
例如按 create_time 排序,3 条记录都是 '2024-05-01',用 RANGE 时它们的 SUM() OVER() 结果相同;用 ROWS 才能保证逐行累加。
解决办法:
- 显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - 排序字段加唯一二级键,如
ORDER BY create_time, id - 避免在
ORDER BY中用函数(如DATE(created_at)),否则索引失效
PARTITION BY + ORDER BY 缺一不可
只写 PARTITION BY user_id 不写 ORDER BY,相当于告诉数据库:“在每个用户内,把所有行当一个整体来算总和”;只写 ORDER BY create_time 不写 PARTITION BY,就变成全表按时间累计,跨用户混在一起。
典型错误写法:
-
SUM(amount) OVER (PARTITION BY user_id)→ 每行都是该用户总额 -
SUM(amount) OVER (ORDER BY create_time)→ 所有用户混在一起累计
正确写法必须同时存在:
SUM(amount) OVER (PARTITION BY user_id ORDER BY create_time, id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
注意:PARTITION BY 字段必须在索引最左,ORDER BY 字段紧随其后,比如 (user_id, create_time, id),否则排序无法走索引。
ORDER BY 字段含 NULL 时行为不可控
不同数据库对 NULL 的默认排序位置不同:PostgreSQL 默认 NULLS FIRST,MySQL 8.0+ 默认 NULLS LAST。如果业务要求有效时间排前面,但某些记录 create_time 为空,结果可能错位甚至漏掉累计断点。
稳妥做法:
- 建表时给时间字段加
NOT NULL约束 - 运行时显式控制:
ORDER BY create_time NULLS LAST - 或用
COALESCE(create_time, '1970-01-01')统一兜底,但要注意类型兼容性
真正容易被忽略的是:窗口函数里的 ORDER BY 控制计算顺序,它和最终查询结果的排序无关——外层 ORDER BY 才管输出顺序。两者不能互相替代。











