SQL分组累计求和必须用SUM() OVER(PARTITION BY ORDER BY),缺ORDER BY则每行结果相同(组内总和),缺PARTITION BY则跨组累加;MySQL 8.0需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW以防RANGE模式导致重复值跳变。

窗口函数里用 SUM() 做累计求和,不是直接写 SUM(sales)
很多人一上来就写 SELECT SUM(sales) OVER (),结果发现算出来是总和,不是逐行累加。关键在 OVER 子句里的排序和范围定义——没指定 ORDER BY,窗口默认是“整个分区无序”,SUM() 就对整组求和。
正确写法必须带 ORDER BY,比如按日期排序:SUM(sales) OVER (ORDER BY order_date)。这时数据库才按时间顺序一行行累加。
- 不写
ORDER BY→ 累计值每行都一样(等于全量和) - 写了
ORDER BY但没指定ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW→ 大多数引擎(PostgreSQL、SQL Server、Snowflake)默认就是这个范围,可省略;但 MySQL 8.0 要显式写才保险 - 如果按多个字段排序,比如
ORDER BY order_date, order_id,能避免同一天多笔订单的排序不确定性
分客户计算累计销售额,得用 PARTITION BY
现实场景中,你要看每个客户的独立累计值,而不是所有客户混在一起加。这时候必须加 PARTITION BY customer_id,把数据按客户切片,再各自排序累加。
错误写法:SUM(sales) OVER (ORDER BY order_date) —— 所有客户的时间线被拉平,A 客户第 3 笔单可能排在 B 客户第 1 笔前面,逻辑就乱了。
- 正确结构:
SUM(sales) OVER (PARTITION BY customer_id ORDER BY order_date) -
PARTITION BY必须在ORDER BY前面,顺序不能反 - 如果客户有重复下单时间,建议补一个唯一字段(如
order_id)一起排序,防止窗口边界模糊
MySQL 8.0 对窗口函数范围更敏感,别省略 ROWS 子句
PostgreSQL 和 SQL Server 在 ORDER BY 存在时会自动设范围为 UNBOUNDED PRECEDING TO CURRENT ROW,但 MySQL 8.0 不这样。它默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,而 RANGE 会合并相同排序值的行——如果两天销售额一样,它们会被当同一“范围”处理,导致累计值跳变。
所以 MySQL 里务必写清楚:SUM(sales) OVER (PARTITION BY customer_id ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)。
-
ROWS按物理行位置累加,RANGE按排序值累加,二者行为不同 - 只要排序字段可能重复(比如只按日期不按 ID),就别依赖默认
RANGE - SQLite 3.25+ 和 DuckDB 也默认
RANGE,同样要显式写ROWS
累计值为空或为 NULL?检查原始数据和排序字段是否含 NULL
如果某行累计销售额是 NULL,大概率不是函数写错了,而是这行的 order_date 或 sales 是 NULL。窗口函数遇到 NULL 排序值时,有些引擎(如 PostgreSQL)会把它排在最前,有些(如 MySQL)排最后,但无论如何,NULL 参与的 SUM() 结果仍是 NULL。
- 先查:
SELECT * FROM orders WHERE order_date IS NULL OR sales IS NULL - 修复建议:用
COALESCE(order_date, '1970-01-01')或过滤掉空值,别让 NULL 进入窗口排序 - 另外,
sales字段如果是字符串类型(比如存了 "N/A"),SUM()会直接报错或转成 0,得提前CAST或清洗
实际跑的时候,最容易卡在排序字段隐含 NULL 或跨客户没 PARTITION BY,这两点比语法本身更常导致结果不对。

















