当你使用窗口函数时,最重要(也是最容易被误解)的细节并不是函数本身,而是窗口帧(window frame) 。两个查询可能看起来完全相同,返回相同数量的行,但仅仅因为使用了 ROWS 而不是 RANGE,就会计算出不同的结果。
在本指南中,我们将建立对窗口帧的直觉理解,比较 ROWS 和 RANGE 的差异,并演示可以在实际分析工作中重复使用的实用模式。读完本文后,你将能够正确选择合适的窗口帧,而不是靠猜测。

图示说明:窗口函数的执行流程 - 对行进行排序后,定义窗口帧(ROWS 按行计数,RANGE 按相等的 ORDER BY 值分组),最后计算窗口函数
核心概念:窗口帧是你计算的"切片"
每个窗口函数都有三个任务:
- 分区(Partition) :使用
PARTITION BY对数据进行分区(可选) - 排序(Order) :使用
ORDER BY对分区内的行进行排序 - 定义帧(Frame) :使用
ROWS或RANGE定义这些有序行的子集
如果省略窗口帧定义,许多数据库会假定一个默认值,例如:
sql
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
这是一个基于范围的 窗口帧。它并不总是意味着"到当前行为止"。相反,它意味着"到所有与当前行的 ORDER BY 值相同的行为止"。这是产生微妙错误的根源。
快速理解
ROWS计算有序集合中的物理行数。它总是逐行移动。RANGE按相等的 ORDER BY 值对行进行分组,并按值移动。
如果你的排序列有重复值,ROWS 和 RANGE 可能会产生不同的结果。
交互式示例 1:有重复值的累计求和
让我们创建一个小型销售表,其中多个交易共享相同的日期。然后我们将使用两种窗口帧计算累计总和。

图示说明:RANGE 在重复日期时会"跳跃"(将重复值分组,单次跳跃),而 ROWS 逐行处理(单独处理重复值)
注意:原文中这是一个可交互的 SQL 编辑器,读者可以修改并运行代码。以下是完整的 SQL 代码示例:
sql
SELECT
sale_date,
amount,
SUM(amount) OVER (
ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_rows,
SUM(amount) OVER (
ORDER BY sale_date
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_range
FROM sales_wf_rows_range_1
ORDER BY sale_date, amount;
查询结果示例:
假设 sales_wf_rows_range_1 表包含以下数据:
| sale_date | amount |
|---|---|
| 2026-02-01 | 100 |
| 2026-02-01 | 150 |
| 2026-02-03 | 200 |
运行上述查询后的结果:
| sale_date | amount | running_rows | running_range |
|---|---|---|---|
| 2026-02-01 | 100 | 100 | 250 |
| 2026-02-01 | 150 | 250 | 250 |
| 2026-02-03 | 200 | 450 | 450 |
需要注意的关键点:
- 在
2026-02-01,RANGE一次性包含了该日期的两行 ,所以running_range出现了"跳跃"。 ROWS每次增加一行,所以累计总和更平滑地增长。
如果你想计算交易级别 的累计总和,ROWS 是正确的选择。如果你想计算日期级别 的累计总和(同一日期的所有销售额一起计算),RANGE 更合适。
何时应该使用 RANGE?
当你的"当前行"应该包含所有具有相同 ORDER BY 值的行 时,RANGE 是完美的选择。常见用例包括:
- 多个交易共享相同
order_date的每日总计 - 多个事件具有相同时间戳的价格变化
- 排名和百分位数,其中并列应被视为单个步骤
注意:RANGE 并非在所有地方都受支持
某些数据库将 RANGE 限制为数值或日期/时间戳列。其他数据库根本不支持带有 INTERVAL 的 RANGE。如有疑问,请测试你的特定引擎,或使用具有唯一排序列的 ROWS。
交互式示例 2:最近 3 行的移动平均
这是一个经典的滚动指标:当前行加上前两行的平均值。这里 ROWS 是唯一合理的选择,因为我们明确想要三行,而不是三个值。
注意:原文中这是一个可交互的 SQL 编辑器。以下是完整的 SQL 代码示例:
sql
SELECT
day,
visitors,
ROUND(AVG(visitors) OVER (
ORDER BY day
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
), 2) AS avg_last_3_rows
FROM daily_traffic
ORDER BY day;
查询结果示例:
假设 daily_traffic 表包含以下数据:
| day | visitors |
|---|---|
| 2026-02-01 | 100 |
| 2026-02-02 | 150 |
| 2026-02-03 | 200 |
| 2026-02-04 | 180 |
运行上述查询后的结果:
| day | visitors | avg_last_3_rows |
|---|---|---|
| 2026-02-01 | 100 | 100.00 |
| 2026-02-02 | 150 | 125.00 |
| 2026-02-03 | 200 | 150.00 |
| 2026-02-04 | 180 | 176.67 |
如果你在这里使用 RANGE,并且两天共享相同的值(例如,两行具有相同的 day),窗口帧将扩展以包含所有并列的行,这会破坏"最近 3 行"的要求。
模式指南:选择正确的窗口帧
使用此检查清单快速选择窗口帧:
使用 ROWS 的情况:
- 你想要严格的行数(最近 3 行、最近 10 行等)
- 你的排序列有重复值,但你仍然想要逐行移动
- 你正在进行行级计算,如按交易的累计总和
使用 RANGE 的情况:
- 你希望共享相同 ORDER BY 值的所有行一起包含
- 你正在按时间段(每日、每周、每月)计算累积指标
- 你希望并列作为一组移动
常见陷阱:"为什么我的累计总和在跳跃?"
如果你在重复的 ORDER BY 值处看到大幅跳跃,你可能无意中使用了 RANGE。许多 SQL 引擎在你指定 ORDER BY 而不指定窗口帧时默认使用 RANGE。
明确修复它:
sql
SUM(amount) OVER (
ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
这一个单词------ROWS------可以消除数小时的困惑。
窗口帧的最佳实践
-
始终明确声明窗口帧,用于分析查询。这使意图清晰,避免引擎默认值。
-
使排序列具有确定性。如果两行可以共享相同的值,当你需要稳定排序时,添加一个次要的决胜列(如 id)。
-
将窗口帧与业务逻辑对齐。"最近 7 天"是基于时间的窗口帧;"最近 7 行"不是。
-
在边缘情况下测试。包括重复值和缺失日期,以确保获得预期的行为。
-
注释你的意图。未来的你(和你的队友)会感谢你。
结论
ROWS 和 RANGE 很微妙,但一旦你理解了差异,你将解锁更精确的分析能力。将 ROWS 视为物理行计数器,将 RANGE 视为基于值的分组。如果你想要逐行移动,使用 ROWS。如果你想要并列一起移动,使用 RANGE。
作为最后的检查,逐行阅读输出并问自己:我想计算行还是值? 回答这个问题,你的窗口帧选择就变得显而易见了。
相关文章推荐
-
在 SQL 中计算百分位数和中位数:AVG 告诉你平均值,但中位数和百分位数呢?了解如何使用窗口函数和巧妙技巧在 SQL 中计算这些基本统计数据。
-
使用 SQL 对数据进行排名:RANK、DENSE_RANK 和 ROW_NUMBER 详解:构建排行榜、查找顶级表现者或分页结果?掌握三个 SQL 排名函数,准确了解何时使用每一个。
-
精通 SQL LEAD 和 LAG 函数进行行比较:需要将一行与其前一行或后一行进行比较?了解 SQL 的 LEAD 和 LAG 窗口函数如何让你访问相邻行,而无需复杂的自连接。
说明:本文内容转载自 SQL Boy 网站,原文包含交互式 SQL 编辑器,读者可以在原网站上实时修改和运行代码。本文已将交互式内容转换为静态代码示例和结果表格,以适应 CSDN 平台。完整的交互式体验请访问原文链接。
本文转载自 www.hisqlboy.com
原文标题:SQL Window Frames: ROWS vs RANGE
原文链接:https://www.hisqlboy.com/blog/sql-window-frames-rows-vs-range
原作者:SQL Boy Team
转载日期:2026-02-06