ORDER BY 子查询和 ROWNUM 是否违背了关系哲学?
ORDER BY 子查询和 ROWNUM 是否违背了关系哲学?
在执行 ORDER BY
之前,Oracle的 ROWNUM
被应用。为了将 ROWNUM
按照排序列放置,所有文档和文本都提出了以下子查询。
select * from ( select * from table order by price ) where rownum <= 7
这让我感到困惑。按照我的理解,输入到 FROM
的表是关系型的,因此不保留任何顺序,这意味着子查询中的顺序在被 FROM
看到时不受尊重。
我记不清具体的场景了,但有关“ ORDER BY
在外部查询中没有效果”的事实我读过不止一次。示例包括内联子查询、 INSERT
的子查询、PARTITION子句的 ORDER BY
等。例如,在
OVER(PARTITION BY name ORDER BY salary)
在外部查询中不会尊重薪水顺序,如果我们希望在外部查询输出时对薪水进行排序,则需要在外部查询中添加另一个 ORDER BY
。
大家对于为什么在此处不尊重关系属性且顺序被存储在子查询中有什么见解吗?
毫不意外,Oracle将此视为一个特例。您可以从执行计划中看到这一点。使用有时会出现的天真(不正确/不确定)版本的限制时,您会得到SORT ORDER BY
和COUNT STOPKEY
操作:
select * from my_table where rownum <= 7 order by price; -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 13 | 3 (34)| 00:00:01 | | 1 | SORT ORDER BY | | 1 | 13 | 3 (34)| 00:00:01 | |* 2 | COUNT STOPKEY | | | | | | | 3 | TABLE ACCESS FULL| MY_TABLE | 1 | 13 | 2 (0)| 00:00:01 | -------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(ROWNUM<=7)
如果只使用有序子查询而没有限制,则仅会得到SORT ORDER BY
操作:
select * from ( select * from my_table order by price ); ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 13 | 3 (34)| 00:00:01 | | 1 | SORT ORDER BY | | 1 | 13 | 3 (34)| 00:00:01 | | 2 | TABLE ACCESS FULL| MY_TABLE | 1 | 13 | 2 (0)| 00:00:01 | -------------------------------------------------------------------------------
使用通常的子查询/ROWNUM
构造会得到不同的结果,
select * from ( select * from my_table order by price ) where rownum <= 7; ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 13 | 3 (34)| 00:00:01 | |* 1 | COUNT STOPKEY | | | | | | | 2 | VIEW | | 1 | 13 | 3 (34)| 00:00:01 | |* 3 | SORT ORDER BY STOPKEY| | 1 | 13 | 3 (34)| 00:00:01 | | 4 | TABLE ACCESS FULL | MY_TABLE | 1 | 13 | 2 (0)| 00:00:01 | ------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter(ROWNUM<=7) 3 - filter(ROWNUM<=7)
外部查询仍然存在COUNT STOPKEY
操作,但内部查询(内联视图或派生表)现在具有SORT ORDER BY STOPKEY
而不是简单的SORT ORDER BY
。所有这些都被隐藏在内部,所以我在推测,但看起来停止键即行号限制被推入子查询处理中,因此实际上子查询可能仅结束为七行 - 虽然计划的ROWS
值并不反映这一点(但使用不同的限制会得到相同的计划),仍然感觉需要分别应用COUNT STOPKEY
操作。
当谈到“使用ROWNUM的Top-N查询处理”时,Tom Kyte在Oracle杂志上撰写了一篇相似的文章(重点添加):
有两种方法可以解决这个问题:
- 让客户端应用程序运行该查询并仅获取前N行。
- 将该查询用作内联视图,并使用ROWNUM限制结果,例如SELECT * FROM(your_query_here)WHERE ROWNUM <=N。第二种方法比第一种方法更为优越,原因有两个。较小的原因是客户端需要的工作量较少,因为数据库负责限制结果集。较重要的原因是数据库可以执行特殊处理,仅提供前N行。使用Top-N查询意味着您为数据库提供了额外的信息。您告诉它:“我只对获取N行感兴趣。我不会考虑其他行”。现在,如果您考虑排序-排序的工作方式以及服务器需要执行的操作,情况就不那么令人震惊了。
然后继续概述了它实际上正在做的事情,比我更有权威性。
有趣的是,我认为最终结果集的顺序实际上不能保证;它似乎总是有效,但可能应该在外部查询中也加入ORDER BY
以使其完整。看起来顺序并没有真正存储在子查询中,它只是以这种方式产生。(我非常怀疑这将永远不会改变,因为它会破坏太多东西;最终看起来与表集合表达式相似,后者似乎总是保留其顺序-打破这个顺序将会停止dbms_xplan
工作。我确定还有其他例子。)
仅作比较,这就是等效的 ROW_NUMBER()
做的事情:
select * from ( select ROW_NUMBER() OVER (ORDER BY price) rn, my_table.* from my_table ) t where rn <= 7; ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 52 | 4 (25)| 00:00:01 | |* 1 | VIEW | | 2 | 52 | 4 (25)| 00:00:01 | |* 2 | WINDOW SORT PUSHED RANK| | 2 | 26 | 4 (25)| 00:00:01 | | 3 | TABLE ACCESS FULL | MY_TABLE | 2 | 26 | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("RN"<=7) 2 - filter(ROW_NUMBER() OVER ( ORDER BY "PRICE")<=7)
在这个上下文中,ORDER BY实际上是Oracle的专有语法,用于在逻辑上无序的行集上生成一个“有序”的行号。在我看来,这是一个设计不良的特性,但相当于ISO标准的SQL ROW_NUMBER()函数(在Oracle中也有效),可能会更清楚地说明正在发生的事情:
select * from ( select ROW_NUMBER() OVER (ORDER BY price) rn, * from table ) t where rn <= 7;
在这个例子中,ORDER BY更适合所属的位置:作为派生行号属性规范的一部分。这比Oracle的版本更强大,因为您可以指定几个不同的排序,定义同一结果中的不同行号。此查询返回的行的实际排序未定义。我相信在您的Oracle特定版本的查询中也是如此,因为在使用ORDER BY的方式时,不保证排序。
值得记住的是,Oracle不是关系型数据库管理系统。与其他SQL DBMS一样,Oracle在某些根本上离开了关系模型。隐式排序和DISTINCT等特性正是因为SQL数据模型的非关系性质以及解决带有重复行的无键表而存在的需求。