Home > Software design >  get the sum of children nodes right below for every parent in a hierarchical query
get the sum of children nodes right below for every parent in a hierarchical query

Time:12-11

i have a table objective with columns id, label, cost, parent_id and i have this query

select 
b.id,
b.parent_id,
b.label,
b.cost
from objective b
start with b.parent_id is null
connect by 
prior b.id=b.parent_id
ORDER SIBLINGS BY b.id  

only a leaf (and sometimes a parent of a leaf) has the column cost not null the others are almost always null, is it possible to get the cost of every node (summing the cost of the nodes right below) or not.

i know it can be done with join and sum but i thought maybe there is a simpler way to do it. if this was the table content :

ID  PARENT_ID   LABEL   COST
1   null        A       0
2   1           B       1
3   2           C       3
4   1           D       0
5   4           E       3
6   5           F       7
7   4           G       5

the result should be something like this :

ID  PARENT_ID   LABEL   COST    CALCULATED_COST
1   null        A       0       15(meaning 0 is a wrong value) 
2   1           B       1       3 (meaning 1 is a wrong value)
3   2           C       3       3
4   1           D       0       12
5   4           E       3       7 (meaning 3 is a wrong value)
6   5           F       7       7
7   4           G       5       5

CodePudding user response:

only a leaf (and sometimes a parent of a leaf) has the column cost not null the others are almost always null

If the rule is that "only a leaf or a parent of a leaf have a non-null cost and the others are always null" then you can use:

SELECT id,
       parent_id,
       label,
       COALESCE(PRIOR cost, 0)   COALESCE(cost, 0) AS cost
FROM   objective
START WITH
       parent_id IS NULL
CONNECT BY
       PRIOR id = parent_id
ORDER SIBLINGS BY
       id

If the rule is that "any row can have a non-null cost" then you will need to parse the data structure in both directions:

SELECT id,
       parent_id,
       label,
       ( SELECT COALESCE(SUM(cost), 0)
         FROM   objective c
         START WITH c.id = o.id
         CONNECT BY PRIOR parent_id = id -- Reverse the direction
       ) AS cost
FROM   objective o
START WITH
       parent_id IS NULL
CONNECT BY
       PRIOR id = parent_id
ORDER SIBLINGS BY
       id

or use a recursive sub-query factoring clause:

WITH rsqfc (id, parent_id, label, cost) AS (
  SELECT id, parent_id, label, COALESCE(cost, 0)
  FROM   objective
  WHERE  parent_id IS NULL
UNION ALL
  SELECT o.id, o.parent_id, o.label, COALESCE(o.cost, 0)   r.cost
  FROM   objective o
         INNER JOIN rsqfc r
         ON (r.id = o.parent_id)
) SEARCH DEPTH FIRST BY id SET order_id
SELECT *
FROM   rsqfc;

fiddle


Update

If you are looking to sum the cost for a row and all its descendants then you can use:

SELECT id,
       parent_id,
       label,
       cost,
       ( SELECT COALESCE(SUM(cost), 0)
         FROM   objective c
         START WITH c.id = o.id
         CONNECT BY PRIOR id = parent_id
       ) AS total_cost
FROM   objective o
START WITH
       parent_id IS NULL
CONNECT BY
       PRIOR id = parent_id
ORDER SIBLINGS BY
       id;

Which, for the sample data outputs:

CREATE TABLE objective ( id, parent_id, label, cost ) AS
SELECT 1, NULL, 'A', 0 FROM DUAL UNION ALL
SELECT 2, 1, 'B', 1 FROM DUAL UNION ALL
SELECT 3, 2, 'C', 3 FROM DUAL UNION ALL
SELECT 4, 1, 'D', 0 FROM DUAL UNION ALL
SELECT 5, 4, 'E', 3 FROM DUAL UNION ALL
SELECT 6, 5, 'F', 7 FROM DUAL UNION ALL
SELECT 7, 4, 'G', 5 FROM DUAL;

Outputs:

ID PARENT_ID LABEL COST TOTAL_COST
1 null A 0 19
2 1 B 1 4
3 2 C 3 3
4 1 D 0 15
5 4 E 3 10
6 5 F 7 7
7 4 G 5 5

fiddle


Update 2:

If you only want to total the descendants that are leaves then:

SELECT id,
       parent_id,
       label,
       cost,
       ( SELECT COALESCE(SUM(cost), 0)
         FROM   objective c
         WHERE  CONNECT_BY_ISLEAF = 1
         START WITH c.id = o.id
         CONNECT BY PRIOR id = parent_id
       ) AS total_cost
FROM   objective o
START WITH
       parent_id IS NULL
CONNECT BY
       PRIOR id = parent_id
ORDER SIBLINGS BY
       id;

Which, for the sample data, outputs:

ID PARENT_ID LABEL COST TOTAL_COST
1 null A 0 15
2 1 B 1 3
3 2 C 3 3
4 1 D 0 12
5 4 E 3 7
6 5 F 7 7
7 4 G 5 5

fiddle

  • Related