编程后端开发人员

如何在 SQL 中实现累积(逐步)总计的计算,而不使用窗口函数,同时考虑在数千或数百万行上的性能?

用 Hintsage AI 助手通过面试

回答

在 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)逐行添加总和

关键特性:

  • 该方法仅在存在唯一的排序标准(id,生成日期)时有效
  • 相关子查询的可扩展性差 — 执行时间呈指数增长
  • 对于大数据量,逻辑上使用 ETL 进行聚合,而不是 SQL 或程序工具

带有陷阱的问题。

在相关子查询中,ORDER BY 是否提供保证的排序?

不 — 子查询不一定影响结果。最终结果的排序总是由主查询外部指定:结果仅取决于 WHERE 的过滤。

在这种方法中,可以并行计算累积总和吗?

不 — 顺序非常重要,尤其是在依赖前面行的计算时,因此简单的并行化在常规 SQL 中是不可能的。

为什么相关子查询在大量行时如此缓慢?

因为对于每一行,都重新计算前面行的总和。这导致 O(N^2) 操作。在 10 万行的样本中,它可能需要几分钟甚至几小时。

常见错误和反模式

  • 根据 id 错误过滤,而不是实际日期 — 总和因 id 的间隙而“跳跃”
  • 尝试在没有排序数据的情况下进行求和
  • 在需要 ETL 或分区处理的大表上使用这种方法

生活示例

消极案例

分析师通过相关子查询计算每日累积收入,但表中偶尔存在删除的 id(间隙)。最终总和出现了跳跃性下降,依赖于 id 的顺序,而非日期。

优点:

  • 在小选择集中有效,不需要窗口函数

缺点:

  • 数据不准确,计算结果与预期不符
  • 维护复杂

积极案例

工程师将累积总和的处理转移到 ETL 脚本(Python/pandas)中,然后将最终值上传至单独的表中,仅同步新数据。结果始终按照日期一致,代码在数百万条记录中快速运行。

优点:

  • 可靠性,可以在不停机的情况下进行重新计算
  • 支持大数据量

缺点:

  • 处理流程更复杂,需要外部处理工具