累加计算是数据分析中的常见需求,例如累计销售额、累计访问量、累计收益等场景。在 SQL 中,存在多种实现累加计算的方法,本文将系统解析各类实现方式及其适用场景。
一、基础数据准备
首先创建一张销售记录表示例:
```sql
创建销售记录表
CREATE TABLE Sales (
SaleDate DATE, 销售日期
ProductName VARCHAR(50), 产品名称
DailySales DECIMAL(10,2) 日销售额
);
插入测试数据
INSERT INTO Sales VALUES
('20240101', '产品A', 1000.00),
('20240102', '产品A', 1500.00),
('20240103', '产品A', 800.00),
('20240104', '产品A', 2000.00),
('20240101', '产品B', 500.00),
('20240102', '产品B', 700.00),
('20240103', '产品B', 1200.00);
查看数据
SELECT FROM Sales ORDER BY ProductName, SaleDate;
```
二、方法一:基于自连接实现
在窗口函数尚未普及时,自连接是常用的累加实现方式,但其性能在处理大规模数据时表现不佳:
```sql
通过自连接计算各产品累计销售额
SELECT
s1.ProductName,
s1.SaleDate,
s1.DailySales,
SUM(s2.DailySales) AS CumulativeSales
FROM Sales s1
INNER JOIN Sales s2
ON s1.ProductName = s2.ProductName 按产品分组
AND s2.SaleDate <= s1.SaleDate 仅累计当前日期及之前的销售
GROUP BY s1.ProductName, s1.SaleDate, s1.DailySales
ORDER BY s1.ProductName, s1.SaleDate;
```
输出结果示例:
| 产品名称 | 销售日期 | 每日销售 | 累计销售 |
| 产品A | 20240101 | 1000.00 | 1000.00 |
| 产品A | 20240102 | 1500.00 | 2500.00 |
| 产品A | 20240103 | 800.00 | 3300.00 |
| 产品A | 20240104 | 2000.00 | 5300.00 |
| 产品B | 20240101 | 500.00 | 500.00 |
| 产品B | 20240102 | 700.00 | 1200.00 |
| 产品B | 20240103 | 1200.00 | 2400.00 |
不足之处: 数据量较大时,由于涉及笛卡尔积关联,性能存在显著瓶颈。
三、方法二:基于子查询实现
作为一种传统实现方式,子查询同样伴随明显的性能问题:
```sql
使用关联子查询进行累计计算
SELECT
s1.ProductName,
s1.SaleDate,
s1.DailySales,
(
SELECT SUM(s2.DailySales)
FROM Sales s2
WHERE s2.ProductName = s1.ProductName
AND s2.SaleDate <= s1.SaleDate
) AS CumulativeSales
FROM Sales s1
ORDER BY s1.ProductName, s1.SaleDate;
```
缺点: 每行记录均需执行一次子查询,性能负担更为沉重。
四、方法三:使用窗口函数(推荐)
在现代 SQL 中,窗口函数是实现累加计算的首选方法,具备优异的执行效率与表达能力:
```sql
通过 SUM() OVER() 窗口函数计算累计销售额
SELECT
ProductName,
SaleDate,
DailySales,
SUM(DailySales) OVER (
PARTITION BY ProductName 按产品分组
ORDER BY SaleDate 按销售日期排序
ROWS BETWEEN UNBOUNDED PRECEDING 从首行开始
AND CURRENT ROW 至当前行
) AS CumulativeSales,
简洁写法:默认范围即为从首行到当前行
SUM(DailySales) OVER (
PARTITION BY ProductName
ORDER BY SaleDate
) AS CumulativeSales2 结果与上述一致
FROM Sales
ORDER BY ProductName, SaleDate;
```
窗口函数的优势:
1. 高性能:仅需单次数据扫描即可完成计算;
2. 灵活性高:支持通过框架子句精确控制累计范围;
3. 可读性强:逻辑清晰,易于编写与维护。
五、方法四:基于CTE递归实现(适用于特殊场景)
对于依赖前序行进行递推计算的场景(如斐波那契数列),可使用递归公用表表达式:
```sql
使用递归CTE生成斐波那契数列
WITH Fibonacci (n, a, b) AS (
初始成员
SELECT 1, 0, 1
UNION ALL
递归成员:基于前一行计算
SELECT n + 1, b, a + b
FROM Fibonacci
WHERE n < 10 限制递归深度,避免无限循环
)
SELECT n, b AS FibonacciNumber
FROM Fibonacci
ORDER BY n;
```
最佳实践建议:
1. 优先选择窗口函数,特别是 `SUM() OVER()`,其在性能与表达上均占优;
2. 明确排序规则:累加计算必须指定 `ORDER BY`,否则结果不具备确定性;
3. 合理划分分区:通过 `PARTITION BY` 实现分组内的独立累计;
4. 精确控制窗口范围:利用 `ROWS BETWEEN` 子句定义累加的行范围;
5. 避免使用游标:除非特定需求,否则不建议采用游标实现累加逻辑。
六、实战示例:计算销售额累计占比排行榜
```sql
按产品总销售额排序,并计算累计占比
WITH ProductSales AS (
SELECT
ProductName,
SUM(DailySales) AS TotalSales
FROM Sales
GROUP BY ProductName
),
RankedProducts AS (
SELECT
ProductName,
TotalSales,
ROW_NUMBER() OVER (ORDER BY TotalSales DESC) AS Rank,
SUM(TotalSales) OVER (ORDER BY TotalSales DESC) AS CumulativeSales,
SUM(TotalSales) OVER () AS GrandTotal
FROM ProductSales
)
SELECT
ProductName,
TotalSales,
Rank,
CumulativeSales,
CAST(CumulativeSales 100.0 / GrandTotal AS DECIMAL(5,2)) AS CumulativePercentage
FROM RankedProducts
ORDER BY Rank;
```
总结
累加计算是 SQL 数据分析中的关键能力。在现代 SQL Server 中,推荐采用窗口函数来实现相关需求。掌握以下三个核心要素,即可灵活应对各类累加场景:
PARTITION BY:定义分组依据;
ORDER BY:指定排序规则;
窗口框架:明确累计范围(默认为首行至当前行)。
熟练掌握上述方法后,各类累计计算需求均可迎刃而解。今后面对累加问题时,不妨首先考虑窗口函数这一高效而强大的工具。
来源:小程序app开发|ui设计|软件外包|IT技术服务公司-木风未来科技-成都木风未来科技有限公司