Search code examples
sqloracle-databasehierarchical-data

Hierarchical Query - Counting records that belongs to Parent and Child Places


I have two tables PLACE and ADRESS. PLACE includes places like parent-child hierarcy. ADRESS includes adresses with PLACE_ID column. These tables as shown below;

PLACE

ID      PARENT_ID       NAME        CONTINENT   
11      null            USA         America
22      11              New York    America
33      22              Manhattan   America
44      null            Brasil      America
55      44              Rio         America
66      null            France      Europe
77      66              Paris       Europe  
88      66              Nice        Europe



MEMBER  

ID      PLACE_ID    NAME    ADRESS
1       22          ..      ..
2       77          ..      ..
3       33          ..      ..
4       22          ..      ..
5       55          ..      ..
6       55          ..      ..
7       88          ..      ..
8       88          ..      ..
9       88          ..      ..
10      22          ..      ..



Expected Result:

ID      PARENT_ID       MEMBER_COUNT    PLACE_NAME      CONTITNET       
11      null                4           USA             America
22      11                  4           New York        America
33      22                  1           Manhattan       America
44      null                2           Brasil          America
55      44                  2           Rio             America
66      null                4           France          Europe
77      66                  1           Paris           Europe  
88      66                  3           Nice            Europe

I want to find out how many members are in which place. I couldn't add child places's member count to parent place. My query as shown below;

WITH MEMBER_COUNT_BY_PLACE AS (
    SELECT  P.PLACE_ID, COUNT(P.ID) AS MEMBER_COUNT
    FROM MEMBER P
    GROUP BY P.PLACE_ID
) SELECT C.ID,  C.NAME, C.PARENT_ID AS PARENTID, C.CONTINENT, SUM(NVL(D.MEMBER_COUNT, 0)) AS MEMBER_COUNT
  FROM PLACE C LEFT JOIN MEMBER_COUNT_BY_PLACE D ON D.PLACE_ID = C.ID
  START WITH D.PLACE_ID IS NOT NULL
  CONNECT BY PRIOR C.PARENT_ID = C.ID
  GROUP BY C.ID, C.NAME, C.PARENT_ID, C.CONTINENT
  ORDER BY CONTINENT ASC, PARENT_ID ASC NULLS FIRST;

Thank you for your help.


Solution

  • This query also aggregates child members:

    with mcnt (id, parent_id, name, continent, child_members, own_members, ff,level_members) as (
       select 
              p.id, p.parent_id, p.name, p.continent
            , 0 as child_members
            , (select count(*) from member m where m.place_id=p.id) as own_members
            , row_number()over(partition by p.parent_id order by p.id) ff
            , sum((select count(*) from member m where m.place_id=p.id))over(partition by p.parent_id) level_members
       from place p
       where not exists(select null from place child where p.id = child.parent_id)
       union all
       select p.id, p.parent_id, p.name, p.continent
            , mcnt.level_members + mcnt.child_members as child_members
            , (select count(*) from member m where m.place_id=p.id) as own_members
            , row_number()over(partition by p.parent_id order by p.id) ff
            , sum((select count(*) from member m where m.place_id=p.id))over(partition by p.parent_id) level_members
       from mcnt, place p
       where mcnt.parent_id=p.id
         and mcnt.ff=1
    )
    select
           id, parent_id, name, continent, child_members, own_members
          ,child_members+own_members as total_cnt
    from mcnt
    order by id;
    

    Full example:

    with
    -- sample data
    place (id, parent_id, name, continent) as
      (select 11, null, 'USA'      , 'America' from dual union all
       select 22, 11  , 'New York' , 'America' from dual union all
       select 33, 22  , 'Manhattan', 'America' from dual union all
       --
      select 35, 33  , 'Central Park', 'America' from dual union all
       select 44, null, 'Brasil'   , 'America' from dual union all
       select 55, 44  , 'Rio'      , 'America' from dual union all
       select 66, null, 'France'   , 'Europe'  from dual union all
       select 77, 66  , 'Paris'    , 'Europe'  from dual union all
       select 88, 66  , 'Nice'     , 'Europe'  from dual
      ),
    member (id, place_id) as
      (select  1, 22 from dual union all
       select  2, 77 from dual union all
       select  3, 33 from dual union all
       select  4, 22 from dual union all
       select  5, 55 from dual union all
       select  6, 55 from dual union all
       select  7, 88 from dual union all
       select  8, 88 from dual union all
       select  9, 88 from dual union all
       select 10, 22 from dual
       --
       union all select 1001, 35 from dual
       union all select 1002, 35 from dual
       union all select 1003, 35 from dual
      ),
    mcnt (id, parent_id, name, continent, child_members, own_members, ff,level_members) as (
       select 
          p.id, p.parent_id, p.name, p.continent
        , 0 as child_members
        , (select count(*) from member m where m.place_id=p.id) as own_members
        , row_number()over(partition by p.parent_id order by p.id) ff
        , sum((select count(*) from member m where m.place_id=p.id))over(partition by p.parent_id) level_members
       from place p
       where not exists(select null from place child where p.id = child.parent_id)
       union all
       select p.id, p.parent_id, p.name, p.continent
        , mcnt.level_members + mcnt.child_members as child_members
        , (select count(*) from member m where m.place_id=p.id) as own_members
        , row_number()over(partition by p.parent_id order by p.id) ff
        , sum((select count(*) from member m where m.place_id=p.id))over(partition by p.parent_id) level_members
       from mcnt, place p
       where mcnt.parent_id=p.id
     and mcnt.ff=1
    )
    select
       id, parent_id, name, continent, child_members, own_members
      ,child_members+own_members as total_cnt
    from mcnt
    order by id;

    Results:

            ID  PARENT_ID NAME         CONTINE CHILD_MEMBERS OWN_MEMBERS  TOTAL_CNT
    ---------- ---------- ------------ ------- ------------- ----------- ----------
            11            USA          America             7           0          7
            22         11 New York     America             4           3          7
            33         22 Manhattan    America             3           1          4
            35         33 Central Park America             0           3          3
            44            Brasil       America             2           0          2
            55         44 Rio          America             0           2          2
            66            France       Europe              4           0          4
            77         66 Paris        Europe              0           1          1
            88         66 Nice         Europe              0           3          3
    
    9 rows selected.