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

时间:2016-04-20 10:58:28

标签: oracle oracle11g relational-database sql-order-by rownum

ROWNUM之前应用了Oracle的ORDER BY。为了根据排序列放置ROWNUM,在所有文档和文本中提出以下子查询。

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

那让我烦恼。据我了解,FROM中的表输入是关系的,因此不存储任何顺序,这意味着当FROM看到时,子查询中的顺序不受尊重。

我不记得确切的情况但是&#34; ORDER BY的这个事实对外部查询没有影响&#34;我读了不止一次。示例包括内联子查询,INSERT的子查询,PARTITION子句的ORDER BY等。例如在

OVER (PARTITION BY name ORDER BY salary)

外部查询中不会遵守工资订单,如果我们希望工资在外部查询输出中排序,则需要在外部查询中添加另一个ORDER BY

每个人都有一些关于为什么关系属性在这里不受尊重并且订单存储在子查询中的见解?

3 个答案:

答案 0 :(得分:9)

此上下文中的ORDER BY实际上是Oracle的专有语法,用于生成&#34; ordered&#34; (逻辑上)无序行集上的行号。在我看来,这是一个设计不佳的功能,但是等效的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模型具有非关系性,因此需要解决具有重复行的无密钥表。

答案 1 :(得分:4)

毫不奇怪,Oracle将此视为一种特殊情况。您可以从执行计划中看到它。对于有时会出现限制的天真(不正确/不确定)版本,您可以获得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操作,但内部查询(内联视图或派生表)现在具有SORT ORDER BY STOPKEY而不是简单SORT ORDER BY。这一切都隐藏在内部,所以我猜测,但它看起来就像停止键 - 即行数限制 - 被推入子查询处理,所以实际上子查询可能只最后总共有7行 - 尽管计划的ROWS值并未反映出来(但随后你得到了具有不同限制的相同计划),并且仍然认为需要应用COUNT STOPKEY操作分开。

当谈到“使用ROWNUM进行前N个查询处理”时,Tom Kyte涵盖了类似的基础in an Oracle Magazine article(强调添加):

  

有两种方法可以解决这个问题:
   - 让客户端应用程序运行该查询并仅获取前N行    - 将该查询用作内联视图,并使用ROWNUM限制结果,如SELECT * FROM(your_query_here)WHERE ROWNUM&lt; = 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)                                   

答案 2 :(得分:1)

添加到sqlvogel的好答案:

&#34;据我所知,FROM输入表是关系型的&#34;

不,表格输入到FROM不是关系。它不是关系因为&#34;表输入&#34;是表和表不是关系。 SQL中无数的怪癖和怪异最终归结为这个简单的事实:SQL中的核心构建块是表,而表不是关系。总结差异:

表可以包含重复的行,关系不能。 (因此,SQL提供了包代数,而不是关系代数。另外一个结果是,SQL甚至不可能为其最基本的构建块定义相等比较!如果你为了平等,你将如何比较表的相等性可能要处理重复的行?)

表可以包含未命名的列,关系不能。 SELECT X + Y FROM ...因此,SQL被序数位置&#34;强制进入&#34;列标识,因此,您会得到各种怪癖,例如:在SELECT A,B FROM ... UNION SELECT B,A FROM ...

表可以包含重复的列名,关系不能。表中的A.ID和B.ID是不同的列名。点之前的部分不是名称的一部分,它是&#34;范围标识符&#34;,并且范围标识符&#34;消失&#34;一旦你在SELECT&#34;之外#34;它出现/被引入。您可以使用嵌套的SELECT验证这一点:SELECT A.ID FROM(SELECT A.ID,B.ID FROM ...)。它不会起作用(除非你的特定实现偏离标准以使其工作)。

各种SQL结构让人们产生这样的印象:表格对行有排序。显然,ORDER BY子句也是GROUP BY子句(只能通过引入相当狡猾的&#34;中间表的概念,并将行组合在一起&#34;)。关系根本不是那样的。

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

应该有更多,但我不记得他们的帽子。