1

I have a tree table. I need to ensure that when inserting or updating a record, the sumvalue field of all parents is updated - this is the sum of all child elements. I wrote a trigger (even a query is enough to demonstrate), but the values ​​are summed up incorrectly.

create table tree (
  id             bigserial     primary key,
  parent_id      bigint,
  title          text,
  sumvalue       bigint not null default 0,
  constraint fk_parent_id foreign key (parent_id)
    references tree(id)
    on delete restrict
    on update restrict
);

create or replace function update_value_trigger_proc() returns trigger as $$
begin
    update tree 
    set sumvalue = (
        with recursive tmp_tree as (
           select id, 
                  parent_id,
                  sumvalue
           from tree
           where id = new.id -- << the id of the "base" node
           union all 
           select p.id,
                  p.parent_id,
                  p.sumvalue
           from tree p
             join tmp_tree t on t.id = p.parent_id
        )
        select coalesce(sum(sumvalue), 0)::bigint as sumvalue_sum
        from tree
    )
    from tree t, (with recursive c as (
      select 
        new.id as parent_id, 
        cast(0 as bigint) as lvl
      union all
      select sa.parent_id, c.lvl-1
      from tree as sa
      join c ON c.parent_id = sa.id
    )
    select c.lvl, tree.* 
    from tree, c 
    where tree.id = c.parent_id and c.parent_id != new.id
    order by lvl desc) as ancestors
    where tree.id = ancestors.id;
    return null;
end
$$ language plpgsql;

create or replace trigger update_tree_trigger after insert or update
  on tree
  for each row
  execute procedure update_tree_trigger_proc();
  
insert into tree(title, parent_id, sumvalue) values
  ('A',    null, 0),
  ('AA',   1,    0),
  ('AB',   1,    0),
  ('AC',   1,    0),
  ('AAA',  2,    0),
  ('AAAA', 5,    5),
  ('AAAB', 5,    5),
  ('AAAC', 5,    5),
  ('AAB',  2,    5),
  ('AAC',  2,    5),
  ('ABA',  3,    5),
  ('ABB',  3,    5),
  ('ABC',  3,    5),
  ('ACA',  4,    5),
  ('ACB',  4,    5),
  ('ACC',  4,    5)
;

Result:

id  parent_id   title   sumvalue
6   5           AAAA    5
7   5           AAAB    5
8   5           AAAC    5
9   2           AAB     5
10  2           AAC     5
11  3           ABA     5
12  3           ABB     5
13  3           ABC     5
14  4           ACA     5
15  4           ACB     5
16  4           ACC     5
5   2           AAA     179410
2   1           AA      8433535 
3   1           AB      299839815
4   1           AC      10718331525
1   null        A       21745115865

Expected result:

id  parent_id   title   sumvalue
6   5           AAAA    5
7   5           AAAB    5
8   5           AAAC    5
9   2           AAB     5
10  2           AAC     5
11  3           ABA     5
12  3           ABB     5
13  3           ABC     5
14  4           ACA     5
15  4           ACB     5
16  4           ACC     5
5   2           AAA     15
2   1           AA      25 
3   1           AB      15
4   1           AC      15
1   null        A       55

Demo: https://dbfiddle.uk/Z4ZKz-wL

UPDATE:

I fixed trigger to:

create or replace function update_value_trigger_proc() returns trigger as $$
begin
    update tree 
    set sumvalue = (
        select coalesce(sum(sumvalue), 0)::bigint as sumvalue_sum from tree where parent_id = new.id
    )
    from tree t, (with recursive c as (
      select 
        new.id as parent_id, 
        cast(0 as bigint) as lvl
      union all
      select sa.parent_id, c.lvl-1
      from tree as sa
      join c ON c.parent_id = sa.id
    )
    select c.lvl, tree.* 
    from tree, c 
    where tree.id = c.parent_id and c.parent_id != new.id
    order by lvl desc) as ancestors
    where tree.id = ancestors.id;
    return null;
end
$$ language plpgsql;

Because we need to sum only one level below (already summed before). Result now:

id  parent_id   title   sumvalue
6   5           AAAA    5
7   5           AAAB    5
8   5           AAAC    5
9   2           AAB     5
10  2           AAC     5
11  3           ABA     5
12  3           ABB     5
13  3           ABC     5
14  4           ACA     5
15  4           ACB     5
16  4           ACC     5
5   2           AAA     0
2   1           AA      0
3   1           AB      0
4   1           AC      0
1   null        A       15

Demo: https://dbfiddle.uk/iwcljdb9

3
  • What if a child's sumvalue is under update whilst you calculate the parents sumvalue? Commented Oct 7, 2022 at 10:14
  • Perhaps you should use a recursive CTE inside a View to keep the sums up to date in realtime (a common implementation for parent-child tables for calculating Bills of Materials in the manufacturing industry). If you need the data persisted, then you can periodically store that View to a Table, but typically querying the View is sufficient for most people because they only care about a handful of parents at a time, which is fast in the recursion that's executed. Either way, it's probably mechanically simpler and easier to maintain than your current implementation.
    – J.D.
    Commented Oct 7, 2022 at 12:11
  • Using trigger for such operation is costly, I suggest you to create materialized view which calculates sums at once. After that you refresh it once in a time period like 30 minutes.
    – Sahap Asci
    Commented Oct 7, 2022 at 12:18

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.