在 OLAP(联机分析处理)场景或明细数据驱动的应用中,SQL Server 提供的**窗口函数(Window Functions)**是一种高效、表达能力强的 SQL 特性。特别是通过 OVER()
子句中的 ROWS BETWEEN
、RANGE BETWEEN
等关键字,开发者可以对“窗口行数”进行精确控制,从而实现滑动聚合、对比分析、排名处理等复杂逻辑。
本文将系统梳理 SQL Server 窗口函数中关于**“窗口行数控制”**的核心机制与用法,并结合具体业务场景进行分析。
一、窗口函数基础结构
窗口函数的基本结构如下:
函数名() OVER (
[PARTITION BY 分组列]
[ORDER BY 排序列]
[ROWS | RANGE 窗口范围]
)
其中:
关键字/语法 | 说明 |
---|---|
OVER() |
表示开启窗口函数语义 |
PARTITION BY |
按某字段分组计算窗口(类似于 GROUP BY,但保留明细) |
ORDER BY |
定义窗口内的顺序,许多函数(如排名函数)依赖它 |
ROWS BETWEEN ... |
以“物理行数”为单位定义窗口范围 |
RANGE BETWEEN ... |
以“排序值范围”为单位定义窗口范围(注意限制) |
二、核心窗口函数分类
类别 | 典型函数 | 用途 |
---|---|---|
排名类 | ROW_NUMBER() 、RANK() 、DENSE_RANK() |
获取排序位置 |
聚合类 | SUM() 、AVG() 、MIN() 、MAX() 、COUNT() |
对窗口内行执行聚合 |
偏移类 | LAG() 、LEAD() |
获取前/后某行的值 |
分布类 | NTILE(n) |
把排序后结果平均分为 n 组 |
首尾类 | FIRST_VALUE() 、LAST_VALUE() |
获取窗口内首尾元素 |
三、窗口范围关键字详解
1. ROWS BETWEEN
—— 精确控制“行数”范围
用于物理行级别的窗口控制。
常见语法:
ROWS BETWEEN <x> PRECEDING AND <y> FOLLOWING
范围表达式 | 含义 |
---|---|
UNBOUNDED PRECEDING |
窗口起点为分区首行 |
UNBOUNDED FOLLOWING |
窗口终点为分区末行 |
CURRENT ROW |
当前行 |
<n> PRECEDING |
当前行之前第 n 行 |
<n> FOLLOWING |
当前行之后第 n 行 |
示例:最近 3 天内销售额统计
SELECT
StoreID,
OrderDate,
SUM(SalesAmount) OVER (
PARTITION BY StoreID
ORDER BY OrderDate
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS RollingSales
FROM Sales;
解释:对每家门店,从当前行往前取 2 行(共 3 天)累计销售额。
2. RANGE BETWEEN
—— 控制“值范围”
用于排序键的值范围(只支持数值或日期等可比较的数据类型),适合处理“时间点相等”或“分段累计”的需求。
示例:
SELECT
OrderID,
OrderDate,
SUM(SalesAmount) OVER (
ORDER BY OrderDate
RANGE BETWEEN INTERVAL 7 DAY PRECEDING AND CURRENT ROW
) AS WeeklySales
FROM Sales;
注意:SQL Server 不支持带间隔单位的 RANGE BETWEEN INTERVAL
写法,只有在排序列值重复时有意义,大部分场景建议使用 ROWS
替代。
3. PARTITION BY
:分组窗口范围
将结果集拆分为多个分区,在每个分区内独立进行窗口计算。
示例:每个用户最近 3 次登录时间:
SELECT
UserID,
LoginTime,
ROW_NUMBER() OVER (
PARTITION BY UserID
ORDER BY LoginTime DESC
) AS RecentLoginRank
FROM Logins;
4. ORDER BY
:定义窗口顺序
窗口函数中,ORDER BY
不仅决定了排序顺序,还直接影响窗口范围的定义、偏移函数的目标行。
四、典型业务场景示例
1. 移动平均值 / 滚动统计
SELECT
OrderDate,
AVG(SalesAmount) OVER (
ORDER BY OrderDate
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS 7DayAvg
FROM Sales;
说明:用于生成时间序列 KPI,如活跃用户、日均订单、周均销售。
2. 分组内 Top-N 查询
WITH RankedSales AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY StoreID ORDER BY SalesAmount DESC) AS rk
FROM Sales
)
SELECT * FROM RankedSales WHERE rk <= 3;
说明:每家门店销售额排名前 3 的订单。
3. 同环比分析(同比、环比)
SELECT
OrderDate,
SalesAmount,
LAG(SalesAmount, 1) OVER (ORDER BY OrderDate) AS PrevDaySales,
SalesAmount - LAG(SalesAmount, 1) OVER (ORDER BY OrderDate) AS Diff
FROM Sales;
说明:可用于订单增长、访问流量增幅等同比分析。
4. 取每组的第一条/最后一条记录
SELECT *,
FIRST_VALUE(OrderDate) OVER (
PARTITION BY CustomerID ORDER BY OrderDate
) AS FirstOrderDate
FROM Orders;
五、性能优化建议
优化项 | 建议 |
---|---|
排序列索引 | 窗口函数大多依赖 ORDER BY ,请确保排序列建索引 |
分区字段选择 | 分区过多会影响性能,适当合并分区或避免过细粒度 |
大数据量 | 可使用中间物化表 + 分页处理 |
滑动窗口 | 推荐 ROWS ,其物理执行计划更稳定、可控 |
六、总结:它解决了什么问题?
问题 | 窗口函数解决方案 |
---|---|
明细数据无法聚合 | 聚合类窗口函数在保留明细的同时支持分组汇总 |
不方便比较上下行 | 偏移类函数(如 LAG/LEAD )替代子查询 |
分组后无法保留明细 | PARTITION BY 保留分组上下文 |
复杂排序排名 | 排名函数简化业务逻辑 |
移动统计性能差 | ROWS BETWEEN 替代子查询或游标,提升可维护性与效率 |
附:常用窗口函数参考表
函数名 | 用途 | 是否支持 OVER() |
---|---|---|
ROW_NUMBER() |
排名 | ✅ |
RANK() / DENSE_RANK() |
去重排名 | ✅ |
SUM() / AVG() |
累计 | ✅ |
LAG() / LEAD() |
上/下行取值 | ✅ |
NTILE(n) |
分组编号 | ✅ |
FIRST_VALUE() / LAST_VALUE() |
取窗口边界值 | ✅ |