MySQL 8.0+ 支持 SUM() OVER() 窗口函数实现累计和,必须含 ORDER BY(如 ORDER BY order_date, id),否则退化为分组总和;PARTITION BY 可分组累计;MySQL 5.7- 需用变量模拟且依赖显式排序。

MySQL 8.0+ 用窗口函数 SUM() OVER() 最直接
MySQL 5.7 及更早版本不支持窗口函数,但 8.0+ 已原生支持,这是目前最清晰、性能也较好的方式。关键不是“能不能算”,而是“按什么顺序累加”——ORDER BY 子句在 OVER() 中不可省略,否则会变成分组总和而非累计值。
常见错误:漏写 ORDER BY,导致结果等同于 SUM(col) GROUP BY,每行都显示整个分组的总和:
SELECT category, amount, SUM(amount) OVER (PARTITION BY category) AS wrong_total -- ❌ 没有 ORDER BY,不是累计 FROM sales;
正确写法需明确排序依据(如时间、ID):
SELECT
category,
order_date,
amount,
SUM(amount) OVER (
PARTITION BY category
ORDER BY order_date, id
) AS running_total
FROM sales;
-
PARTITION BY定义分组边界(比如按category分开累计) -
ORDER BY决定组内累加顺序;若存在相同时间戳,建议补上id等唯一列避免非确定性结果 - 默认窗口帧是
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,正好符合累计语义,无需显式写出
PostgreSQL / SQL Server 同样适用 SUM() OVER()
这些数据库对窗口函数的支持比 MySQL 更早、更稳定,语法一致,但要注意 PostgreSQL 对 NULL 的处理更严格:如果排序字段含 NULL,可能被排在最前或最后(取决于 NULLS FIRST/LAST),影响累计逻辑。
示例中若 order_date 允许为空,建议显式控制:
SUM(amount) OVER ( PARTITION BY category ORDER BY order_date NULLS LAST, id ) AS running_total
- SQL Server 2005+ 支持该语法,但旧版(如 2000)只能靠自连接模拟,效率极低
- PostgreSQL 从 8.4 开始支持窗口函数,9.0+ 性能优化明显
- 所有支持方都要求
ORDER BY存在,否则报错(PostgreSQL 直接拒绝,SQL Server 报错Windowed functions require an OVER clause with ORDER BY)
MySQL 5.7 或更老版本只能用变量模拟
没有窗口函数时,依赖用户变量 @running_sum 是主流做法,但必须满足两个硬性条件:查询结果顺序确定、变量赋值与引用在同一 SELECT 中且顺序可控。稍有不慎就会因优化器重排或并发会话干扰而错乱。
典型安全写法(带初始化子查询):
SELECT category, order_date, amount, @running_sum := IF(@prev = category, @running_sum + amount, amount) AS running_total, @prev := category FROM ( SELECT category, order_date, amount FROM sales ORDER BY category, order_date, id -- ⚠️ 必须显式排序! ) s CROSS JOIN (SELECT @running_sum := 0, @prev := '') AS init;
- 子查询中
ORDER BY不可省略,否则变量累加顺序无法保证 - 不能把变量初始化写在外部
SET,因为多语句执行顺序不被 SELECT 保证 - 该方法在 MySQL 8.0+ 中仍可用,但已被标记为“不推荐”,未来版本可能禁用变量在 SELECT 中的副作用行为
避免用自连接做 RUNNING TOTAL(尤其大数据量)
有人用 JOIN 加条件模拟累计,比如:
O(n²)
- 仅适合几百行以内的调试或报表场景
- 如果非要兼容极老数据库且数据量小,至少给
(category, order_date)建联合索引 - 注意:若
order_date有重复,需引入唯一列(如id)打破并列,否则 SUM 会重复计算
实际用的时候,先看数据库版本——能用 SUM() OVER() 就别碰变量或自连接;用变量的话,排序和初始化缺一不可;至于自连接,除非你确认数据永远不超过 500 行,否则别在生产环境试。











