PLSQL - 递归子查询RSF打破CONNECT BY LOOP限制

例如有表EMP_ROLES存有数据如下图

PLSQL - 递归子查询RSF打破CONNECT BY LOOP限制_第1张图片

RNAME表示员工充当的角色名称,当一人身兼多职时,便会以+号串联各个角色

这是一种很聪明的存储方法,但有时我们却又可能需要拆出每个人的所有角色以便匹配,也就是说要以员工+单角色名称的维度来组织数据,如下图

PLSQL - 递归子查询RSF打破CONNECT BY LOOP限制_第2张图片

棘手的地方在于RNAME的内容不是固定的:角色的组合有长有短,组合起来的顺序也可能不同,这使得SQL需要做到动态拆分,这让人容易想到使用正则表达式搭配层次查询拆分,比如:

SQL> select regexp_substr('Oracle_Dev_Share', '[^\_]+', 1, level) r
  2    from dual
  3  connect by level <= regexp_count('Oracle_Dev_Share', '\_') + 1;

R
-------
Oracle
Dev
Share

但如果在这张表上直接这样使用,结果将是一个灾难

PS:结果行数太多,所以这里加一个START WITH rid = 1以便看出问题原因

SQL> select rid, ename, regexp_substr(rname, '[^\+]+', 1, level) rname, level
  2    from emp_roles
  3   start with rid = 1
  4  connect by level <= regexp_count(rname, '\+') + 1;

 RID ENAME        RNAME  LEVEL
---- ------------ ------ -----
   1 YUSUF        DAD        1
   3 SCOTT        GCM        2
   5 TURNER       GCM        3
   4 MARTIN       MPC        2
   5 TURNER       GCM        3
   5 TURNER       MPC        2
   5 TURNER       GCM        3

7 rows selected

简单来说,这是一个未指示父子关系的放飞自我的递归,它只对LEVEL进行了一下筛选:首先YUSUF是第一层,然后全表作为他的下一层(LEVEL = 2),进行到这里要求RNAME的+号数量 ≥ 1,从而QINJINGBAO(角色是SON)被淘汰;下面第二层的各行分别来到第三层,依然是全表作为遴选对象,这里要求RNAME的+号数量 ≥ 2,唯有TURNER可以入选;第四层要求RNAME的+号数量 ≥ 3,不存在这样的行,递归结束

可想而知去掉START WITH rid = 1将得到什么

既然自由的递归不行,指示SQL在同一行上递归可以吗?这样也不行,因为这形成了递归循环(CONNECT BY loop)

SQL> select rid,
  2         ename,
  3         regexp_substr(rname, '[^\+]+', 1, level) rname
  4    from emp_roles
  5  connect by prior rid = rid
  6         and level <= regexp_count(rname, '\+') + 1;
select rid,
       ename,
       regexp_substr(rname, '[^\+]+', 1, level) rname
  from emp_roles
connect by prior rid = rid
       and level <= regexp_count(rname, '\+') + 1

ORA-01436: 用户数据中的 CONNECT BY 循环

递归子查询RSF是Oracle 11.2引入的新特性,它没有递归循环的限制,用RSF可以较为轻松地解决这个问题

SQL> with rsf_q(rid, ename, rname, lv) as
  2   (select er.rid, er.ename, regexp_substr(er.rname, '[^\+]+') rname, 1 lv
  3      from emp_roles er
  4    union all
  5    select er.rid,
  6           er.ename,
  7           regexp_substr(er.rname, '[^\+]+', 1, rq.lv + 1) rname,
  8           rq.lv + 1 lv
  9      from rsf_q rq, emp_roles er
 10     where rq.rid = er.rid
 11       and rq.lv <= regexp_count(er.rname, '\+'))
 12  search depth first by rid set rorder
 13  select rid, ename, rname from rsf_q;

 RID ENAME        RNAME
---- ------------ ------
   1 YUSUF        DAD
   2 QINJINGBAO   SON
   3 SCOTT        MPC
   3 SCOTT        GCM
   4 MARTIN       GCM
   4 MARTIN       MPC
   5 TURNER       MM
   5 TURNER       MPC
   5 TURNER       GCM

9 rows selected

在11.2以前,可以考虑构造虚拟行集来关联实现裂行

SQL> with e_roles as
  2   (select er.rid, er.ename, er.rname, regexp_count(er.rname, '\+') + 1 rcount
  3      from emp_roles er),
  4  pseudo_rows as
  5   (select level row_num
  6      from dual
  7    connect by level <= (select max(rcount) from e_roles))
  8  select er.rid,
  9         er.ename,
 10         regexp_substr(er.rname, '[^\+]+', 1, pr.row_num) rname
 11    from e_roles er, pseudo_rows pr
 12   where er.rcount >= pr.row_num
 13   order by er.rid, er.ename, pr.row_num;

 RID ENAME        RNAME
---- ------------ ------
   1 YUSUF        DAD
   2 QINJINGBAO   SON
   3 SCOTT        MPC
   3 SCOTT        GCM
   4 MARTIN       GCM
   4 MARTIN       MPC
   5 TURNER       MM
   5 TURNER       MPC
   5 TURNER       GCM

PLSQL - 递归子查询RSF打破CONNECT BY LOOP限制_第3张图片

你可能感兴趣的:(Oracle学习笔记,oracle,plsql,sql,数据库)