在 SQL 中,累积总计和滚动总和的计算传统上是通过窗口函数(例如,SUM() OVER(ORDER BY ...))来解决的,但在早期或简化版本的数据库中,只能使用子查询和分组。历史上,数据库架构师在窗口函数标准 SQL:2003 出现之前,寻找变通方法。
问题 — 在没有窗口函数的情况下,需要显式计算每一行之前所有值的总和,这导致在足够大的选集下,产生 O(N^2) 的嵌套查询,除非使用一些技巧。
解决方案:
通常使用相关子查询或通过更新值的临时表:
示例代码:
-- 使用相关查询计算累积总和 SELECT t1.id, t1.amount, ( SELECT SUM(t2.amount) FROM transactions t2 WHERE t2.id <= t1.id ) AS running_total FROM transactions t1 ORDER BY t1.id; -- 通过临时表手动更新值 CREATE TEMPORARY TABLE temp_running (id INT, amount INT, running_total INT); -- 使用外部代码(例如 pl/pgsql)逐行添加总和
关键特性:
在相关子查询中,ORDER BY 是否提供保证的排序?
不 — 子查询不一定影响结果。最终结果的排序总是由主查询外部指定:结果仅取决于 WHERE 的过滤。
在这种方法中,可以并行计算累积总和吗?
不 — 顺序非常重要,尤其是在依赖前面行的计算时,因此简单的并行化在常规 SQL 中是不可能的。
为什么相关子查询在大量行时如此缓慢?
因为对于每一行,都重新计算前面行的总和。这导致 O(N^2) 操作。在 10 万行的样本中,它可能需要几分钟甚至几小时。
分析师通过相关子查询计算每日累积收入,但表中偶尔存在删除的 id(间隙)。最终总和出现了跳跃性下降,依赖于 id 的顺序,而非日期。
优点:
缺点:
工程师将累积总和的处理转移到 ETL 脚本(Python/pandas)中,然后将最终值上传至单独的表中,仅同步新数据。结果始终按照日期一致,代码在数百万条记录中快速运行。
优点:
缺点: