SQL 窗口帧:ROWS vs RANGE 深度解析

当你使用窗口函数时,最重要(也是最容易被误解)的细节并不是函数本身,而是窗口帧(window frame) 。两个查询可能看起来完全相同,返回相同数量的行,但仅仅因为使用了 ROWS 而不是 RANGE,就会计算出不同的结果。

在本指南中,我们将建立对窗口帧的直觉理解,比较 ROWSRANGE 的差异,并演示可以在实际分析工作中重复使用的实用模式。读完本文后,你将能够正确选择合适的窗口帧,而不是靠猜测。

图示说明:窗口函数的执行流程 - 对行进行排序后,定义窗口帧(ROWS 按行计数,RANGE 按相等的 ORDER BY 值分组),最后计算窗口函数

核心概念:窗口帧是你计算的"切片"

每个窗口函数都有三个任务:

  1. 分区(Partition) :使用 PARTITION BY 对数据进行分区(可选)
  2. 排序(Order) :使用 ORDER BY 对分区内的行进行排序
  3. 定义帧(Frame) :使用 ROWSRANGE 定义这些有序行的子集

如果省略窗口帧定义,许多数据库会假定一个默认值,例如:

sql 复制代码
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

这是一个基于范围的 窗口帧。它并不总是意味着"到当前行为止"。相反,它意味着"到所有与当前行的 ORDER BY 值相同的行为止"。这是产生微妙错误的根源。

快速理解

  • ROWS 计算有序集合中的物理行数。它总是逐行移动。
  • RANGE相等的 ORDER BY 值对行进行分组,并按值移动。

如果你的排序列有重复值,ROWSRANGE 可能会产生不同的结果。

交互式示例 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-01RANGE 一次性包含了该日期的两行 ,所以 running_range 出现了"跳跃"。
  • ROWS 每次增加一行,所以累计总和更平滑地增长。

如果你想计算交易级别 的累计总和,ROWS 是正确的选择。如果你想计算日期级别 的累计总和(同一日期的所有销售额一起计算),RANGE 更合适。

何时应该使用 RANGE?

当你的"当前行"应该包含所有具有相同 ORDER BY 值的行 时,RANGE 是完美的选择。常见用例包括:

  • 多个交易共享相同 order_date 的每日总计
  • 多个事件具有相同时间戳的价格变化
  • 排名和百分位数,其中并列应被视为单个步骤

注意:RANGE 并非在所有地方都受支持

某些数据库将 RANGE 限制为数值或日期/时间戳列。其他数据库根本不支持带有 INTERVALRANGE。如有疑问,请测试你的特定引擎,或使用具有唯一排序列的 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------可以消除数小时的困惑。

窗口帧的最佳实践

  1. 始终明确声明窗口帧,用于分析查询。这使意图清晰,避免引擎默认值。

  2. 使排序列具有确定性。如果两行可以共享相同的值,当你需要稳定排序时,添加一个次要的决胜列(如 id)。

  3. 将窗口帧与业务逻辑对齐。"最近 7 天"是基于时间的窗口帧;"最近 7 行"不是。

  4. 在边缘情况下测试。包括重复值和缺失日期,以确保获得预期的行为。

  5. 注释你的意图。未来的你(和你的队友)会感谢你。

结论

ROWSRANGE 很微妙,但一旦你理解了差异,你将解锁更精确的分析能力。将 ROWS 视为物理行计数器,将 RANGE 视为基于值的分组。如果你想要逐行移动,使用 ROWS。如果你想要并列一起移动,使用 RANGE

作为最后的检查,逐行阅读输出并问自己:我想计算行还是值? 回答这个问题,你的窗口帧选择就变得显而易见了。


相关文章推荐


说明:本文内容转载自 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

著作权归原作者所有。本文仅用于学习交流,非商业用途。

相关推荐
小高不会迪斯科5 小时前
CMU 15445学习心得(二) 内存管理及数据移动--数据库系统如何玩转内存
数据库·oracle
e***8905 小时前
MySQL 8.0版本JDBC驱动Jar包
数据库·mysql·jar
l1t5 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
失忆爆表症7 小时前
03_数据库配置指南:PostgreSQL 17 + pgvector 向量存储
数据库·postgresql
AI_56787 小时前
Excel数据透视表提速:Power Query预处理百万数据
数据库·excel
Gauss松鼠会8 小时前
【GaussDB】GaussDB数据库开发设计之JDBC高可用性
数据库·数据库开发·gaussdb
+VX:Fegn08958 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
识君啊9 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
一个天蝎座 白勺 程序猿9 小时前
破译JSON密码:KingbaseES全场景JSON数据处理实战指南
数据库·sql·json·kingbasees·金仓数据库