Oracle CONNECT BY引起的困惑

8

查询语句:

select level from dual connect by rownum<10;

提供数字1到9。

另一个查询:

SELECT LEVEL FROM DUAL CONNECT BY rownum>5;

输出结果:1

我曾在类似于经理/员工的分层数据中使用CONNECT BY。但是我无法解释上述两个查询的结果。

编辑: 我并没有试图通过查询#2来实现任何特殊的目的。我只想知道oracle如何解释这个查询。由于使用CONNECT BY,是否有任何部分充当父级和子级?为什么结果是1?背后发生了什么?

2个回答

17

如何逐步执行和评估CONNECT BY查询(以示例为例)。

假设我们有以下表格和一个connect by查询:

select * from mytable;

         X
----------
         1 
         2 
         3 
         4 

SELECT level, m.* 
FROM mytable m
START with x = 1
CONNECT BY PRIOR x +1 = x  OR  PRIOR x + 2 = x 
ORDER BY level;

步骤 1:

从表格mytable中选择符合START WITH条件的行,并将 LEVEL = 1 分配给返回的结果集:

 CREATE TABLE step1 AS
 SELECT 1 "LEVEL", X from mytable
 WHERE x = 1;

 SELECT * FROM step1;

         LEVEL          X
    ---------- ----------
             1          1

第二步

将等级增加1:

LEVEL = LEVEL + 1

使用 CONNECT BY 条件作为连接条件,将先前步骤返回的结果集与 mytable 表连接。

在此子句中,PRIOR column-name 是指先前步骤返回的结果集,简单的 column-name 则是指 mytable 表:

CREATE TABLE step2 AS
SELECT 2 "LEVEL", mytable.X from mytable
JOIN step1 "PRIOR"
ON "PRIOR".x +1 = mytable.x or  "PRIOR".x + 2 = mytable.x;

select * from step2;

     LEVEL          X
---------- ----------
         2          2 
         2          3

第 x+1 步

重复 #2 直到最后一个操作返回空结果集。

第三步

CREATE TABLE step3 AS
SELECT 3 "LEVEL", mytable.X from mytable
JOIN step2 "PRIOR"
ON "PRIOR".x +1 = mytable.x or  "PRIOR".x + 2 = mytable.x;

select * from step3;

     LEVEL          X
---------- ----------
         3          3 
         3          4 
         3          4

第四步

CREATE TABLE step4 AS
SELECT 4 "LEVEL", mytable.X from mytable
JOIN step3 "PRIOR"
ON "PRIOR".x +1 = mytable.x or  "PRIOR".x + 2 = mytable.x;

select * from step4;

     LEVEL          X
---------- ----------
         4          4 

第五步

CREATE TABLE step5 AS
SELECT 5 "LEVEL", mytable.X from mytable
JOIN step4 "PRIOR"
ON "PRIOR".x +1 = mytable.x or  "PRIOR".x + 2 = mytable.x;

select * from step5;

no rows selected

第五步未返回任何行,因此现在我们完成查询

最后一步

UNION ALL 合并所有步骤的结果,并将其作为最终结果返回:

SELECT * FROM step1
UNION ALL
SELECT * FROM step2
UNION ALL
SELECT * FROM step3
UNION ALL
SELECT * FROM step4
UNION ALL

SELECT * FROM step5;

     LEVEL          X
---------- ----------
         1          1 
         2          2 
         2          3 
         3          3 
         3          4 
         3          4 
         4          4 
现在让我们将上述步骤应用到您的查询中:
SELECT * FROM dual;

DUMMY
-----
X 

SELECT LEVEL FROM DUAL CONNECT BY rownum>5;

步骤 1

由于查询不包含 START WITH 子句,Oracle 从源表中选择所有记录:

CREATE TABLE step1 AS
SELECT 1 "LEVEL" FROM dual;

select * from step1;

     LEVEL
----------
         1 

第二步

CREATE TABLE step2 AS
SELECT 2 "LEVEL" from dual
JOIN step1 "PRIOR"
ON rownum > 5

select * from step2;

no rows selected

由于最后一步没有返回任何行,我们将完成查询。

最后一步

SELECT * FROM step1
UNION ALL

SELECT * FROM step2;

     LEVEL
----------
         1

上一个查询的分析:

select level from dual connect by rownum<10;

我把这作为一项家庭作业留给你。


1
+1 感谢您详细的解释。您的解释比 Oracle 网站上的任何文档都要好得多。现在对我来说非常清晰明了 :-) - coolscitist
虽然这个答案在大部分情况下是正确的,但它在涉及到CONNECT BY查询中如何分配ROWNUM时是错误的。在CONNECT BY查询中,结果集是唯一的,并且ROWNUMLEVEL = 1连续分配到更高的级别;它不会在每个级别重新从1开始。有关详细信息,请参见稍后的线程,其中有人实际尝试完成此答案末尾留下的所谓“作业任务”。https://stackoverflow.com/questions/52899897/oracle-connect-by-rownum/52900132#52900132 - user5683823

6
这与CONNECT BY无关,而是你错误使用ROWNUM的结果。

引用文档的说法:

对于查询返回的每一行,ROWNUM伪列都会返回一个数字,表示Oracle从表或一组连接行中选择行的顺序。第一行选择的ROWNUM为1,第二行为2,依此类推。

ROWNUM是结果集的因素,而不是查询的因素。尽管它们是相关联的,但并不完全相同;如果第一行不存在,则不可能存在第6个结果。

这也在文档中有所解释:

Conditions testing for ROWNUM values greater than a positive integer are always false. For example, this query returns no rows:

SELECT *
  FROM employees
  WHERE ROWNUM > 1;

The first row fetched is assigned a ROWNUM of 1 and makes the condition false. The second row to be fetched is now the first row and is also assigned a ROWNUM of 1 and makes the condition false. All rows subsequently fail to satisfy the condition, so no rows are returned.


1
我知道rownum与结果集有关。在这里,使用level代替rownum会得到相同的结果。我不明白的是为什么第二个查询的结果是1。 - coolscitist
1
我并不想通过第二个查询来实现任何目的。我只是无法理解查询中的“父级”和“子级”。Oracle如何解释这个查询? - coolscitist
第二个查询的结果是1,原因在我的答案中引用了文档。在查询中,“parent”是前一行,因为你是从一个单行表中进行选择。子行始终是当前行。 - Ben
“previous”和“current”行是什么?我相信从双重选择级别只能得到一行? - coolscitist
它确实可以@Robik,但CONNECT BY是一种分层查询;由于您从DUAL选择,因此您有一个初始子项,然后连接创建一个子项。前一行在第一个实例中是双重的,当前行是级别+1,等等。 - Ben
感谢您的帮助。kordirko的解释让我清楚地了解了Oracle在幕后如何执行CONNECT BY。 - coolscitist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接