Recursive Subquery Factoring (Recursive WITH)

在学习RSF之前,需要先理解如下内容:

  • Subquery Factoring的基本使用方法
  • 层次查询的语法及相关功能的实现

Oracle中称为Recursive Subquery Factoring : RSF, ANSI标准中的名称是递归公共表表达式(Recursive Common Table Expression)

RSF是Oracle 11g R2版本中的新特性之一,它按照ANSI标准进行了设计,因此,在其他的数据库当中也都能够以同样的语法使用。
与层次查询(Hierarcical Query)相比,它有更好的兼容性。虽然在实现某些层次结构查询的需求时,层次查询的语法相对简洁,而且在性能上也可能会优于RSF,但是,正是由于它的兼容性,以及能够更加灵活地完善层次查询功能的特点,使它在某些场景当中相当有用。

学习RSF的一个比较好的方法也是将其与层次查询进行比较,在使用RSF实现层次查询中各种功能的同时,能够更加深刻地理解RSF的特性,同时也会巩固对层次查询的理解。

RSF的限制条件

  • DISTINCT关键字或GROUP BY子句
  • MODEL子句
  • 聚合函数。但是,在SELECT列表中可以使用分析函数
  • 引用query_name的子查询
  • 引用query_name作为右表的外联接

首先看一个例子:

实现对SCOTT Schema的EMP表中的上下级关系查询的需求,得到如下结果:

LV  EMPNO  ENAME         MGR         -------------------------
1   7839   KING                      -- 定位点成员部分输出的列 --
2   7566      JONES      7839        -------------------------
3   7788        SCOTT    7566        --                     --
4   7876          ADAMS  7788        --                     --
3   7902        FORD     7566        -- 递归成员部分定义后,一 --
4   7369          SMITH  7902        -- 行一行地反复递归查询并 --
2   7698      BLAKE      7839        -- 与前一次执行后的结果合 --
3   7499        ALLEN    7698        -- 并(UNION ALL)      --
3   7521        WARD     7698        --                     --
3   7654        MARTIN   7698        --                     --
3   7844        TURNER   7698        --                     --
3   7900        JAMES    7698        --                     --
2   7782      CLARK      7839        --                     --
3   7934        MILLER   7782        -------------------------

使用层次查询实现该功能:

SELECT LEVEL LV
     , EMPNO
     , LPAD(' ', LEVEL*2-1,' ')||ENAME ENAME
     , MGR
  FROM EMP_RSF
 START WITH MGR IS NULL
CONNECT BY PRIOR EMPNO = MGR
 ORDER SIBLINGS BY EMPNO;

使用RSF实现该功能:

WITH E( LV, EMPNO, ENAME, MGR ) AS(  --RSF需要对数据集合中的列进行定义之后才能使用。
        SELECT 1 LV --<定义层次查询LEVEL的初始值> -----------------------
             , EMPNO                            --      定位         --
             , ENAME                            --      成员         --
             , MGR                              --      部分         --
          FROM EMP_RSF                          --                   --
         WHERE MGR IS NULL --       -----------------------
        UNION ALL                               --使用UNION ALL连接定位点成员部分和递归部分
        SELECT Y.LV+1 --<定义LEVEL值不断递增>    -----------------------
             , X.EMPNO                          --      递归         --
             , X.ENAME                          --      成员         --
             , X.MGR                            --      部分         --
          FROM EMP_RSF X, E Y                   --                   --
         WHERE Y.EMPNO = X.MGR) --  -----------------------
SEARCH DEPTH FIRST BY EMPNO SET ORDER1 -- 使用SEARCH子句控制递归的方向
SELECT LV
     , EMPNO
     , LPAD(' ', LV*2-1,' ')||ENAME ENAME
     , MGR
FROM E;

通过这个例子可以看出,RSF能够实现层次查询中的功能,虽然从语法上来看,比层次查询要复杂一些。

从RSF实现的代码中可以看出:

  • RSF实现时需要对数据集合中的列进行定义
  • RSF分为定位点成员(锚点成员:Anchor Member)和递归成员(Recursive Member)两部分,且两部分之间需要使用UNION ALL进行连接
  • 定位点成员部分中的WHERE子句用来定位开始的点,对应层次查询中的START WITH子句
  • 在递归成员部分当中定义递归的规律,这部分相当于层次查询中的CONNECT BY子句,而且还可以在递归成员的SELECT语句后对列进行操作
  • 可以使用SEARCH子句控制递归的方向,其中分为深度优先和广度优先。默认选项是广度优先,但是大部分层次结构是深度优先。在SEARCH子句可以定义一个顺序的伪列,该伪列对应于层次查询中的ORDER SIBLINGS BY中定义的列
Recursive Subquery Factoring (Recursive WITH)_第1张图片
Recursive Subquery Factoring (Recursive WITH)_第2张图片

以上两个图只是说明概念,与实际输出结果无直接关系。

  • 其实内部实际执行的过程还是按照广度优先的方式进行,只是在最终输出结果时,会根据SEARCH子句中指定的方式输出。

层次查询其他功能实现的比较

使用RSF实现层次查询的SYS_CONNECT_BY_PATH:

--Hierarcical Query
SELECT LEVEL LV
     , EMPNO
     , LPAD(' ', LEVEL*2-1,' ')||ENAME ENAME
     , MGR
     , SYS_CONNECT_BY_PATH(ENAME,':')
  FROM EMP_RSF
 START WITH MGR IS NULL
CONNECT BY PRIOR EMPNO = MGR
 ORDER SIBLINGS BY EMPNO;

--RSF
WITH E( LV, EMPNO, ENAME, MGR, PATH) AS(
        SELECT 1 LV, EMPNO, ENAME, MGR
             , ':'||ENAME PATH
          FROM EMP_RSF
         WHERE MGR IS NULL
        UNION ALL
        SELECT Y.LV+1, X.EMPNO, X.ENAME, X.MGR
             , Y.PATH||':'||X.ENAME
          FROM EMP_RSF X, E Y
         WHERE Y.EMPNO = X.MGR)
SEARCH DEPTH FIRST BY EMPNO SET ORDER1
SELECT LV
     , EMPNO
     , LPAD(' ', LV*2-1,' ')||ENAME ENAME
     , MGR
     , PATH
FROM E;

LV    EMPNO    ENAME          MGR   PATH
1     7839     KING                 :KING
2     7566        JONES       7839  :KING:JONES
3     7788          SCOTT     7566  :KING:JONES:SCOTT
4     7876            ADAMS   7788  :KING:JONES:SCOTT:ADAMS
3     7902          FORD      7566  :KING:JONES:FORD
4     7369            SMITH   7902  :KING:JONES:FORD:SMITH
2     7698        BLAKE       7839  :KING:BLAKE
3     7499          ALLEN     7698  :KING:BLAKE:ALLEN
3     7521          WARD      7698  :KING:BLAKE:WARD
3     7654          MARTIN    7698  :KING:BLAKE:MARTIN
3     7844          TURNER    7698  :KING:BLAKE:TURNER
3     7900          JAMES     7698  :KING:BLAKE:JAMES
2     7782        CLARK       7839  :KING:CLARK
3     7934          MILLER    7782  :KING:CLARK:MILLER

比较一下使用两种方法实现SYS_CONNECT_BY_PATH的这两段代码。首先不讨论是否简洁,起码从功能上都是一样的。
但是,有一个小差别是,如果在层次查询当中,想取消PATH中第一个:号的话,需要在SYS_CONNECT_BY_PATH前使用LTRIM函数去掉引号,但是使用RSF的话,只需要在定义起点的时候,当第一个引号字符串去掉即可。

使用RSF实现层次查询的CONNECT_BY_ROOT:

--Hierarcical Query
SELECT LEVEL LV
     , EMPNO
     , LPAD(' ', LEVEL*2-1,' ')||ENAME ENAME
     , MGR
     , CONNECT_BY_ROOT ENAME ROOT
     , SYS_CONNECT_BY_PATH(ENAME,':')
  FROM EMP_RSF
 START WITH MGR IS NULL
CONNECT BY PRIOR EMPNO = MGR
 ORDER SIBLINGS BY EMPNO;

--RSF 
WITH E( LV, EMPNO, ENAME, MGR, PATH) AS(
        SELECT 1 LV, EMPNO, ENAME, MGR, ':'||ENAME PATH
          FROM EMP_RSF
         WHERE MGR IS NULL
        UNION ALL
        SELECT Y.LV+1, X.EMPNO, X.ENAME, X.MGR, Y.PATH||':'||X.ENAME
          FROM EMP_RSF X, E Y
         WHERE Y.EMPNO = X.MGR)
SEARCH DEPTH FIRST BY EMPNO SET ORDER1
SELECT LV
     , EMPNO
     , LPAD(' ', LV*2-1,' ')||ENAME ENAME
     , MGR
     , NVL(SUBSTR(PATH, 2, INSTR(PATH, ':', 2)-2),ENAME) ROOT
     , PATH
FROM E;

LV  EMPNO  ENAME         MGR   ROOT  PATH
1   7839   KING                KING  :KING
2   7566      JONES      7839  KING  :KING:JONES
3   7788        SCOTT    7566  KING  :KING:JONES:SCOTT
4   7876          ADAMS  7788  KING  :KING:JONES:SCOTT:ADAMS
3   7902        FORD     7566  KING  :KING:JONES:FORD
4   7369         SMITH   7902  KING  :KING:JONES:FORD:SMITH
2   7698      BLAKE      7839  KING  :KING:BLAKE
3   7499        ALLEN    7698  KING  :KING:BLAKE:ALLEN
3   7521        WARD     7698  KING  :KING:BLAKE:WARD
3   7654        MARTIN   7698  KING  :KING:BLAKE:MARTIN
3   7844        TURNER   7698  KING  :KING:BLAKE:TURNER
3   7900        JAMES    7698  KING  :KING:BLAKE:JAMES
2   7782      CLARK      7839  KING  :KING:CLARK
3   7934        MILLER   7782  KING  :KING:CLARK:MILLER

使用RSF实现层次查询的CONNECT_BY_ISCYCLE和NOCYCLE:
在层次查询中,CONNECT_BY_ISCYCLE主要用于检验是否存在循环,所谓的CYCLE,是只如下图:

CYCLE图片
此时,在查询的过程当中就会出现死循环,因此,就会弹出错误。
因此,在层次查询当中使用NOCYCLE来控制不产生多余的死循环,而在RSF中则使用CYCLE子句来对其进行控制
根据上面图中所示,对表中数据进行修改,使SMITH称为KING的父节点。

--Update
UPDATE EMP_RSF SET MGR = 7369 WHERE MGR IS NULL;

--Hierarcical Query
SELECT LEVEL LV
     , EMPNO
     , LPAD(' ', LEVEL*2-1,' ')||ENAME ENAME
     , MGR
     , CONNECT_BY_ISCYCLE ISCYCLE
  FROM EMP_RSF
 START WITH EMPNO = 7839
CONNECT BY NOCYCLE PRIOR EMPNO = MGR;
 
LV  EMPNO  ENAME          MGR   ISCYCLE
1   7839   KING           7369   0
2   7566      JONES       7839   0
3   7788        SCOTT     7566   0
4   7876          ADAMS   7788   0
3   7902        FORD      7566   0
4   7369          SMITH   7902   1
2   7698      BLAKE       7839   0
3   7499        ALLEN     7698   0
3   7521        WARD      7698   0
3   7654        MARTIN     7698  0
3   7844        TURNER     7698  0
3   7900        JAMES     7698   0
2   7782      CLARK       7839   0
3   7934        MILLER     7782  0
 
--RSF
WITH E( LV, EMPNO, ENAME, MGR) AS(
        SELECT 1 LV, EMPNO, ENAME, MGR
          FROM EMP_RSF
         WHERE EMPNO = 7839
        UNION ALL
        SELECT Y.LV+1, X.EMPNO, X.ENAME, X.MGR
          FROM EMP_RSF X, E Y
         WHERE Y.EMPNO = X.MGR)
SEARCH DEPTH FIRST BY EMPNO SET ORDER1
CYCLE EMPNO SET ISCYCLE TO '1' DEFAULT '0'
SELECT LV
     , EMPNO
     , LPAD(' ', LV*2-1,' ')||ENAME ENAME
     , MGR
     , ISCYCLE
FROM E;
 
LV  EMPNO  ENAME          MGR  ISCYCLE
1   7839   KING           7369   0
2   7566      JONES       7839   0
3   7788        SCOTT     7566   0
4   7876          ADAMS   7788   0
3   7902        FORD      7566   0
4   7369          SMITH   7902   0
5   7839           KING   7369   1
2   7698      BLAKE       7839   0
3   7499        ALLEN     7698   0
3   7521        WARD      7698   0
3   7654        MARTIN    7698   0
3   7844        TURNER    7698   0
3   7900        JAMES     7698   0
2   7782      CLARK       7839   0
3   7934        MILLER    7782   0

对两种方式实现的结果进行比较,可以发现RSF实现的结果当中会多出一行,并标记为1来表示发生的循环。而使用层次查询时,如果想使用CONNECT_BY_ISCYCLE的话,必须同时使用NOCYCLE,并且是在最下一层节点上标记是否发生了循环。

使用RSF实现层次查询的CONNECT_BY_ISLEAF:

--Hierarcical Query
SELECT LEVEL LV
     , EMPNO
     , LPAD(' ', LEVEL*2-1,' ')||ENAME ENAME
     , MGR
     , CONNECT_BY_ISLEAF ISLEAF
  FROM EMP_RSF
 START WITH MGR IS NULL
CONNECT BY PRIOR EMPNO = MGR;
 
--RSF(Out Join)
WITH LEAVES AS(
      SELECT EMPNO
        FROM EMP
       WHERE EMPNO NOT IN (SELECT DISTINCT MGR
                             FROM EMP_RSF
                            WHERE MGR IS NOT NULL)),
     E(LV, EMPNO, ENAME, MGR) AS(
      SELECT 1 LV, EMPNO, ENAME, MGR
        FROM EMP_RSF
       WHERE MGR IS NULL
      UNION ALL
      SELECT Y.LV+1, X.EMPNO, X.ENAME, X.MGR
        FROM EMP_RSF X, E Y
       WHERE Y.EMPNO = X.MGR)
SEARCH DEPTH FIRST BY EMPNO SET ORDER1
SELECT E.LV, E.EMPNO
     , LPAD(' ', LV*2-1,' ')||ENAME ENAME, MGR
     , DECODE(LEAVES.EMPNO, NULL, 0, 1) ISLEAF
  FROM E LEFT JOIN LEAVES
    ON (E.EMPNO = LEAVES.EMPNO);

--RSF(Analysis Function)
WITH E(LV, EMPNO, ENAME, MGR) AS(
      SELECT 1 LV, EMPNO, ENAME, MGR
        FROM EMP_RSF
       WHERE MGR IS NULL
      UNION ALL
      SELECT Y.LV+1, X.EMPNO, X.ENAME, X.MGR
        FROM EMP_RSF X, E Y
       WHERE Y.EMPNO = X.MGR)
SEARCH DEPTH FIRST BY EMPNO SET ORDER1
SELECT E.LV, E.EMPNO, LPAD(' ', LV*2-1,' ')||ENAME ENAME, MGR
     , CASE WHEN LV - LEAD(LV) OVER(ORDER BY ORDER1) < 0 THEN 0 ELSE 1 END ISLEAF
  FROM E;

LV  EMPNO  ENAME         MGR  ISLEAF
1   7839   KING                  0
2   7566      JONES      7839    0
3   7788        SCOTT    7566    0
4     7876          ADAMS  7788    1
3     7902        FORD     7566    0
4     7369          SMITH  7902    1
2     7698      BLAKE      7839    0
3     7499        ALLEN    7698    1
3     7521        WARD     7698    1
3     7654        MARTIN   7698    1
3     7844        TURNER   7698    1
3     7900        JAMES    7698    1
2     7782      CLARK      7839    0
3     7934        MILLER   7782    1

你可能感兴趣的:(Recursive Subquery Factoring (Recursive WITH))