SQL 递归子查询分解

SQL Recursive Sub Query Factoring

使用以下信息考虑 table。 Table数据:

Code_ID Name Head_Name Head_Rank Report_To_Code
1 ABC XYZ 07
2 DEF BBB 01 1
3 GHI ZZZ 02 1
4 JFK XXX 10 2

样本:

CREATE TABLE TEST_01 
   (  CODE_ID NUMBER(5), 
      NAME VARCHAR2(3), 
      HEAD_NAME VARCHAR2(3), 
      HEAD_RANK VARCHAR2(2), 
      REPORT_TO_CODE NUMBER(5)
   );
   
insert into test_01 (CODE_ID, NAME, HEAD_NAME, HEAD_RANK, REPORT_TO_CODE)
values (1, 'ABC', 'XYZ', '07', null);

insert into test_01 (CODE_ID, NAME, HEAD_NAME, HEAD_RANK, REPORT_TO_CODE)
values (2, 'DEF', 'BBB', '01', 1);

insert into test_01 (CODE_ID, NAME, HEAD_NAME, HEAD_RANK, REPORT_TO_CODE)
values (3, 'GHI', 'ZZZ', '02', 1);

insert into test_01 (CODE_ID, NAME, HEAD_NAME, HEAD_RANK, REPORT_TO_CODE)
values (4, 'JFK', 'XXX', '10', 2);

commit;

我们的最终输出需要存储为层次结构,如下所示。

Code_ID Name Head_Name Head_Rank L1_Code L2_Code L3_Code .. L12_Code R_ID LVL_CNT
1 ABC XYZ 07 1 0
1 ABC XYZ 07 2 2 1
1 ABC XYZ 07 2 4 4 2
1 ABC XYZ 07 3 3 1
2 DEF BBB 01 2 0
2 DEF BBB 01 4 4 1
3 GHI ZZZ 02 3 0
4 JFK XXX 10 4 0

没有人向3或4汇报,所以他们只有一级。 此处,R_ID -> 采用层次结构的最后一个 CODE_ID。 LVL_CNT -> 获取 R_ID 相对于主 CODE_ID.

的最大分层计数

所有主代码的reportees,需要用level count来追溯。最大值可以是 12.

我们已经使用多个联合进行开发,但是由于我们每月要处理大量数据,所以这个过程非常缓慢。 示例代码:

SELECT
           T01.CODE_ID
       ,   T01.NAME
       ,   T01.HEAD_NAME
       ,   T01.HEAD_RANK
       ,   NULL as L1_CODE
       ,   NULL as L2_CODE
       ,   NULL as L3_CODE
       ,   T01.CODE_ID
       ,   0
      FROM    test_01     T01
UNION
SELECT
           T01.REPORT_TO_CODE
       ,   TT.NAME
       ,   TT.HEAD_NAME
       ,   TT.HEAD_RANK
       ,   T01.CODE_ID as L1_CODE
       ,   NULL as L2_CODE
       ,   NULL as L3_CODE
       ,   T01.CODE_ID
       ,   1
      FROM    test_01     T01
            , test_01     TT
      WHERE T01.REPORT_TO_CODE = TT.CODE_ID
        AND T01.REPORT_TO_CODE <> T01.CODE_ID
UNION
SELECT
           T01.REPORT_TO_CODE
       ,   TT.NAME
       ,   TT.HEAD_NAME
       ,   TT.HEAD_RANK
       ,   T01.CODE_ID as L1_CODE
       ,   T02.CODE_ID as L2_CODE
       ,   NULL as L3_CODE
       ,   T02.CODE_ID
       ,   2
      FROM    test_01     T01
            , test_01     T02
            , test_01     TT
      WHERE T01.REPORT_TO_CODE = TT.CODE_ID
        AND T02.REPORT_TO_CODE =  T01.CODE_ID
        AND T01.REPORT_TO_CODE <> T01.CODE_ID
        AND T02.REPORT_TO_CODE <> T02.CODE_ID
UNION
SELECT
           T01.REPORT_TO_CODE
       ,   TT.NAME
       ,   TT.HEAD_NAME
       ,   TT.HEAD_RANK
       ,   T01.CODE_ID as L1_CODE
       ,   T02.CODE_ID as L2_CODE
       ,   T03.CODE_ID as L3_CODE
       ,   T03.CODE_ID
       ,   3
      FROM    test_01     T01
            , test_01     T02
            , test_01     T03
            , test_01     TT
      WHERE T01.REPORT_TO_CODE = TT.CODE_ID
        AND T02.REPORT_TO_CODE = T01.CODE_ID
        AND T03.REPORT_TO_CODE = T02.CODE_ID
        AND T01.REPORT_TO_CODE <> T01.CODE_ID
        AND T02.REPORT_TO_CODE <> T02.CODE_ID
        AND T03.REPORT_TO_CODE <> T03.CODE_ID

我想知道是否有任何更简单的方法可以使用 connect by prior 或 join with less union 来获得相同的结果,我将非常高兴和感激。

您可以使用递归子查询分解子句:

WITH rsqfc ( code_id, name, head_name, head_rank, report_to_code, l1_code, l2_code, l3_code, r_id, lvl_cnt ) AS (
  SELECT code_id,
         name,
         head_name,
         head_rank,
         report_to_code,
         CAST( NULL AS NUMBER ),
         CAST( NULL AS NUMBER ),
         CAST( NULL AS NUMBER ),
         code_id,
         0
  FROM   table_name
UNION ALL
  SELECT r.code_id,
         r.name,
         r.head_name,
         r.head_rank,
         r.report_to_code,
         CASE lvl_cnt
         WHEN 0
         THEN t.code_id
         ELSE r.l1_code
         END,
         CASE lvl_cnt
         WHEN 1
         THEN t.code_id
         ELSE r.l2_code
         END,
         CASE lvl_cnt
         WHEN 2
         THEN t.code_id
         ELSE r.l3_code
         END,
         t.code_id,
         lvl_cnt + 1
  FROM   table_name t
         INNER JOIN rsqfc r
         ON ( r.r_id = t.report_to_code )
)
SELECT *
FROM   rsqfc
ORDER BY code_id, l1_code NULLS FIRST, l2_code NULLS FIRST, l3_code NULLS FIRST;

其中,对于示例数据:

CREATE TABLE table_name ( Code_ID, Name, Head_Name, Head_Rank, Report_To_Code ) AS
SELECT 1, 'ABC', 'XYZ', '07', NULL FROM DUAL UNION ALL
SELECT 2, 'DEF', 'BBB', '01', 1 FROM DUAL UNION ALL
SELECT 3, 'GHI', 'ZZZ', '02', 1 FROM DUAL UNION ALL
SELECT 4, 'JFK', 'XXX', '10', 2 FROM DUAL;

输出:

CODE_ID | NAME | HEAD_NAME | HEAD_RANK | REPORT_TO_CODE | L1_CODE | L2_CODE | L3_CODE | R_ID | LVL_CNT
------: | :--- | :-------- | :-------- | -------------: | ------: | ------: | ------: | ---: | ------:
      1 | ABC  | XYZ       | 07        |           null |    null |    null |    null |    1 |       0
      1 | ABC  | XYZ       | 07        |           null |       2 |    null |    null |    2 |       1
      1 | ABC  | XYZ       | 07        |           null |       2 |       4 |    null |    4 |       2
      1 | ABC  | XYZ       | 07        |           null |       3 |    null |    null |    3 |       1
      2 | DEF  | BBB       | 01        |              1 |    null |    null |    null |    2 |       0
      2 | DEF  | BBB       | 01        |              1 |       4 |    null |    null |    4 |       1
      3 | GHI  | ZZZ       | 02        |              1 |    null |    null |    null |    3 |       0
      4 | JFK  | XXX       | 10        |              2 |    null |    null |    null |    4 |       0

db<>fiddle here