ORDER BY 子查询和 ROWNUM 违背关系哲学?

ORDER BY subquery and ROWNUM goes against relational philosophy?

Oracle 的 ROWNUMORDER BY 之前应用。为了ROWNUM按照一个排好序的列,在所有的文档和文本中都提出了下面的子查询。

select *
from (
  select *
  from table
  order by price
)
where rownum <= 7

这让我很烦恼。据我了解,table 输入到 FROM 是相关的,因此没有存储任何顺序,这意味着当 FROM.

看到子查询中的顺序时不遵守

我不记得确切的场景,但是“ORDER BY 对外部查询没有影响”这一事实我已经读过不止一次。示例是内联子查询、PARTITION 子句的 INSERTORDER BY 的子查询等。例如 in

OVER (PARTITION BY name ORDER BY salary)

外层查询不考虑薪水顺序,如果我们想在外层查询输出时对薪水进行排序,需要在外层查询中再添加一个ORDER BY

大家的一些见解,为什么这里不尊重关系 属性 而顺序存储在子查询中?

此上下文中的 ORDER BY 实际上是 Oracle 的专有语法,用于在(逻辑上)无序的行集上生成 "ordered" 行号。在我看来,这是一个设计不佳的功能,但等效的 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 不是关系型 DBMS。与其他 SQL DBMS 一样,Oracle 在一些基本方面背离了关系模型。产品中存在隐式排序和 DISTINCT 等功能正是因为 SQL 数据模型的非关系性质以及随之而来的需要解决具有重复行的无键表。

毫不奇怪,Oracle 将此视为一种特殊情况。您可以从执行计划中看到这一点。使用有时会出现的原始 (incorrect/indeterminate) 版本的限制,您会得到 SORT ORDER BYCOUNT 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操作,但内部查询(内联视图或派生table)现在有一个SORT ORDER BY STOPKEY而不是简单的SORT ORDER BY。这一切都隐藏在内部,所以我推测,但它 看起来 像停止键 - 即行号限制 - 被推入子查询处理,所以实际上无论如何,子查询最终可能只会有七行 - 尽管计划的 ROWS 值没有反映出这一点(但是你得到了具有不同限制的相同计划),并且仍然觉得需要应用 COUNT STOPKEY单独操作。

Tom Kyte 在谈论 "Top- N Query Processing with ROWNUM" 时涵盖了类似的基础 in an Oracle Magazine article(强调已添加):

There are two ways to approach this:
- Have the client application run that query and fetch just the first N rows.
- Use that query as an inline view, and use ROWNUM to limit the results, as in SELECT * FROM ( your_query_here ) WHERE ROWNUM <= N.

The second approach is by far superior to the first, for two reasons. The lesser of the two reasons is that it requires less work by the client, because the database takes care of limiting the result set. The more important reason is the special processing the database can do to give you just the top N rows. Using the top- N query means that you have given the database extra information. You have told it, "I'm interested only in getting N rows; I'll never consider the rest." Now, that doesn't sound too earth-shattering until you think about sorting—how sorts work and what the server would need to do.

...然后继续概述它实际在做什么,比我更权威。

有趣的是,我不认为最终结果集的顺序实际上是有保证的;它似乎总是有效,但可以说你仍然应该在外部查询上有一个 ORDER BY 来完成它。看起来订单并没有真正存储在子查询中,它只是恰好是这样产生的。 (我非常怀疑它会不会改变,因为它会破坏太多东西;这最终看起来类似于 table 集合表达式,它似乎也总是保留其顺序 - 破坏会停止 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)                                   

添加到 sqlvogel 的好答案:

"As I understand, table input into FROM is relational"

不,table 输入 FROM 不是关系型的。它不是关系,因为 "table input" 是 table,而 table 不是关系。 SQL 中的无数怪癖最终都归结为一个简单的事实:SQL 中的核心积木是 table,而 table 不是关系.总结差异:

表可以包含重复行,关系不能。 (因此,SQL 提供包代数,而不是关系代数。另一个结果是,SQL 甚至不可能为其最基本的积木定义相等比较!!!鉴于您可能必须处理重复的行,您比较 tables 是否相等?)

表可以包含未命名的列,关系不能。 SELECT X+Y FROM ... 因此,SQL 被迫进入 "column identity by ordinal position",因此,您会遇到各种怪癖,例如在 SELECT A,B FROM ... UNION SELECT B,A FROM ...

表可以包含重复的列名,关系不能。 table 中的 A.ID 和 B.ID 是 而不是 不同的列名称。点之前的部分不是名称的一部分,它是一个 "scope identifier",并且那个范围标识符 "disappears" 一旦你 "outside the SELECT" 它就被 appears/is 引入。你可以用嵌套的 SELECT 验证这一点:SELECT A.ID FROM (SELECT A.ID, B.ID FROM ...)。它不会工作(除非您的特定实现偏离标准以使其工作)。

各种 SQL 结构给人的印象是 table 确实对行进行了排序。显然,ORDER BY 子句,还有 GROUP BY 子句(只能通过引入相当狡猾的 "intermediate tables with rows grouped together" 概念才能使其工作)。关系根本不是那样。

表可以包含 NULL,关系不能。这个已经被打死了

应该还有一些,但我不记得了。