Search code examples
sqloraclehierarchical-datahierarchical-query

How can i get a child record's parent, grandparent, and so on and itself along with the distance from root in SQL


I have a table as shown below -

    ID | NAME        | PARENT

     1 | Global      | null 
     2 | USA         | 1
     3 | Canada      | 1
     4 | USA-1       | 2
     5 | USA-11      | 4

The column parent refers to the column id i.e., Global is the root. For USA-11, USA-1 is the parent, USA is the grandparent and Global is the great grand parent. It is a rugged hierarchy and can go upto any level. I need help in writing a query that will expand this hierarchy in the following manner -

    ID | NAME        | PARENT    | Distance_from_parent

     1 | Global      | Global    | 0
     2 | USA         | USA       | 0
     2 | USA         | Global    | 1
     3 | Canada      | Canada    | 0
     3 | Canada      | Global    | 1
     4 | USA-1       | USA-1     | 0
     4 | USA-1       | USA       | 1
     4 | USA-1       | Global    | 2
     5 | USA-11      | USA-11    | 0
     5 | USA-11      | USA-1     | 1
     5 | USA-11      | USA       | 2
     5 | USA-11      | Global    | 3

So if you notice, I'm repeating every location name against itself and its hierarchy. The column "distance_from_parent" is the distance from the parent mentioned in the "Parent" column.

I have tried this using Connect By, Level and prior, but unable to achieve this kind of output.

The DDL for this is -

create table subsidiary (id number, name varchar2(50), parent number); 

insert into subsidiary (id, name, parent) values (1, 'Parent', null);
insert into subsidiary (id, name, parent) values (2, 'USA', 1);
insert into subsidiary (id, name, parent) values (3, 'Canada', 1);
insert into subsidiary (id, name, parent) values (4, 'USA-1', 2);
insert into subsidiary (id, name, parent) values (5, 'USA-11', 4);

Solution

  • Use a hierarchical query with CONNECT_BY_ROOT:

    SELECT CONNECT_BY_ROOT( ID ) AS id,
           CONNECT_BY_ROOT( name ) AS name,
           name AS parent,
           LEVEL - 1 AS distance_from_parent
    FROM   subsidiary
    CONNECT BY PRIOR parent = id
    ORDER BY id, distance_from_parent;
    

    So for your test data:

    CREATE TABLE subsidiary ( ID, NAME, PARENT ) AS
    SELECT 1, 'Global', null FROM DUAL UNION ALL 
    SELECT 2, 'USA',    1 FROM DUAL UNION ALL
    SELECT 3, 'Canada', 1 FROM DUAL UNION ALL
    SELECT 4, 'USA-1',  2 FROM DUAL UNION ALL
    SELECT 5, 'USA-11', 4 FROM DUAL;
    

    This outputs:

    ID | NAME   | PARENT | DISTANCE_FROM_PARENT
    -: | :----- | :----- | -------------------:
     1 | Global | Global |                    0
     2 | USA    | USA    |                    0
     2 | USA    | Global |                    1
     3 | Canada | Canada |                    0
     3 | Canada | Global |                    1
     4 | USA-1  | USA-1  |                    0
     4 | USA-1  | USA    |                    1
     4 | USA-1  | Global |                    2
     5 | USA-11 | USA-11 |                    0
     5 | USA-11 | USA-1  |                    1
     5 | USA-11 | USA    |                    2
     5 | USA-11 | Global |                    3
    

    db<>fiddle here