SQL累积求和指的是按照指定的排序规则,将当前行及之前所有行的数值进行累加计算,在业务分析中应用十分广泛,比如统计每月的累计营收、用户的累计消费金额等。不同的数据库版本和场景可以选择不同的实现方式,下面逐一介绍常用的实现方法。

一、使用窗口函数实现累积求和
窗口函数是实现SQL累积求和最简洁高效的方式,主流的关系型数据库如MySQL 8.0+、PostgreSQL、SQL Server、Oracle都支持该语法。核心是使用SUM()函数配合OVER子句,通过ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW指定累加范围是从第一行到当前行。
假设有一张sales表,存储了每个月的销售数据,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| month | int | 月份,取值1-12 |
| amount | decimal(10,2) | 当月销售额 |
使用窗口函数计算累计销售额的SQL语句如下:
-- 计算每个月截至当月的累计销售额 SELECT month, amount, -- 按月份排序,累加当前行及之前所有行的销售额 SUM(amount) OVER (ORDER BY month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_amount FROM sales ORDER BY month;
如果不想显式写累加范围,也可以省略ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,因为ORDER BY配合SUM()窗口函数时,默认累加范围就是第一行到当前行,简化后的语句如下:
SELECT month, amount, SUM(amount) OVER (ORDER BY month) AS cumulative_amount FROM sales ORDER BY month;
二、使用自连接实现累积求和
如果使用的数据库版本不支持窗口函数,比如MySQL 5.7及以下版本,可以通过自连接的方式实现累积求和。思路是将原表和自身进行连接,连接条件为右表的月份小于等于左表的月份,然后按左表的月份分组,对右表的销售额求和。
对应的SQL实现如下:
SELECT s1.month, s1.amount, SUM(s2.amount) AS cumulative_amount FROM sales s1 -- 自连接,关联月份小于等于当前月份的所有记录 LEFT JOIN sales s2 ON s2.month <= s1.month GROUP BY s1.month, s1.amount ORDER BY s1.month;
这种方式的缺点是当数据量较大时,自连接会产生大量中间结果,性能比窗口函数差很多,适合小数据量的场景使用。
三、使用子查询实现累积求和
除了自连接,也可以通过子查询的方式实现累积求和,逻辑和自连接类似,在SELECT子句中嵌套子查询,计算小于等于当前月份的所有销售额之和。
实现语句如下:
SELECT month, amount, -- 子查询计算累计和 (SELECT SUM(amount) FROM sales s2 WHERE s2.month <= s1.month) AS cumulative_amount FROM sales s1 ORDER BY month;
子查询方式的性能和自连接接近,同样不适合大数据量场景,但是逻辑相对更直观,容易理解。
四、不同方法对比
| 实现方式 | 适用数据库版本 | 性能 | 代码简洁度 |
|---|---|---|---|
| 窗口函数 | MySQL 8.0+、PostgreSQL、SQL Server、Oracle等 | 高 | 高 |
| 自连接 | 所有支持标准SQL的数据库 | 中低 | 中 |
| 子查询 | 所有支持标准SQL的数据库 | 中低 | 中 |
五、注意事项
- 排序字段如果有重复值,窗口函数默认会把相同排序值的行都算入累加范围,如果需要按唯一字段排序,建议选择主键或者唯一索引字段作为排序依据。
- 如果数据中存在NULL值,
SUM()函数会自动忽略NULL,不会影响累加结果,不需要额外做NULL处理。 - 如果需要按分组进行累积求和,比如按不同部门分别计算累计销售额,可以在
OVER子句中添加PARTITION BY子句,示例如下:
-- 按部门分组,计算每个部门内的累计销售额 SELECT dept_id, month, amount, SUM(amount) OVER (PARTITION BY dept_id ORDER BY month) AS dept_cumulative_amount FROM sales ORDER BY dept_id, month;