SQL Server 中的窗口函数(Window Functions)是一种强大的查询工具,它允许我们在查询结果集中对数据进行分区、排序和计算,而不会改变结果集的行数。窗口函数通过 OVER 子句定义一个"窗口",在该窗口内对数据进行操作。这使得我们能够轻松实现排名、聚合、移动平均等复杂计算,而无需使用子查询或自连接。
窗口函数的基本语法是:
sql
窗口函数 OVER (
[PARTITION BY 分区列]
[ORDER BY 排序列 [ASC|DESC]]
[ROWS | RANGE BETWEEN 边界1 AND 边界2]
)
- PARTITION BY:将结果集分成多个分区,每个分区独立计算。
- ORDER BY:定义窗口内的排序顺序。
- ROWS/RANGE:可选,定义窗口帧(frame),指定计算的行范围。
窗口函数主要分为三类:排名函数、聚合函数和分析函数。下面我们逐一介绍每个函数的具体用法和使用场景。为了说明,我们假设有一个名为 Sales
的表,结构如下:
OrderID | Product | Quantity | Price | OrderDate |
---|---|---|---|---|
1 | A | 10 | 100 | 2023-01-01 |
2 | B | 20 | 200 | 2023-01-02 |
3 | A | 15 | 150 | 2023-01-03 |
... | ... | ... | ... | ... |
窗口函数完整列表
排名函数(Ranking Functions)
- ROW_NUMBER() - 为每行分配唯一的连续整数
- RANK() - 分配排名,相同值相同排名,有间隙
- DENSE_RANK() - 分配排名,相同值相同排名,无间隙
- NTILE(n) - 将数据分成n个组
聚合函数(Aggregate Functions)
- SUM() - 计算窗口内总和
- AVG() - 计算窗口内平均值
- MIN() - 计算窗口内最小值
- MAX() - 计算窗口内最大值
- COUNT() - 计算窗口内行数
- STDEV() - 计算窗口内标准差
- STDEVP() - 计算窗口内总体标准差
- VAR() - 计算窗口内方差
- VARP() - 计算窗口内总体方差
分析函数(Analytic Functions)
- LEAD() - 访问后续行的值
- LAG() - 访问前面行的值
- FIRST_VALUE() - 获取窗口内第一个值
- LAST_VALUE() - 获取窗口内最后一个值
- NTH_VALUE() - 获取窗口内第n个值
分布函数(Distribution Functions)
- PERCENT_RANK() - 计算相对排名(0-1)
- CUME_DIST() - 计算累计分布(0-1)
- PERCENTILE_CONT() - 连续百分位数
- PERCENTILE_DISC() - 离散百分位数
每个函数详细介绍
1. ROW_NUMBER()
函数说明:为结果集中的每一行分配一个唯一的连续整数,从1开始递增。相同值的行会获得不同的行号。
语法:
sql
ROW_NUMBER() OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
参数说明:
PARTITION BY
:可选,将结果集分成多个分区,每个分区独立编号ORDER BY
:必需,定义排序顺序,决定行号的分配
示例:
sql
-- 按产品分组,按数量降序编号
SELECT
Product,
Quantity,
ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Quantity DESC) AS RowNum
FROM Sales;
-- 全局按价格降序编号
SELECT
Product,
Price,
ROW_NUMBER() OVER (ORDER BY Price DESC) AS GlobalRank
FROM Sales;
使用场景:
- 分页查询(LIMIT OFFSET)
- 删除重复记录
- 生成唯一标识符
- 数据抽样
2. RANK()
函数说明:为行分配排名,相同值的行获得相同排名,下一个排名会跳过(产生间隙)。
语法:
sql
RANK() OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
示例:
sql
-- 按价格排名,相同价格相同排名
SELECT
Product,
Price,
RANK() OVER (ORDER BY Price DESC) AS PriceRank
FROM Sales;
结果示例:
Product | Price | PriceRank
--------|-------|----------
A | 200 | 1
B | 200 | 1
C | 150 | 3 (跳过了2)
D | 100 | 4
使用场景:
- 竞赛排名
- 成绩排名
- 销售排行榜
3. DENSE_RANK()
函数说明:类似于RANK,但排名连续,没有间隙。相同值的行获得相同排名,下一个排名连续。
语法:
sql
DENSE_RANK() OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
示例:
sql
SELECT
Product,
Price,
DENSE_RANK() OVER (ORDER BY Price DESC) AS DenseRank
FROM Sales;
结果示例:
Product | Price | DenseRank
--------|-------|----------
A | 200 | 1
B | 200 | 1
C | 150 | 2 (连续,没有跳跃)
D | 100 | 3
使用场景:
- 奖牌排名(金银铜)
- 等级评定
- 需要连续排名的场景
4. NTILE(n)
函数说明:将分区内的行分成n个组,每组分配一个从1到n的数字。如果行数不能被n整除,前面的组会多一行。
语法:
sql
NTILE(组数) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
示例:
sql
-- 将数据分成4个四分位数
SELECT
Product,
Price,
NTILE(4) OVER (ORDER BY Price DESC) AS Quartile
FROM Sales;
使用场景:
- 分位数分析
- 数据分桶
- 等级划分(如A、B、C、D级)
5. SUM()
函数说明:计算窗口内列的总和,支持累计计算。
语法:
sql
SUM(列) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
[ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...]
[ROWS | RANGE BETWEEN 边界1 AND 边界2]
)
窗口帧选项:
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
- 累计到当前行ROWS BETWEEN 3 PRECEDING AND CURRENT ROW
- 当前行及前3行ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- 当前行到末尾
示例:
sql
-- 累计销售总额
SELECT
OrderDate,
Price,
SUM(Price) OVER (ORDER BY OrderDate) AS RunningTotal
FROM Sales;
-- 按产品分组的累计销售
SELECT
Product,
OrderDate,
Price,
SUM(Price) OVER (PARTITION BY Product ORDER BY OrderDate) AS ProductRunningTotal
FROM Sales;
-- 移动3天平均
SELECT
OrderDate,
Price,
AVG(Price) OVER (ORDER BY OrderDate ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS MovingAvg3
FROM Sales;
使用场景:
- 财务累计报表
- 销售趋势分析
- 移动平均计算
6. AVG()
函数说明:计算窗口内列的平均值。
语法:
sql
AVG(列) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
[ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...]
[ROWS | RANGE BETWEEN 边界1 AND 边界2]
)
示例:
sql
-- 每个产品的平均价格
SELECT
Product,
Price,
AVG(Price) OVER (PARTITION BY Product) AS AvgPrice
FROM Sales;
-- 移动平均
SELECT
OrderDate,
Price,
AVG(Price) OVER (ORDER BY OrderDate ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) AS MovingAvg5
FROM Sales;
使用场景:
- 趋势分析
- 股票技术分析
- 性能基准
7. MIN() / MAX()
函数说明:计算窗口内列的最小值/最大值。
语法:
sql
MIN(列) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
[ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...]
[ROWS | RANGE BETWEEN 边界1 AND 边界2]
)
MAX(列) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
[ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...]
[ROWS | RANGE BETWEEN 边界1 AND 边界2]
)
示例:
sql
-- 每个产品的历史最高价和最低价
SELECT
Product,
Price,
MIN(Price) OVER (PARTITION BY Product) AS MinPrice,
MAX(Price) OVER (PARTITION BY Product) AS MaxPrice
FROM Sales;
使用场景:
- 价格监控
- 极值分析
- 范围计算
8. COUNT()
函数说明:计算窗口内的行数。
语法:
sql
COUNT(*) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
[ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...]
[ROWS | RANGE BETWEEN 边界1 AND 边界2]
)
示例:
sql
-- 每个产品的订单数量
SELECT
Product,
COUNT(*) OVER (PARTITION BY Product) AS ProductOrderCount
FROM Sales;
使用场景:
- 分组统计
- 数据验证
- 频率分析
9. LEAD()
函数说明:访问当前行后n行的值。如果超出范围,返回默认值。
语法:
sql
LEAD(列, n, 默认值) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
参数说明:
列
:要访问的列n
:向后偏移的行数(默认为1)默认值
:当超出范围时返回的值(默认为NULL)
示例:
sql
-- 计算价格变化
SELECT
OrderDate,
Price,
LEAD(Price, 1, 0) OVER (ORDER BY OrderDate) AS NextPrice,
Price - LEAD(Price, 1, 0) OVER (ORDER BY OrderDate) AS PriceChange
FROM Sales;
使用场景:
- 时间序列分析
- 增长率计算
- 趋势预测
10. LAG()
函数说明:访问当前行前n行的值。如果超出范围,返回默认值。
语法:
sql
LAG(列, n, 默认值) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
示例:
sql
-- 计算环比增长率
SELECT
OrderDate,
Price,
LAG(Price, 1, Price) OVER (ORDER BY OrderDate) AS PrevPrice,
(Price - LAG(Price, 1, Price) OVER (ORDER BY OrderDate)) /
LAG(Price, 1, Price) OVER (ORDER BY OrderDate) * 100 AS GrowthRate
FROM Sales;
使用场景:
- 环比分析
- 历史比较
- 变化率计算
11. FIRST_VALUE()
函数说明:返回窗口帧中第一个值。
语法:
sql
FIRST_VALUE(列) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
[ROWS | RANGE BETWEEN 边界1 AND 边界2]
)
示例:
sql
-- 每个产品的首次销售价格
SELECT
Product,
OrderDate,
Price,
FIRST_VALUE(Price) OVER (PARTITION BY Product ORDER BY OrderDate) AS FirstPrice
FROM Sales;
使用场景:
- 基准值比较
- 初始状态记录
- 历史对比
12. LAST_VALUE()
函数说明:返回窗口帧中最后一个值。注意:默认窗口帧到当前行,通常需要调整。
语法:
sql
LAST_VALUE(列) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
[ROWS | RANGE BETWEEN 边界1 AND 边界2]
)
示例:
sql
-- 每个产品的最新价格(需要调整窗口帧)
SELECT
Product,
OrderDate,
Price,
LAST_VALUE(Price) OVER (
PARTITION BY Product
ORDER BY OrderDate
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS LastPrice
FROM Sales;
使用场景:
- 最新状态获取
- 最终值比较
- 状态跟踪
13. PERCENT_RANK()
函数说明:计算相对排名,返回0到1之间的值。第一名的值为0,最后一名的值为1。
语法:
sql
PERCENT_RANK() OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
示例:
sql
-- 价格百分位排名
SELECT
Product,
Price,
PERCENT_RANK() OVER (ORDER BY Price DESC) AS PricePercentRank
FROM Sales;
使用场景:
- 统计分布分析
- 百分位计算
- 数据标准化
14. CUME_DIST()
函数说明:计算累计分布,返回0到1之间的值。表示小于等于当前值的行数占总行数的比例。
语法:
sql
CUME_DIST() OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
示例:
sql
-- 价格累计分布
SELECT
Product,
Price,
CUME_DIST() OVER (ORDER BY Price) AS PriceCumeDist
FROM Sales;
使用场景:
- 分布分析
- 分位数计算
- 数据分布可视化
15. PERCENTILE_CONT()
函数说明:计算连续百分位数,返回插值后的值。
语法:
sql
PERCENTILE_CONT(百分位数) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
示例:
sql
-- 计算中位数(50%分位数)
SELECT
Product,
PERCENTILE_CONT(0.5) OVER (PARTITION BY Product ORDER BY Price) AS MedianPrice
FROM Sales;
使用场景:
- 统计分位数
- 数据分布分析
- 异常值检测
16. PERCENTILE_DISC()
函数说明:计算离散百分位数,返回实际存在的值。
语法:
sql
PERCENTILE_DISC(百分位数) OVER (
[PARTITION BY 分区列1, 分区列2, ...]
ORDER BY 排序列1 [ASC|DESC], 排序列2 [ASC|DESC], ...
)
示例:
sql
-- 计算离散中位数
SELECT
Product,
PERCENTILE_DISC(0.5) OVER (PARTITION BY Product ORDER BY Price) AS DiscreteMedian
FROM Sales;
使用场景:
- 离散分位数
- 实际值分析
- 数据验证
排名函数
排名函数用于为行分配排名或分组。
1. ROW_NUMBER()
用法:为每个分区内的行分配一个唯一的连续整数,从1开始,按照 ORDER BY 排序。
语法:
sql
ROW_NUMBER() OVER (PARTITION BY 分区列 ORDER BY 排序列)
例子:
sql
SELECT
Product,
Quantity,
ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Quantity DESC) AS RowNum
FROM Sales;
结果(假设数据):
- A, 15, 1
- A, 10, 2
- B, 20, 1
使用场景:分页查询、生成唯一行号、删除重复记录(结合 CTE)。
2. RANK()
用法:为行分配排名,如果值相同则排名相同,下一个排名会跳过(有间隙)。
语法:
sql
RANK() OVER (PARTITION BY 分区列 ORDER BY 排序列)
例子:
sql
SELECT
Product,
Quantity,
RANK() OVER (ORDER BY Quantity DESC) AS Rank
FROM Sales;
结果:
- B, 20, 1
- A, 15, 2
- A, 10, 3 (没有跳跃,因为没有并列)
如果有两个15,则:15排2,下一个跳到4。
使用场景:排名竞赛、识别前N名,但允许并列。
3. DENSE_RANK()
用法:类似于 RANK,但排名连续,没有间隙。
语法:
sql
DENSE_RANK() OVER (PARTITION BY 分区列 ORDER BY 排序列)
例子:同上,如果有两个15,则:15排2,下一个排3。
使用场景:需要连续排名的场景,如奖牌排名(金银铜连续)。
4. NTILE(n)
用法:将分区内的行分成 n 个组,每组分配一个从1到n的数字。
语法:
sql
NTILE(组数) OVER (PARTITION BY 分区列 ORDER BY 排序列)
例子:
sql
SELECT
Product,
Quantity,
NTILE(2) OVER (ORDER BY Quantity DESC) AS Tile
FROM Sales;
结果:分成两组,前半组1,后半组2。
使用场景:分桶分析、将数据分成等份(如分位数)。
聚合函数
聚合函数如 SUM、AVG、MIN、MAX、COUNT 可以与 OVER 结合,在窗口内计算。
1. SUM()
用法:计算窗口内列的总和。
语法:
sql
SUM(列) OVER (PARTITION BY 分区列 ORDER BY 排序列 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
例子(累计销售):
sql
SELECT
OrderDate,
Price,
SUM(Price) OVER (ORDER BY OrderDate) AS RunningTotal
FROM Sales;
结果:每行显示到当前日期的累计总价。
使用场景:运行总计、累计求和、财务报告。
2. AVG()
用法:计算平均值。
语法:类似 SUM。
例子:计算移动平均。
使用场景:趋势分析、股票移动平均。
3. MIN() / MAX()
用法:窗口内最小/最大值。
例子:查找每个产品的历史最低价。
使用场景:价格监控、极值分析。
4. COUNT()
用法:计数。
例子:计算每个分区内的行数。
使用场景:分组计数而不使用 GROUP BY。
分析函数
分析函数用于访问窗口中其他行的数据。
1. LEAD(列, n, 默认值)
用法:返回当前行后 n 行的列值。
语法:
sql
LEAD(列, n, 默认值) OVER (PARTITION BY 分区列 ORDER BY 排序列)
例子:
sql
SELECT
OrderDate,
Price,
LEAD(Price, 1, 0) OVER (ORDER BY OrderDate) AS NextPrice
FROM Sales;
结果:显示下一订单的价格。
使用场景:比较前后行、计算增长率、时间序列分析。
2. LAG(列, n, 默认值)
用法:返回当前行前 n 行的列值。
语法:类似 LEAD。
例子:计算价格变化。
使用场景:与 LEAD 类似,用于历史比较。
3. FIRST_VALUE(列)
用法:返回窗口帧中第一个值。
语法:
sql
FIRST_VALUE(列) OVER (PARTITION BY 分区列 ORDER BY 排序列 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
例子:每个产品的首次销售价格。
使用场景:基准值比较。
4. LAST_VALUE(列)
用法:返回窗口帧中最后一个值。注意:默认帧到当前行,需要调整。
例子:每个产品的最新价格。
使用场景:最新状态获取。
其他函数
1. PERCENT_RANK()
用法:计算相对排名(0到1)。
语法:OVER (ORDER BY 排序列)
例子:百分位排名。
使用场景:统计分布。
2. CUME_DIST()
用法:累计分布(0到1)。
使用场景:分布分析。
结论
SQL Server 窗口函数极大地简化了复杂查询,提高了效率。通过掌握这些函数,你可以处理各种数据分析任务。建议在实际项目中练习,以加深理解。注意:窗口函数不支持在 WHERE 或 GROUP BY 中使用,通常在 SELECT 或 ORDER BY 中。