相关子查询实现移动累计总和的本质是:对当前行,执行一次子查询,求按排序列(如sort_col)从首行到当前行的所有值之和;子查询依赖外部查询字段,故称“相关”,但性能差、易出错,不适用于大表。

SQL 里没有内置的移动累计总和函数(如 window 函数中的 SUM() OVER (ORDER BY ... ROWS BETWEEN ...)),但用相关子查询能实现,只是性能差、易出错——别在大表上直接套用。
什么是相关子查询实现移动累计总和
本质是:对当前行的每一行,执行一次子查询,求从首行到当前行(按某列排序)的所有值之和。子查询依赖外部查询的字段(比如 id 或 order_date),所以叫“相关”。
典型结构是:SELECT t1.x, (SELECT SUM(t2.val) FROM table t2 WHERE t2.sort_col
常见错误现象:结果重复、顺序错乱、NULL 值干扰求和、WHERE 条件漏掉排序字段的可比性(如用 <code>= 而非 )。
- 必须确保
sort_col具有唯一性,或组合唯一(否则多行同值会导致累计值跳变) - 如果
val列含NULL,SUM()会自动忽略,但若全为NULL行,结果为NULL而非0,必要时加COALESCE(SUM(...), 0) - 子查询中不能引用外部查询的别名(如
t1)在某些旧版 MySQL 中会报错,需改用无别名或显式限定
PostgreSQL / SQL Server / Oracle 中更推荐用窗口函数替代
相关子查询在这些数据库里虽支持,但执行计划通常是嵌套循环,数据量超几千行就明显变慢。窗口函数才是正解:
SELECT date, amount,
SUM(amount) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING) AS running_sum
FROM sales;
关键差异:
-
ROWS UNBOUNDED PRECEDING明确语义为“从第一行到当前行”,比相关子查询的WHERE ... 更安全、更高效 - 若需按分组重置(如每个用户独立累计),加
PARTITION BY user_id即可,相关子查询要额外写多层嵌套,极易出错 - Oracle 11g+、PostgreSQL 8.4+、SQL Server 2005+ 都支持;MySQL 8.0+ 也支持,但 5.7 及更早版本不支持,此时才被迫用相关子查询
MySQL 5.7 或更老版本的实操要点
这是相关子查询真正“不得不上”的场景。务必注意三点:
- 给排序字段建索引,例如
CREATE INDEX idx_date ON sales(date);,否则子查询每次都要全表扫描 - 避免在子查询的
WHERE中使用函数包裹排序字段(如DATE(date)),会失效索引 - 如果业务允许近似值,可考虑用变量法(
@sum := @sum + amount),但它不保证执行顺序,官方已明确不推荐用于生产环境,尤其在有ORDER BY和优化器重排时行为不可控 - 示例(安全写法):
SELECT s1.date, s1.amount, (SELECT COALESCE(SUM(s2.amount), 0) FROM sales s2 WHERE s2.date
相关子查询写起来像直觉,但执行时每行都触发一次子查询,复杂度是 O(n²)。哪怕只有 1 万行,也可能执行上亿次比较。真要跑,先在小数据集验证逻辑,再看执行计划里的 type 是否为 range 或更好——如果是 ALL,赶紧停手换方案。










