RANGE BETWEEN 遇到相同排序值会将所有同值行全纳入窗口,因其按逻辑值范围而非物理行序划分窗口;ROWS BETWEEN 则严格按物理行号取当前行及指定偏移行,不受重复值影响。

RANGE BETWEEN遇到相同排序值会把它们全包进窗口
当使用 RANGE BETWEEN 且排序列存在重复值时,窗口不会只取“物理上相邻的几行”,而是按**排序表达式的实际值**扩展——所有与当前行在 ORDER BY 列上值相等的行,无论物理位置如何,都会被纳入同一窗口。这是它和 ROWS BETWEEN 的根本区别。
- 比如
ORDER BY score DESC,当前行score = 95,而分区中还有另外两行也是95,那这三行在RANGE窗口中被视为“同一位置”,CURRENT ROW实际代表这整个值组 -
RANGE BETWEEN 1 PRECEDING AND CURRENT ROW中的1 PRECEDING指的是排序值比当前小 1 的所有行(如当前是 95,则包含所有 94 的行),不是“上一行” - 如果排序列是字符串或日期,
1 PRECEDING要求类型支持算术运算(如DATE可以,VARCHAR通常报错) - MySQL 8.0+ 和 PostgreSQL 支持
RANGE,但 SQLite 和早期 Hive 不支持;Spark SQL 默认用RANGE,除非显式写ROWS
RANGE BETWEEN CURRENT ROW默认包含所有同值行
没写 BETWEEN 子句但用了 ORDER BY 时,隐含的默认窗口是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这里的 CURRENT ROW 不是单行,而是“当前排序值的所有行”。所以 SUM(sales) OVER (ORDER BY dept) 在 dept 有重复时,每个同部门的行得到的累计和是一样的——因为它们共享同一个“当前值边界”。
- 想避免这种行为?必须显式改用
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -
RANK()、DENSE_RANK()这类排名函数内部用的就是RANGE语义,所以并列时排名一致 - 用
LAG(sales, 1)时不受RANGE影响,它只看物理行序,和窗口帧无关
ORDER BY列类型不支持算术运算会导致RANGE报错
RANGE 要求排序表达式能参与数值比较和加减,否则直接报错。常见踩坑点:
-
ORDER BY name(字符串) +RANGE BETWEEN 1 PRECEDING AND CURRENT ROW→ 报错,因为name - 1无意义 -
ORDER BY created_at(TIMESTAMP)可以,1 PRECEDING表示 1 秒前,但要注意精度(毫秒级需写INTERVAL '1' MILLISECOND) -
ORDER BY CAST(score AS DECIMAL(5,2))安全;但ORDER BY score若底层是FLOAT,可能因精度导致“本该相等的值被判定为不同”,破坏RANGE分组 - Hive 中
RANGE对STRING排序列仅支持UNBOUNDED和CURRENT ROW,其他偏移量会被忽略或报错
用RANGE还是ROWS,关键看业务是否依赖“值语义”
要算“截至当前分数的累计人数”,用 RANGE;要算“截至当前行(不管分数是否重复)的累计人数”,用 ROWS。二者结果差异往往就出现在重复值附近。
- 移动平均场景:按时间滚动 7 天 → 用
RANGE BETWEEN INTERVAL '6' DAY PRECEDING AND CURRENT ROW(PostgreSQL/MySQL) - 排行榜“显示前 10 名,含并列” →
DENSE_RANK() OVER (ORDER BY score DESC)内部依赖RANGE语义 - 分页取第 11–20 行 → 必须用
ROW_NUMBER() OVER (...) BETWEEN 11 AND 20,这里ROWS语义才保证物理行数可控 - 在
PARTITION BY category ORDER BY price下做“价格区间内销量占比”,RANGE才能自然聚合相同价格点
真正容易被忽略的,是 RANGE 在隐式窗口中的默认行为——你以为它只算到当前行,其实它悄悄把所有同值行都拉进来了。调试时别只看 EXPLAIN,一定要查实际输出行的 ORDER BY 列值和聚合结果是否对得上。

















