应优先使用窗口函数SUM() OVER()实现按产品和日期的累计求和,需指定PARTITION BY product_id和ORDER BY sale_date(重复时加二级排序),MySQL 5.7等旧版本才用子查询替代。

用窗口函数 SUM() OVER() 直接累加
核心就是靠窗口函数,不是子查询也不是自连接。只要你的数据库支持标准 SQL 窗口函数(PostgreSQL、SQL Server、Oracle、Snowflake、MySQL 8.0+、BigQuery 都行),就该优先用 SUM(sale_amount) OVER (PARTITION BY product_id ORDER BY sale_date)。
注意两点:一是 PARTITION BY product_id 确保每个产品独立计算;二是 ORDER BY sale_date 必须存在且有意义——如果日期有重复,建议加上二级排序(比如 ORDER BY sale_date, sale_id),否则累计值可能非确定。
- 别漏写
ORDER BY:没它,SUM() OVER()就变成整组求和,不是“累计” - 避免用
ORDER BY sale_date ASC显式写ASC:虽然合法,但冗余;默认就是升序,写它反而容易在调试时误以为降序才对 - 如果原始数据里
sale_date是DATETIME或带毫秒,而你想按“日粒度”累计,得先用DATE(sale_date)截断,再ORDER BY
MySQL 5.7 或旧版 SQLite 怎么办?
这些版本不支持窗口函数,只能退回到相关子查询。写法是:对每条销售记录,查出所有同产品、且日期 ≤ 当前记录日期的销售额之和。
SELECT
s1.product_id,
s1.sale_date,
s1.sale_amount,
(SELECT SUM(s2.sale_amount)
FROM sales s2
WHERE s2.product_id = s1.product_id
AND s2.sale_date <= s1.sale_date) AS cumsum
FROM sales s1;
性能会明显变差——尤其是销售记录多时,这属于 O(n²) 操作。如果必须用,至少确保 (product_id, sale_date) 上有联合索引,否则查询可能卡住。
- 别用
WHERE s2.sale_date 却忘了 <code>product_id条件:会导致跨产品累加,结果完全错误 - 别在子查询里
GROUP BY:子查询本身已按单行驱动,加GROUP BY会报错或逻辑错乱 - SQLite 3.25+ 其实已支持窗口函数,但很多线上环境仍跑着 3.19 甚至更老版本,务必先
SELECT sqlite_version();确认
处理并列日期时累计值“跳变”问题
当多个销售发生在同一 sale_date,标准窗口函数默认按“范围帧(RANGE)”行为处理:同日期的所有行共享相同累计值(即最后一条的和)。如果你需要逐行递增(比如体现入库顺序),就得显式改用 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。
SUM(sale_amount) OVER ( PARTITION BY product_id ORDER BY sale_date, sale_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
这里 sale_id 是假设的唯一递增主键。没有类似字段的话,仅靠 sale_date 无法保证稳定排序,累计值就不可复现。
- 别依赖数据库默认的“插入顺序”:它不参与
ORDER BY,也不保证在查询中可见 -
RANGE帧在日期/数字列上容易引发意外聚合;ROWS帧才真正按物理行序累加 - PostgreSQL 和 SQL Server 默认用
ROWS,但 MySQL 8.0 默认是RANGE——这点差异极容易被忽略,导致测试环境和生产环境结果不一致
导出历史快照表时要不要加 ROW_NUMBER()?
如果目标是生成一份带序号的累计历史表(例如用于 BI 工具分页或前端展示),可以在同一查询里补上 ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY sale_date)。但注意:这个序号只是当前窗口内的顺序,不代表全局销售流水号。
更关键的是,别为了“看起来整齐”而对累计值做 ROUND() 或 CAST(... AS DECIMAL(10,2)) 后再累加——浮点误差会累积。应该全程用精确数值类型(如 DECIMAL)运算,只在最终展示层格式化。
- 别在窗口函数里嵌套
ROUND(SUM(...)):四舍五入后的值再被下一行累加,误差会滚雪球 - 如果源数据
sale_amount是FLOAT,先CAST(sale_amount AS DECIMAL(15,2))再参与累计 - 导出到 CSV 或 Excel 时,累计列若显示为科学计数法(如 1.23456789E+7),不是 SQL 问题,是下游工具自动格式化所致——得检查导出设置或加单引号前缀

















