order by 决定窗口范围而非仅排序,缺它则无累计;sum() over(partition by user_id) 默认全分区求和,每行值相同;正确累计需同时指定 partition by、order by 及 rows 帧。

ORDER BY 不是“让 SUM() 变成累计”,而是直接决定窗口从哪一行开始、到哪一行结束——没它,就根本没累计这回事。
没写 ORDER BY 时,SUM() OVER() 其实不累计
数据库看到 SUM(amount) OVER (PARTITION BY user_id),会先算出整个分区的总和,再把同一个数字填进每一行。这不是“累计结果错了”,而是压根没做累计。
- 常见错误现象:
SUM(amount) OVER (PARTITION BY user_id)每行值都一样,且等于该用户全部金额之和 - 本质是:窗口范围默认为
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,即整分区全量求和 - 哪怕数据本身按时间物理有序,数据库也不会自动按时间累加——它只认你写的
ORDER BY
ORDER BY 决定“当前行之前有哪些行”
写了 ORDER BY create_time, id,数据库才明确知道:对每个用户,先排时间,时间相同时再按 id 排序,然后从第一行加到当前行。
- 默认帧模式因数据库而异:PostgreSQL / SQL Server 默认用
RANGE,MySQL 8.0+ 默认用ROWS - 如果只写
ORDER BY create_time,而多条记录create_time相同,RANGE会让它们共享同一累计值(因为值相同就被归进同一组) - 要严格逐行累加,必须显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
PARTITION BY 和 ORDER 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) - 索引也要匹配:联合索引应为
(user_id, create_time, id),否则排序无法走索引
NULL 和重复值会让累计结果不可控
ORDER BY 字段含 NULL 时,不同数据库默认行为不一致:PostgreSQL 默认 NULLS FIRST,MySQL 8.0+ 默认 NULLS LAST。如果业务要求有效时间排前面,但部分记录 create_time 为空,可能造成累计断点或顺序错乱。
- 建议显式控制:
ORDER BY COALESCE(create_time, '1970-01-01'), id - 避免在
ORDER BY中用函数,如DATE(created_at),会导致索引失效 - 真正难调的不是语法,而是排序字段的分布——比如 90% 的
status值都是'active',RANGE帧会让窗口膨胀,性能骤降











