在SQL的实际业务查询中,我们常常需要在分组的前提下计算累计值,比如统计每个部门内员工薪资的累计总和、每个用户每月消费金额的累计变化等。这类需求如果处理不当,很容易写出复杂度高、执行效率低的查询语句,下面介绍两种主流的实现方式。

准备测试数据
首先我们创建一个销售记录表,用于后续的查询示例,表结构包含销售分组、销售日期和当日销售额三个字段:
-- 创建销售记录表
CREATE TABLE sales_record (
group_id INT COMMENT '分组ID',
sale_date DATE COMMENT '销售日期',
sale_amount DECIMAL(10,2) COMMENT '当日销售额'
);
-- 插入测试数据
INSERT INTO sales_record (group_id, sale_date, sale_amount) VALUES
(1, '2024-01-01', 100.00),
(1, '2024-01-02', 150.00),
(1, '2024-01-03', 200.00),
(2, '2024-01-01', 80.00),
(2, '2024-01-02', 120.00),
(2, '2024-01-03', 90.00);
方案一:使用窗口函数计算分组累计值
窗口函数是SQL中处理分组内排序、累计等场景的高效工具,我们可以通过SUM()窗口函数结合OVER子句实现分组累计值的计算,语法逻辑清晰,执行效率也更高。
语法说明
核心语法结构为:SUM(计算字段) OVER (PARTITION BY 分组字段 ORDER BY 排序字段 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),其中PARTITION BY指定分组的依据,ORDER BY指定分组内排序的规则,ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW表示计算从分组第一行到当前行的累计值。
示例查询
下面的查询会按分组ID分组,按销售日期排序,计算每个分组内的销售额累计值:
SELECT
group_id,
sale_date,
sale_amount,
-- 计算每个分组内的销售额累计值
SUM(sale_amount) OVER (
PARTITION BY group_id
ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_amount
FROM sales_record
ORDER BY group_id, sale_date;
执行上述查询后,得到的结果如下:
| group_id | sale_date | sale_amount | cumulative_amount |
|---|---|---|---|
| 1 | 2024-01-01 | 100.00 | 100.00 |
| 1 | 2024-01-02 | 150.00 | 250.00 |
| 1 | 2024-01-03 | 200.00 | 450.00 |
| 2 | 2024-01-01 | 80.00 | 80.00 |
| 2 | 2024-01-02 | 120.00 | 200.00 |
| 2 | 2024-01-03 | 90.00 | 290.00 |
方案二:使用自连接计算分组累计值
如果数据库版本不支持窗口函数,也可以通过自连接的方式实现分组累计值的计算,这种方式兼容性更好,但查询逻辑相对复杂一些。
实现逻辑
将销售记录表自身连接,连接条件为分组ID相同,且右表的销售日期小于等于左表的销售日期,然后按左表的分组ID、销售日期、销售额分组,对右表的销售额求和,即可得到每个分组内的累计值。
示例查询
SELECT
a.group_id,
a.sale_date,
a.sale_amount,
SUM(b.sale_amount) AS cumulative_amount
FROM sales_record a
INNER JOIN sales_record b
ON a.group_id = b.group_id
AND b.sale_date <= a.sale_date
GROUP BY a.group_id, a.sale_date, a.sale_amount
ORDER BY a.group_id, a.sale_date;
该查询的结果和窗口函数方案的结果完全一致,不过当数据量较大时,自连接会产生较多的中间结果,执行效率不如窗口函数方案。
两种方案的选择建议
- 如果使用的数据库支持窗口函数,比如MySQL 8.0+、PostgreSQL、SQL Server等,优先选择窗口函数方案,代码更简洁,执行效率更高。
- 如果数据库版本较低不支持窗口函数,比如MySQL 5.7及以下版本,可以选择自连接方案实现需求。
- 如果累计值需要计算到当前行的前N行,窗口函数可以通过调整
ROWS子句的范围灵活实现,自连接方案则需要额外添加日期差的条件,复杂度会进一步提升。