用sum() over(order by sale_date)计算累计销售额最高效,需按时间排序实现行级累积;低版本mysql须用自连接模拟,但性能差。

用窗口函数 SUM() OVER() 计算累计销售额
直接用 SUM() OVER (ORDER BY ...) 是最常用、最高效的方式。关键不是“分组后累加”,而是按时间或订单顺序做行级累积——商品可能多次销售,需先按时间排序再累加。
常见错误是写成 GROUP BY product_id 后套 SUM(),那只会得到总销售额,不是“逐笔递增的累计值”。真正要的是每条销售记录对应的“截至该笔的累计额”。
- 必须指定
ORDER BY子句(通常是sale_date或order_id),否则OVER()默认无序,结果不可靠 - 若同一商品有多笔同时间销售,建议加二级排序(如
ORDER BY sale_date, id)保证确定性 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 3.25+ 也支持,但旧版不支持窗口函数
兼容低版本 MySQL(无窗口函数)的自连接方案
MySQL 5.7 或更早版本不支持 OVER(),只能用自连接模拟累计逻辑:对每条记录,查出所有“时间 ≤ 当前记录时间”的同商品销售记录并求和。
性能是最大风险点——数据量稍大(比如单商品超千条记录)就会明显变慢,因为每次都要扫描匹配行。
- 写法示例:
SELECT s1.product_id, s1.sale_date, s1.amount, (SELECT SUM(s2.amount) FROM sales s2 WHERE s2.product_id = s1.product_id AND s2.sale_date - 务必在
(product_id, sale_date)上建复合索引,否则查询会全表扫描 - 如果
sale_date有重复,且业务要求严格按插入顺序,建议改用带自增id的排序条件
注意 NULL 和重复记录对累计值的影响
NULL 值会被 SUM() 自动忽略,通常没问题;但若销售金额字段允许 NULL,且你想把 NULL 当作 0 处理,得显式写 COALESCE(amount, 0)。
重复记录(比如因 ETL 错误导入两遍)会导致同一笔销售被累加两次,结果虚高。窗口函数不会自动去重,得提前清洗或加 DISTINCT(但要注意:DISTINCT 在窗口函数里不能直接用,需先用 CTE 或子查询去重)。
- 安全做法:先确认源表主键/唯一约束是否健全,或用
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)标记重复行再过滤 - 不要依赖
SUM(DISTINCT amount)——不同订单金额可能相同,去重会误删
按商品分组 + 时间分段统计累计值(非逐行)
如果需求其实是“每个商品每天的累计销售额”,即按天聚合后再累加,那就不是窗口函数直接能解决的——得先 GROUP BY product_id, sale_date 汇总日销售额,再对结果集套窗口函数。
这种场景容易混淆“逐笔累计”和“逐日累计”,输出粒度不同,业务含义也不同。例如:某商品三天销量为 [100, 200, 150],逐笔累计是 [100, 300, 450],逐日累计也是 [100, 300, 450];但如果第三天有两笔 75,则逐笔是 [100, 300, 375, 450],逐日仍是 [100, 300, 450]。
- 典型写法:
WITH daily AS ( SELECT product_id, sale_date, SUM(amount) AS day_amount FROM sales GROUP BY product_id, sale_date ) SELECT product_id, sale_date, day_amount, SUM(day_amount) OVER (PARTITION BY product_id ORDER BY sale_date) AS cum_amount FROM daily; -
PARTITION BY product_id必须加上,否则不同商品的销售额会混在一起累加











