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 --<START WITH>       -----------------------
        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) --<CONNECT BY>  -----------------------
SEARCH DEPTH FIRST BY EMPNO SET ORDER1 --<ORDER SIBLING BY> 使用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中定义的列

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

  • 其实内部实际执行的过程还是按照广度优先的方式进行,只是在最终输出结果时,会根据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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • with子句最有用的特性之一就是消除复杂的sql查询,当查询中包含大量的表和数据列时,想要搞清楚查询中的数据流就变...
    猫猫_tomluo阅读 1,558评论 0 3
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,442评论 25 707
  • 引用古诗词写一句话! 举例: 1、生命就是 “感时花溅泪,恨别鸟惊心”的无奈与感伤;生命就是“衣带渐宽终不悔,为伊...
    小贤哥2017阅读 408评论 12 4
  • 写在前边 有个读书的圈子督促着自己每周一定要看完一本书,真好。原本我看书是随心情,看到哪是哪,没个督促也就混混的过...
    伪王小豆阅读 2,156评论 1 0