原理是用自连接+GROUP BY模拟窗口累计:对每行t1,连接所有t2满足t2时间≤t1时间的记录,再按t1分组求和;关键在连接条件方向必须正确(如t1.date >= t2.date),否则导致NULL或反向累加。

用自连接模拟累计和的原理是什么?
旧版 MySQL(5.7 及之前)、SQLite、早期 PostgreSQL 等不支持 SUM() OVER () 时,核心思路是:对每一行,把“所有满足时间/序号 ≤ 当前行”的记录找出来,再聚合。这本质是用 JOIN + GROUP BY 模拟窗口行为。
关键约束在于连接条件必须严格反映“累计”逻辑——比如按 order_date 累计,就得写 t1.order_date >= t2.order_date(注意方向),否则会漏算或反向累加。
常见错误现象:
- 结果全为 NULL:没加
GROUP BY或连接条件写反(如用了却没对应调整别名) - 累计值重复放大:连接后没去重且源表主键不唯一,导致笛卡尔膨胀
- 性能骤降:没在连接字段(如
order_date、id)建索引,全表扫描 N² 次
MySQL 5.7 实操:按日期求销售累计和
假设表 sales 有字段 id、order_date、amount,要按日期升序累计:
SELECT t1.order_date, SUM(t2.amount) AS cumulative_amount FROM sales t1 JOIN sales t2 ON t2.order_date <p>注意事项:</p>
- 如果
order_date不唯一,仅GROUP BY t1.order_date会把当天多笔订单压成一行,丢失明细;加t1.id保留每行独立性 - 若需严格按插入顺序(而非日期),改用自增
id连接:t2.id - MySQL 5.7 默认禁用
ONLY_FULL_GROUP_BY,但开启后必须把 SELECT 中非聚合列全写进 GROUP BY
SQLite 中处理 NULL 和重复日期的坑
SQLite 不强制主键,且日期可能为 NULL。直接套用自连接会导致 NULL 行被排除(NULL 恒为 false)。
安全写法:
SELECT t1.order_date, (SELECT SUM(t2.amount) FROM sales t2 WHERE t2.order_date <p>为什么用相关子查询而不是 JOIN?</p>
- 避免 NULL 参与连接导致整行消失
- 子查询中可单独加
IS NOT NULL过滤,不影响外层结果集结构 - SQLite 对相关子查询优化较弱,数据量大时建议先建索引:
CREATE INDEX idx_sales_date ON sales(order_date);
PostgreSQL 9.5 以下版本的兼容写法
虽然后续版本支持窗口函数,但某些生产环境仍卡在 9.3。此时不能依赖 ROW_NUMBER(),但可用 generate_series 配合 LATERAL,不过更稳妥的是回归自连接。
关键差异点:
- PostgreSQL 对
GROUP BY更严格,必须包含所有非聚合 SELECT 列 - 若按时间分组但需显示原始行(如每笔订单带当日累计),必须用
ORDER BY+row_number()生成序号辅助连接,或改用LEFT JOIN配合子查询 - 性能敏感场景务必检查执行计划:
EXPLAIN ANALYZE看是否走了索引,避免嵌套循环(Nested Loop)在大数据量下爆炸
累计和逻辑看似简单,但旧数据库里真正难的是边界控制——日期为空、时间相同、主键缺失、索引缺失,任何一个都可能让结果错位或查询超时。动手前先确认三件事:源数据是否有重复/空值、连接字段是否已索引、目标行是否需要保留原始粒度。











