Presto窗口函数OVER子句中ROWS BETWEEN与RANGE BETWEEN的差异
本文针对旧版本PrestoSQL(尤其是亚马逊Athena引擎1基于的0.172版本,以及引擎2基于的0.217版本),这些版本的窗口函数ROWS/RANGE相关问题已在Trino(原PrestoSQL)346及以后版本修复。
核心问题解答
1. ROWS BETWEEN与RANGE BETWEEN是同义词吗?有何核心差异?
在PrestoSQL 0.172/Athena引擎1版本中,当仅使用UNBOUNDED和CURRENT ROW作为边界时,二者的表现看起来完全一致——返回结果没有区别。但它们本质是不同的概念:
ROWS是基于物理行的数量来划定窗口范围RANGE是基于排序键的数值范围来划定窗口范围
只不过在这个旧版本中,RANGE的功能被大幅阉割了,仅支持UNBOUNDED和CURRENT ROW,导致它的行为和ROWS在这些场景下完全重合。
2. 为什么ROWS BETWEEN支持更多选项?
这是旧版本PrestoSQL的功能限制问题。在这个版本中,RANGE的实现不支持基于数值偏移的边界(比如1 PRECEDING、1 FOLLOWING),仅允许UNBOUNDED PRECEDING、CURRENT ROW、UNBOUNDED FOLLOWING这几个选项;而ROWS的实现是完整的,支持任意数值的前后偏移(比如5 PRECEDING、2 FOLLOWING),以及各种组合。
你可以试试这段代码,RANGE会报错,但ROWS能正常执行:
-- 报错的RANGE语句 use tpch.tiny; select custkey, orderdate, array_agg(orderdate) over ( partition by custkey order by orderdate asc range between 1 preceding and 1 following ) previous_orders from orders where custkey in (419, 320) and orderdate < date('1996-01-01') order by custkey, orderdate asc;
报错信息:
ERROR: Window frame RANGE PRECEDING is only supported with UNBOUNDED
换成ROWS则能正常返回结果:
-- 正常执行的ROWS语句 use tpch.tiny; select custkey, orderdate, array_agg(orderdate) over ( partition by custkey order by orderdate asc rows between 1 preceding and 1 following ) previous_orders from orders where custkey in (419, 320) and orderdate < date('1996-01-01') order by custkey, orderdate asc;
3. 是否存在相同参数下,二者返回不同结果的场景?
在这个旧版本中,当仅使用UNBOUNDED和CURRENT ROW时,二者返回的结果完全一致。比如:
range between unbounded preceding and current row和rows between unbounded preceding and current row返回的聚合结果完全相同range between current row and unbounded following和对应的ROWS语句结果也一致
但如果是在支持完整RANGE功能的新版本Trino中,当排序键存在重复值时,二者会有明显差异:RANGE会把所有排序键值等于当前行的行都包含进窗口,而ROWS只按物理行数量计算。不过在PrestoSQL 0.172/Athena引擎1中,因为RANGE不支持偏移量,所以不存在这种场景。
4. 仅使用UNBOUNDED/CURRENT ROW时,是否需要优先选择RANGE或ROWS?
在这个旧版本中,二者行为完全一致,性能上也没有明显差异,所以选哪个都可以。不过考虑到ROWS支持更多场景,如果你以后需要扩展窗口范围(比如添加偏移量),直接修改ROWS的参数即可,不需要替换关键字,所以优先选ROWS会更灵活。
5. 既然ROWS支持更多选项,为何官方文档完全未提及?
这确实是旧版本PrestoSQL的文档疏漏。从代码来看,ROWS的解析器和测试用例都是存在的,但文档没有同步更新。这种情况在开源项目的早期版本中偶尔会出现——功能先实现了,但文档没跟上。
补充测试示例汇总
RANGE可用场景(仅支持UNBOUNDED/CURRENT ROW)
range between unbounded preceding and current row
返回当前行及之前所有行的聚合:
custkey | orderdate | previous_orders ---------+------------+-------------------------------------------------------------------------- 320 | 1992-07-10 | [1992-07-10] 320 | 1992-07-30 | [1992-07-10, 1992-07-30] 320 | 1994-07-08 | [1992-07-10, 1992-07-30, 1994-07-08] ...
range between current row and unbounded following
返回当前行及之后所有行的聚合:
custkey | orderdate | previous_orders ---------+------------+-------------------------------------------------------------------------- 320 | 1992-07-10 | [1992-07-10, 1992-07-30, 1994-07-08, 1994-08-04, 1994-09-18, 1994-10-12] 320 | 1992-07-30 | [1992-07-30, 1994-07-08, 1994-08-04, 1994-09-18, 1994-10-12] ...
range between unbounded preceding and unbounded following
返回分区内所有行的聚合:
custkey | orderdate | previous_orders ---------+------------+-------------------------------------------------------------------------- 320 | 1992-07-10 | [1992-07-10, 1992-07-30, 1994-07-08, 1994-08-04, 1994-09-18, 1994-10-12] 320 | 1992-07-30 | [1992-07-10, 1992-07-30, 1994-07-08, 1994-08-04, 1994-09-18, 1994-10-12] ...
ROWS扩展场景(支持偏移量)
rows between 1 preceding and 1 following
返回当前行、前1行、后1行的聚合:
custkey | orderdate | previous_orders ---------+------------+-------------------------------------- 320 | 1992-07-10 | [1992-07-10, 1992-07-30] 320 | 1992-07-30 | [1992-07-10, 1992-07-30, 1994-07-08] 320 | 1994-07-08 | [1992-07-30, 1994-07-08, 1994-08-04] ...
rows between current row and 1 following
返回当前行和后1行的聚合:
custkey | orderdate | previous_orders ---------+------------+-------------------------- 320 | 1992-07-10 | [1992-07-10, 1992-07-30] 320 | 1992-07-30 | [1992-07-30, 1994-07-08] ...
rows between 5 preceding and 2 preceding
返回当前行前5行到前2行的聚合(不足则返回NULL):
custkey | orderdate | previous_orders ---------+------------+-------------------------------------------------- 320 | 1992-07-10 | NULL 320 | 1992-07-30 | NULL 320 | 1994-07-08 | [1992-07-10] ...
内容的提问来源于stack exchange,提问作者mbafford




