Home > Software design >  Sum of recursive tree in postgres
Sum of recursive tree in postgres

Time:04-24

have a tree structure that looks like this:

                                 root
                   A                               B
          A1           A2                     B1        B2
      A1.1  A1.2      A2.1                   B1.1

the table looks something like this:

id | name |value | parent_id
1    root   null    null
2    A      null    1
3    B      null    1    
4    A1     null    2
5    A1.1   2       4
6    A1.2   3       4
7    A2     null    2
8    A2.1   5       7
9    B1     null    3
10   B2     1       3
11   B1.1   10      9   
.........................

All the non leaf nodes must contain the sum of their children, not only the root node. For example, this is how my desired output looks like:

id | name |value | parent_id
1    root   21      null
2    A      10      1
3    B      11      1    
4    A1     5       2
5    A1.1   2       4
6    A1.2   3       4
7    A2     5       2
8    A2.1   5       7
9    B1     10      3
10   B2     1       3
11   B1.1   10      9

how can i achieve this with a fast Postgres query

CodePudding user response:

You need a recursive CTE in the UPDATE statement:

UPDATE tablename AS t
SET "value" = c.value
FROM (
  WITH RECURSIVE cte AS(
    SELECT t1.id, t1.name, t1.value, t1.parent_id
    FROM tablename t1
    WHERE NOT EXISTS (SELECT 1 FROM tablename t2 WHERE t2.parent_id = t1.id)
    UNION ALL
    SELECT t.id, t.name, c.value, t.parent_id
    FROM tablename t INNER JOIN cte c 
    ON c.parent_id = t.id
  )
  SELECT id, SUM("value") "value"
  FROM cte
  GROUP BY id
) c 
WHERE c.id = t.id;

See the demo.

CodePudding user response:

Using a recursive cte:

with recursive cte(id, name, val, p) as (
   select t.id, t.name, t.value, '.'||(select t1.id from tbl t1 where t1.parent_id is null)||'.'||t.id||'.' from tbl t where t.parent_id = (select t1.id from tbl t1 where t1.parent_id is null)
   union all
   select t.id, t.name, t.value, c.p||t.id||'.' from cte c join tbl t on c.id = t.parent_id
)
select t.id, sum(case when c.val is null then 0 else c.val end) from tbl t 
join cte c on c.p like '%.'||t.id||'.%' group by t.id order by t.id

To update your original table with the summed child node values, you can use a subquery in update:

update tbl set value = t1.val from (
   select t.id tid, sum(case when c.val is null then 0 else c.val end) val from tbl t 
   join cte c on c.p like '%.'||t.id||'.%' group by t.id) t1 
where t1.tid = id
  • Related