自连接模拟累计求和的本质是:对当前行,将所有排序位置≤它的记录数值求和;需依赖唯一排序字段(如id或date),若存在重复则须结合唯一主键保底排序,MySQL 5.7等旧版本常用此法实现。

用自连接模拟累计求和的原理
没有窗口函数时,累计求和本质是:对当前行,把所有满足“排序位置 ≤ 当前行”的记录的数值加起来。这可以通过 JOIN 把表和自身按顺序条件关联实现——比如按时间或 ID 排序后,让左表的每行匹配右表中“ID 小于等于它”的所有行。
关键点在于:必须有明确的排序字段(如 id、order_date),且该字段值不能重复,否则会多算;若存在重复,需搭配额外唯一字段(如自增主键)做保底排序。
MySQL 5.7 / PostgreSQL 9.5 等旧版本实操写法
假设有一张销售表 sales,含字段 id(递增)、amount(单笔金额),目标是按 id 顺序计算累计金额:
SELECT s1.id, s1.amount,
SUM(s2.amount) AS cumulative_amount
FROM sales s1
JOIN sales s2 ON s2.id <= s1.id
GROUP BY s1.id, s1.amount
ORDER BY s1.id;
注意三点:
-
GROUP BY必须包含所有非聚合列(s1.id,s1.amount),否则 MySQL 5.7+ 严格模式会报错Expression #2 of SELECT list is not in GROUP BY clause - 如果
id不连续(如删过数据),不影响逻辑,只影响“顺序”本身是否符合业务预期 - 性能随数据量增长急剧下降:N 行输入 → 约 N² 次比较,万级数据就要警惕
处理重复时间戳或并列排序的补救方法
当排序依据是 order_date 且存在多条同一天记录时,直接 s2.order_date 会导致同天数据互相全量累加,结果偏大。
稳妥做法是引入唯一辅助排序字段(如自增 id):
SELECT s1.id, s1.amount, s1.order_date,
SUM(s2.amount) AS cumulative_amount
FROM sales s1
JOIN sales s2
ON s2.order_date < s1.order_date
OR (s2.order_date = s1.order_date AND s2.id <= s1.id)
GROUP BY s1.id, s1.amount, s1.order_date
ORDER BY s1.order_date, s1.id;
这个条件拆成两部分:小于日期 + 等于日期但 id 更小或相等,确保严格保序。
Oracle 11g 或 SQL Server 2008 的兼容性提醒
上述写法在这些数据库里基本可用,但要注意:
- SQL Server 对
JOIN条件中的不等式()支持良好,但若表无索引,执行计划容易走嵌套循环并严重慢 - Oracle 中若用
ROWNUM伪列替代id,不可行——ROWNUM是结果集生成时才分配,无法用于自连接中的稳定排序依据 - 所有场景下,务必给排序字段建索引,例如
CREATE INDEX idx_sales_order_date_id ON sales(order_date, id);
真正麻烦的是数据量超过 10 万行还硬扛这种写法——这时候不是语法问题,是得说服业务方接受分页累计、或升级数据库版本用 SUM() OVER (ORDER BY ...)。

















