Home > Back-end >  Oracle - Parent - child fill mising hierarchy levels
Oracle - Parent - child fill mising hierarchy levels

Time:12-16

I have created my fiddle example here: FIDDLE Here is also athe code from the fiddle:

CREATE TABLE T1(ID INT, CODE INT, CODE_NAME VARCHAR(100), PARENT_ID INT);

INSERT INTO T1 VALUES(100,1,'LEVEL 1', NULL);
INSERT INTO T1 VALUES(110,11,'LEVEL 2', 100);
INSERT INTO T1 VALUES(120,111,'LEVEL 3', 110);
INSERT INTO T1 VALUES(125,112,'LEVEL 3', 110);
INSERT INTO T1 VALUES(130,1111,'LEVEL 4', 120);
INSERT INTO T1 VALUES(200,2,'LEVEL 1', NULL);
INSERT INTO T1 VALUES(210,21,'LEVEL 2', 200);
INSERT INTO T1 VALUES(300,3,'LEVEL 1', NULL);

I have trouble finding the soultuin how to get from that table this result:

|  CODE  |  CODE NAME | CODE 1 |CODE NAME 1| CODE 2 | CODE NAME 2| CODE 3 | CODE NAME 3 |
 -------- ------------ -------- ----------- -------- ------------ -------- ------------- 
|   1    |  LEVEL 1   |   11   |  LEVEL 2  |  111   | LEVEL 3    |  1111  |    LEVEL 4  |
|   1    |  LEVEL 1   |   11   |  LEVEL 2  |  112   | LEVEL 3    |   112  |    LEVEL 3  |   
|   2    |  LEVEL 1   |   21   |  LEVEL 2  |   21   | LEVEL 2    |    21  |    LEVEL 2  |
|   3    |  LEVEL 1   |   3    |  LEVEL 1  |    3   | LEVEL 1    |     3  |    LEVEL 1  |

I have tried something with connect by but that is not what I need(I think)... The max I will ever have is 4 levels and if there are only two levels in the data then the 3rd and the 4th level should be filled wiht the values of the last existing value. The same rule is valid if there are 3 levels or 1 level.

CodePudding user response:

For sample data you posted:

SQL> select * from t1;

        ID       CODE CODE_NAME   PARENT_ID
---------- ---------- ---------- ----------
       100          1 LEVEL 1
       110         11 LEVEL 2           100
       120        111 LEVEL 3           110
       130       1111 LEVEL 4           120
       200          2 LEVEL 1
       210         21 LEVEL 2           200

6 rows selected.

SQL>

an ugly (and who-knows-how-performant) query that, though, returns desired result is

with temp as
  (select id, code, code_name, parent_id, level lvl,
      row_number() over (partition by level order by id) rn
   from t1
   start with parent_id is null
   connect by prior id = parent_id
  ),
a as
  (select * from temp where lvl = 1),
b as
  (select * from temp where lvl = 2),
c as
  (select * from temp where lvl = 3),
d as
  (select * from temp where lvl = 4)  
select 
           a.code                          code1,          a.code_name                                         code_name1,
  coalesce(b.code, a.code)                 code2, coalesce(b.code_name, a.code_name)                           code_name2,
  coalesce(c.code, b.code, a.code)         code3, coalesce(c.code_name, b.code_name, a.code_name)              code_name3,
  coalesce(d.code, c.code, b.code, a.code) code4, coalesce(d.code_name, c.code_name, b.code_name, a.code_name) code_name4
from a join b on b.rn = a.rn
       left join c on c.rn = b.rn
       left join d on d.rn = c.rn; 

which results in

     CODE1 CODE_NAME1      CODE2 CODE_NAME2      CODE3 CODE_NAME3      CODE4 CODE_NAME4
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
         1 LEVEL 1            11 LEVEL 2           111 LEVEL 3          1111 LEVEL 4
         2 LEVEL 1            21 LEVEL 2            21 LEVEL 2            21 LEVEL 2

What does it do?

  • temp CTE creates a hierarchy; additionally, row_number function numbers each row within the same level
  • a, b, c, d CTEs extract values belonging to their own level value (you said there can be up to 4 levels)
  • finally, coalesce on column names along with outer join do the job

CodePudding user response:

You can use a recursive sub-query:

WITH hierarchy (
  code,  code_name,
  code1, code_name1,
  code2, code_name2,
  code3, code_name3,
  id, depth
) AS (
  SELECT code,
         code_name,
         CAST(NULL AS INT),
         CAST(NULL AS VARCHAR2(100)),
         CAST(NULL AS INT),
         CAST(NULL AS VARCHAR2(100)),
         CAST(NULL AS INT),
         CAST(NULL AS VARCHAR2(100)),
         id,
         1
  FROM   t1
  WHERE  parent_id IS NULL
UNION ALL
  SELECT h.code,
         h.code_name,
         CASE depth WHEN 1 THEN COALESCE(t1.code, h.code) ELSE h.code1 END,
         CASE depth WHEN 1 THEN COALESCE(t1.code_name, h.code_name) ELSE h.code_name1 END,
         CASE depth WHEN 2 THEN COALESCE(t1.code, h.code1) ELSE h.code2 END,
         CASE depth WHEN 2 THEN COALESCE(t1.code_name, h.code_name1) ELSE h.code_name2 END,
         CASE depth WHEN 3 THEN COALESCE(t1.code, h.code2) ELSE h.code3 END,
         CASE depth WHEN 3 THEN COALESCE(t1.code_name, h.code_name2) ELSE h.code_name3 END,
         t1.id,
         h.depth   1
  FROM   hierarchy h
         LEFT OUTER JOIN t1
         ON (h.id = t1.parent_id)
  WHERE  depth < 4
)
CYCLE code, depth SET is_cycle TO 1 DEFAULT 0
SELECT code,  code_name,
       code1, code_name1,
       code2, code_name2,
       code3, code_name3
FROM   hierarchy
WHERE  depth = 4;

Which, for the sample data:

CREATE TABLE T1(ID, CODE, CODE_NAME, PARENT_ID) AS
SELECT 100, 1,    'LEVEL 1',  NULL FROM DUAL UNION ALL
SELECT 110, 11,   'LEVEL 2',  100  FROM DUAL UNION ALL
SELECT 120, 111,  'LEVEL 3',  110  FROM DUAL UNION ALL
SELECT 130, 1111, 'LEVEL 4',  120  FROM DUAL UNION ALL
SELECT 200, 2,    'LEVEL 1',  NULL FROM DUAL UNION ALL
SELECT 210, 21,   'LEVEL 2a', 200  FROM DUAL UNION ALL
SELECT 220, 22,   'LEVEL 2b', 200  FROM DUAL UNION ALL
SELECT 230, 221,  'LEVEL 3',  220  FROM DUAL UNION ALL
SELECT 300, 3,    'LEVEL 1',  NULL FROM DUAL;

Outputs:

CODE CODE_NAME CODE1 CODE_NAME1 CODE2 CODE_NAME2 CODE3 CODE_NAME3
1 LEVEL 1 11 LEVEL 2 111 LEVEL 3 1111 LEVEL 4
3 LEVEL 1 3 LEVEL 1 3 LEVEL 1 3 LEVEL 1
2 LEVEL 1 21 LEVEL 2a 21 LEVEL 2a 21 LEVEL 2a
2 LEVEL 1 22 LEVEL 2b 221 LEVEL 3 221 LEVEL 3

db<>fiddle here

CodePudding user response:

From your example I assume you want to see one row per root key as your example is not realy a tree but a bamboo

If so this is a trivial PIVOT query - unfortunately limited to some level deep (here example for your 4 levels)

with p (ROOT_CODE, CODE, CODE_NAME, ID, PARENT_ID, LVL) as (
select CODE, CODE, CODE_NAME, ID, PARENT_ID, 1 LVL from t1 where PARENT_ID is NULL
union all
select p.ROOT_CODE, c.CODE, c.CODE_NAME, c.ID, c.PARENT_ID, p.LVL 1 from t1 c
join p on c.PARENT_ID = p.ID),
t2 as (
select  ROOT_CODE, CODE,CODE_NAME,LVL  from p)
select * from t2
PIVOT
(max(CODE) code, max(CODE_NAME) code_name
for LVL in (1 as "LEV1",2 as "LEV2",3 as "LEV3",4 as "LEV4")
);

 ROOT_CODE  LEV1_CODE LEV1_CODE_  LEV2_CODE LEV2_CODE_  LEV3_CODE LEV3_CODE_  LEV4_CODE LEV4_CODE_
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
         1          1 LEVEL 1            11 LEVEL 2           111 LEVEL 3          1111 LEVEL 4   
         2          2 LEVEL 1            21 LEVEL 2   

The recursive CTE calculates the ROOT_CODE required for the pivot. I' leaving as an exercise to fill the not defined levels (with COALESCE) with the previous values as in your example.

In case (as commented) you nedd oner row for each leave key a simple solution based on CONNECT_BY_PATHis possible.

I'm using again *recursive CTEcalculating the path from *root* to the *current node* and finaly filtering in the result the *leaves* (IDthat are notPARENT_ID`)

with p ( CODE, CODE_NAME, ID, PARENT_ID, PATH) as (
select   CODE, CODE_NAME, ID, PARENT_ID, to_char(CODE)||'|'||CODE_NAME PATH from t1 where PARENT_ID is NULL
union all
select   c.CODE, c.CODE_NAME, c.ID, c.PARENT_ID, p.PATH ||'|'||to_char(c.CODE)||'|'||c.CODE_NAME from t1 c
join p on c.PARENT_ID = p.ID)
select PATH from p
where ID in (select ID from T1 MINUS  select PARENT_ID from T1)
order by 1;

The result holds for any level deepness and is concatenated string with delimiter

PATH                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
----------------------------------------------
1|LEVEL 1|11|LEVEL 2|111|LEVEL 3|1111|LEVEL 4
1|LEVEL 1|11|LEVEL 2|112|LEVEL 3
2|LEVEL 1|21|LEVEL 2
3|LEVEL 1
        

Use substr instr to extract and coalesce for the default values.

  • Related