PostgreSQL递归查询计算父值

Posted

技术标签:

【中文标题】PostgreSQL递归查询计算父值【英文标题】:PostgreSQL recursive query for calculation parent value 【发布时间】:2021-11-01 18:39:40 【问题描述】: 这是条目的层次结构。
             _________Milky Way (30)________
            /               |               \
    Alpha(10)           Beta(20)            Delta(null)
     /  \                                       |
Mars(7) Jupiter(3)                          Delta-child(44)

父值是子值的总和。 例如。

Alpha = Mars + Jupiter = 7 + 3 = 10
Milky Way = Alpha + Beta + Delta = 10 + 20 + null = 30 

任务:重新计算父级直到根,以防任何子级更新。让我们甚至简化 任务:选择所有条目,直到根,并重新计算值。 假设 Mars 已更新。现在 Mars 的值为 2。

             _________Milky Way (?)________
            /               |               \
    Alpha(?)            Beta(20)            Delta(null)
     /  \                                       |
Mars(2) Jupiter(3)                          Delta-child(44)

这意味着所有的父母都应该更新:

Alpha = Mars + Jupiter = 2 + 3 = 5
Milky Way = Alpha + Beta + Delta = 5 + 20 + null =  25.

注意: Delta -> Delta-child 耦合已损坏,一切正常。它可能发生,让我们把它放在这里的范围之外。 '添加此示例只是为了确保在计算过程中不会对其进行计数,因为层次结构可能足够大,并且没有任务重新计算所有子叶子,只需要从父节点到根节点。

由于一些“从层次结构中选择..” 我想收到重新计算的父母的价值观。 前任。

id name value
1 Milky Way 25
2 Alpha 5

已更新 Mars 的代码示例(sqlfiddle 链接如下):Schema

CREATE TABLE hierarchy
        (
        id int4,
        parent_id int4,
        name varchar(255),
        value int4
        );          

价值观

insert into hierarchy
values
(1, null, 'Milky Way', 30),
(2, 1, 'Alpha', 10),
(3, 1, 'Beta', 20),
(4, 1, 'Delta', null),
(5, 2, 'Mars', 2),
(6, 2, 'Jupiter', 3),
(7, 4, 'Delta-child', 44);

我的尝试

    我能够列出所有应该在计算中使用的叶子 sqlfiddle 1

    WITH RECURSIVE cte AS ( 
      SELECT h1.id,  h1.parent_id, h1.name , h1.value from hierarchy h1
      where h1.id = 5
         UNION
     SELECT h2.id,  h2.parent_id, h2.name , h2.value from hierarchy h2
     JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id ) 
     where cte.id != h2.id 
    ) select * from cte
    order by id
    

    当我尝试对值求和时,由于某种原因,查询进入无限循环sqlfiddle 2

     WITH RECURSIVE cte AS ( 
      SELECT h1.id,  h1.parent_id, h1.name , h1.value from hierarchy h1
      where h1.id = 5
         UNION
     SELECT h2.id,  h2.parent_id, h2.name , (h2.value + cte.value) as value from hierarchy h2
     JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id ) 
     where cte.id != h2.id 
    ) select * from cte
    order by id
    

    我尝试了另外一个查询,不幸的是它不包括父母的兄弟姐妹。sqlfiddle 3

                WITH RECURSIVE cte AS ( 
             SELECT h1.id,  h1.parent_id, h1.name , h1.value from hierarchy h1
              where h1.parent_id = (select parent_id from hierarchy where id = 5)  
                 UNION
             SELECT h2.id,  h2.parent_id, h2.name , cte.value as value from hierarchy h2
             JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id ) 
             where cte.id != h2.id 
            ) select id, parent_id, name, sum(value) from cte
            group by id, parent_id, name
            order by id
    

如有任何帮助,我将不胜感激。 :-)

【问题讨论】:

【参考方案1】:

我花了一些时间,但你的试验做得很好。

我所做的是将问题分成几部分。

    使用递归查询沿层次结构向下移动
with recursive base_qry as (
    select id, 
        parent_id,
        value,
        ARRAY[id] as id_array
    from hierarchy 
    union all 
    select h.id,
    h.parent_id,
    h.value,
    id_array || h.id as id_array 
    from hierarchy h join base_qry b on h.parent_id = b.id and h.value is not null
    )
    了解受影响的节点过滤包含id_array[array_length(id_array,1)] = 5 的所有节点的数组的最后一个 id
nodes_affected as (
    select * from base_qry 
    where id_array[array_length(id_array,1)] = 5
    order by array_length(id_array,1) desc
    LIMIT 1)
    查找所有未对更改节点造成影响的树分支(在此处检查节点 id=5 的过滤器)
all_combinations as (
    select b.id, b.parent_id, b.value, b.id_array from
        base_qry b join nodes_affected n 
        on ARRAY[n.id_array] && ARRAY[b.id_array]
    where (b.id_array[array_length(b.id_array,1)] = 5 
        or b.id_array @> ARRAY[5] = false)
 )

聚合

select id_array[1]::int id, sum(value)
from all_combinations where id not in (select parent_id from all_combinations where parent_id is not null)
group by id_array[1]::int
order by 1

整个查询

with recursive base_qry as (
    select id, 
        parent_id,
        value,
        ARRAY[id] as id_array
    from hierarchy 
    union all 
    select h.id,
    h.parent_id,
    h.value,
    id_array || h.id as id_array 
    from hierarchy h join base_qry b on h.parent_id = b.id and h.value is not null
    ),
nodes_affected as (
    select * from base_qry 
    where id_array[array_length(id_array,1)] = 5
    order by array_length(id_array,1) desc
    LIMIT 1),
all_combinations as (
    select b.id, b.parent_id, b.value, b.id_array from
        base_qry b join nodes_affected n 
        on ARRAY[n.id_array] && ARRAY[b.id_array]
    where (b.id_array[array_length(b.id_array,1)] = 5 
        or b.id_array @> ARRAY[5] = false)
 )
select id_array[1]::int id, sum(value)
from all_combinations where id not in (select parent_id from all_combinations where parent_id is not null)
group by id_array[1]::int
order by 1
;

【讨论】:

【参考方案2】:

从叶子开始,遍历叶子贡献给层次结构的所有节点。然后将所有对节点的贡献相加。

WITH RECURSIVE cte AS ( 
         SELECT id, parent_id, value
         FROM hierarchy h1
         WHERE not exists (select 1 from hierarchy h2 where h2.parent_id = h1.id) 
             UNION ALL
         SELECT h.id, h.parent_id, case when h.value is null then 0 else cte.value end 
         FROM hierarchy h
         JOIN cte ON (cte.parent_id = h.id) 
) 

select h.id, h.name, v.value
from (
   select id, sum(value) as value
   from cte
   group by id
) v
join hierarchy h on h.id = v.id
order by h.id;

db<>fiddle

【讨论】:

【参考方案3】:

您可以使用递归CTE来获取所有节点及其路径,然后对于hierarchy中的每个非叶子节点,您可以在当前层次结构行id存在的情况下将CTE重新加入hierarchy在路径中并对值求和:

with recursive cte(id, p, n, v, t) as (
   select h.*, concat('[', h.id::text) from hierarchy h where h.parent_id is null
   union all
   select h.id, h.parent_id, h.name, h.value, concat(c.t, ', ', h.id)
   from cte c join hierarchy h on c.id = h.parent_id
)
select h.id, sum(c.v)
from hierarchy h join cte c on c.id != h.id and exists (select 1 from jsonb_array_elements(concat(c.t, ']')::jsonb) v where v.value::text = h.id::text) 
group by h.id order by h.id

输出:

id  sum
1   79
2   5
4   44

【讨论】:

以上是关于PostgreSQL递归查询计算父值的主要内容,如果未能解决你的问题,请参考以下文章

[转]PostgreSQL 递归查询一例

PostgreSQL递归查询

PostgreSQL递归查询以获得排名边缘

PostgreSQL 在递归查询中找到所有可能的组合(排列)

递归 linq 表达式以获取非 NULL 父值?

postgresql with递归