当前位置: 首页 > 知识库问答 >
问题:

按子查询排序和ROWNUM违背了关系哲学?

鱼阳伯
2023-03-14

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

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

这让我讨厌。据我所知,从输入到的表是关系型的,因此没有存储顺序,这意味着当从看到时,子查询中的顺序不受尊重。

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

OVER(按姓名划分,按工资排序)

在外部查询中不会尊重工资顺序,如果我们希望在外部查询输出处对工资进行排序,则需要在外部查询中添加另一个ORDER BY

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

共有3个答案

阎劲
2023-03-14

sqlvogel的好答案是:

“据我所知,从中输入的表是关系型的”

不,对From的表输入不是关系的。它不是关系的,因为“表输入”是表,表不是关系。SQL中无数的怪癖和古怪最终都归结为一个简单的事实:SQL的核心建筑砖是表,而表不是关系。总结一下区别:

表可以包含重复行,关系不能包含重复行。(因此,SQL提供的是包代数,而不是关系代数。另一个结果是,SQL甚至不可能为其最基本的构建块html" target="_blank">定义等式比较!!!如果您可能需要处理重复行,您会如何比较表的等式?)

表可以包含未命名的列,关系不能。SELECT X Y From...因此,SQL被迫进入“按顺序位置的列标识”,因此,您会得到各种怪癖,例如在SELECT A、B from... UNION SELECT B、A From...

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

各种SQL构造给人的印象是,表确实具有对行的排序。显然,ORDER BY子句也是GROUP BY子句(只能通过引入“行分组在一起的中间表”这一相当狡猾的概念来实现)。关系根本不是这样的。

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

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

严亮
2023-03-14

不足为奇的是,Oracle将此视为一种特殊情况。你可以从执行计划中看到这一点。对于有时出现的简单(不正确/不确定)版本的限制,您可以获得排序顺序和计数停止键操作:

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)                                                                  

如果只使用有序子查询,没有限制,则只能通过操作获得排序顺序:

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)                                                                  

外部查询仍然存在计数停止键操作,但内部查询(内联视图或派生表)现在具有按停止键的排序顺序,而不是简单的按排序顺序。这一切都隐藏在内部,所以我在猜测,但它看起来像是停止键(即行数限制)被推入子查询处理中,因此实际上子查询可能只会以七行结束,尽管计划的值没有反映这一点(但随后会得到具有不同限制的相同计划),它仍然觉得需要单独应用计数停止键操作。

Tom Kyte在Oracle杂志的一篇文章中谈到了类似的内容,当时谈到了“使用ROWNUM进行Top-N查询处理”(着重部分已添加):

有两种方法可以实现这一点:让客户端html" target="_blank">应用程序运行该查询并仅获取前N行
-将该查询用作内联视图,并使用ROWNUM限制结果,如在SELECT*FROM(your\u query\u here)中,其中ROWNUM

由于两个原因,第二种方法远远优于第一种方法。这两个原因中较小的一个原因是它需要更少的客户端工作,因为数据库负责限制结果集。更重要的原因是数据库可以进行特殊处理,只提供前N行。使用top-N查询意味着您已经为数据库提供了额外的信息。你说过,“我只对N行感兴趣;我永远不会考虑剩下的。”现在,在你考虑排序如何工作以及服务器需要做什么之前,这听起来并不太惊天动地。

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

有趣的是,我并不认为最终结果集的顺序是可以保证的;它似乎总是可行的,但可以说,您也应该在外部查询上有一个ORDER BY来完成它。看起来顺序并没有真正存储在子查询中,它恰好是这样生成的。(我非常怀疑这种情况是否会改变,因为它会破坏太多东西;这最终看起来类似于一个表集合表达式,它似乎也总是保留其顺序-尽管这样会停止dbms\u xplan的工作。我相信还有其他示例。)

仅供比较,这是等效的行数()所做的:

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)                                   
巫晋鹏
2023-03-14

在这种情况下,ORDER BY实际上是Oracle的专有语法,用于在(逻辑上)无序的行集合上生成“有序”行号。在我看来,这是一个设计糟糕的功能,但等效的ISO标准SQL ROW\u NUMBER()函数(在Oracle中也有效)可能会让它更清楚地了解正在发生的事情:

select *
from (
  select ROW_NUMBER() OVER (ORDER BY price) rn, *
  from table
) t
where rn <= 7;

在这个例子中,ORDER BY去了它更符合逻辑的地方:作为派生行号属性规范的一部分。这比Oracle的版本更强大,因为您可以指定几个不同的顺序,在同一个结果中定义不同的行号。此查询返回的行的实际顺序是未定义的。我相信在您的Oracle特定版本的查询中也是如此,因为当您以这种方式使用ORDER BY时,无法保证排序。

值得记住的是,甲骨文不是关系数据库管理系统。与其他SQLDBMS一样,甲骨文在一些基本方面偏离了关系模型。产品中存在隐式排序和DISTINCT等特性,正是因为数据SQL模型的非关系性质,以及随之而来的处理具有重复行的无键表的需要。

 类似资料:
  • 我正在使用Ebean和Play 2框架,并得到两个模型:用户模型和图书模型。用户模型与图书模型以一对多关系连接。所以每个用户可以有很多书或者根本没有书。图书模式本身也有其特性。现在我想在用户模型中创建一个查询,它只返回用户,这些用户拥有具有某些属性的书籍。例如:一个属性可能是条件,如新建或使用。现在给我所有有新条件的书的用户。是否可以使用Ebean方法创建这样的查询?还是我必须使用原始SQL?

  • 我有一个针对Oracle数据库的规范top-N查询,这是所有常见问题解答和Howto建议的: 它在Oracle 11上运行良好,即它按内部选择中指定的顺序返回前N个记录。 然而,它在Oracle 12上中断。它仍然返回相同的前N个记录,但它们可能会被洗牌。这些记录的最终顺序是不确定的。 我在谷歌上搜索了一下,但没有找到任何相关的讨论。看起来其他人总是从这样的选择中获得正确的记录顺序。 不过有一个发

  • 问题内容: 我的数据库中有一个页面表,每个页面可以有一个父项,如下所示: 如果选择一个以上级别,那么最好的MySQL查询是最好的选择父级,子级,子级顺序的所有页面的查询,最多三个级别。上面的示例将产生所需的顺序: 问题答案: 我认为您应该在表中再添加一个字段,称为level并将其存储在节点的level中,然后按级别然后按父级对查询进行排序。

  • 我正在开发一个人力资源管理应用程序,所以我对如何通过JPA管理实体感到困惑。 我的情况是一组多语言上下文中的表:-employees-departments-languages-departments_languages 在我的数据库表之后: 从这个查询中,我需要员工信息,以及部门名称(假设languageId为1) 从eclipse JPA控制台执行查询将返回一个Employee对象,该对象具有

  • 主要内容:按子列排序,按索引排序,按值排序Firebase提供了多种排序数据的方式。 在本章中,我们将学习简单的查询示例。 这里将使用前面章节中的相同数据。数据记录如下所示 - 按子列排序 要按列排序数据,可以使用下面的代码。 示例 让我们来看看下面的例子。 执行上面示例代码,得到以下结果 - 按索引排序 我们可以通过类似的方式:按索引键来排序数据。 示例 让我们看看下面的一个例子。 执行上面示例代码,得到以下结果 - 按值排序 我们也可

  • 问题内容: 我创建了一个存储过程,我想在其中添加替代的order by子句。问题是查询在“无效的列名’aantal regels’”上失败 这是我现在的查询。 希望有人可以帮助我! 问题答案: 您不能以这种方式按别名排序。 第一种选择是重复代码。注意:仅仅因为重复代码,SQL引擎就不会天真地再次执行它,而是重用了结果。 或全部使用子查询…