使用Postgresql WITH子句递归地对树的节点求和

时间:2014-03-08 20:06:40

标签: sql recursion postgresql-9.1

(使用Postgresql 9.1)

我在数据库中有一个树结构,我需要对节点的值求和。有两点需要注意:

  1. 并非所有节点都有值。
  2. 如果父节点有值,则忽略子值。
  3. 虽然使用强大的递归WITH子句来递归树是很容易的,但它正在强制执行这两个破坏我的代码的警告。这是我的设置:

    CREATE TABLE node (
      id VARCHAR(1) PRIMARY KEY
    );
    
    INSERT INTO node VALUES ('A');
    INSERT INTO node VALUES ('B');
    INSERT INTO node VALUES ('C');
    INSERT INTO node VALUES ('D');
    INSERT INTO node VALUES ('E');
    INSERT INTO node VALUES ('F');
    INSERT INTO node VALUES ('G');
    
    CREATE TABLE node_value (
      id VARCHAR(1) PRIMARY KEY,
      value INTEGER
    );
    
    INSERT INTO node_value VALUES ('B', 5);
    INSERT INTO node_value VALUES ('D', 2);
    INSERT INTO node_value VALUES ('E', 0);
    INSERT INTO node_value VALUES ('F', 3);
    INSERT INTO node_value VALUES ('G', 4);
    
    CREATE TABLE tree (
      parent VARCHAR(1),
      child VARCHAR(1)
    );
    
    INSERT INTO tree VALUES ('A', 'B');
    INSERT INTO tree VALUES ('B', 'D');
    INSERT INTO tree VALUES ('B', 'E');
    INSERT INTO tree VALUES ('A', 'C');
    INSERT INTO tree VALUES ('C', 'F');
    INSERT INTO tree VALUES ('C', 'G');
    

    这给了我以下树(节点和值):

    A
    |--B(5)
    |  |--D(2)
    |  |--E(0)
    |
    |--C
       |--F(3)
       |--G(4)
    

    根据上述规则,以下是预期的总和值:

    A = (5 + 3 + 4) = 12
    B = 5
    D = 2
    E = 0
    C = (3 + 4) = 7
    F = 3
    G = 4
    

    我编写了以下SQL,但是我无法集成递归的UNION和JOIN逻辑来强制实施规则#1和#2:

    WITH recursive treeSum(root, parent, child, total_value) AS (
    
      SELECT tree.parent root, tree.parent, tree.child, node_value.value total_value
      FROM tree
      LEFT JOIN node_value ON node_value.id = tree.parent
    
      UNION
    
      SELECT treeSum.root, tree.parent, tree.child, node_value.value total_value
      FROM tree
      INNER JOIN treeSum ON treeSum.child = tree.parent
      LEFT JOIN node_value ON node_value.id = tree.parent
    )
    
    SELECT root, sum(total_value) FROM treeSum WHERE root = 'A' GROUP BY root
    

    查询为根A返回10,但它应该是12.我知道UNION和/或JOIN逻辑正在抛弃它。任何帮助将不胜感激。

    编辑:澄清一下,A的总和是12而不是14.根据规则,如果节点有值,则抓住该值并忽略其子节点。因为B的值为5,我们忽略D和E. C没有值,所以我们抓住它的子,因此A = 5(B)+ 3(F)+ 4(G)= 12的总和。我知道它是奇怪,但这是要求。感谢。

    编辑2:这些结果将与外部数据集结合,因此我无法在WITH子句中对根进行硬编码。例如,我可能需要这样的东西:

    SELECT root, SUM(total_value) FROM treeSUM GROUP BY root WHERE root = 'A'
    

    这个树是其中之一,因此意味着有多个根,通过调用代码指定 - 不在递归子句本身内。感谢。

    编辑3:如何在生产中使用它的一个例子是根将由另一个表指定,所以我不能将根硬编码到递归子句中。许多树木可能有许多根源。

    SELECT id, SUM(COALESCE(value,0)) FROM treeSUM 
    INNER JOIN roots_to_select rts ON rts.id = treeSUM.id GROUP BY id
    

    解决方案(从koriander的答案中清除)!以下内容允许外部来源指定根(使用roots_to_selectWHERE条件:

    WITH recursive roots_to_select AS (
      SELECT 'A'::varchar as id
    ),
    
    treeSum(root, id, value) AS (
    
    select     node.id as root, node.id, node_value.value
    from       node
    inner join roots_to_select rts on (node.id = rts.id)
    left join  node_value          on (node.id = node_value.id)
    
    union
    
    select     treeSum.root, node.id, node_value.value 
    from       treeSum
    inner join tree       on (treeSum.id = tree.parent)
    inner join node       on (tree.child = node.id)
    left join  node_value on (node.id    = node_value.id)
    where      treeSum.value is null
    )
    
    select   root, sum(coalesce(value, 0))
    from     treeSum
    group by root
    

    输出:12

1 个答案:

答案 0 :(得分:1)

测试here

with recursive treeSum(id, value) AS (

select    node.id, node_value.value
from      node
left join node_value on (node.id = node_value.id)
where     node.id = 'A'

union

select     node.id, node_value.value 
from       treeSum
inner join tree       on (treeSum.id = tree.parent)
inner join node       on (tree.child = node.id)
left join  node_value on (node.id = node_value.id)
where      treeSum.value is null
)

select sum(coalesce(value, 0)) from treeSum

编辑1:要将结果与其他表合并,您可以执行以下操作:

select id, (select sum(coalesce(value, 0)) from treeSum) as nodesum
from   node
inner join some_table on (...)
where  node.id = 'A'

编辑2:根据您的编辑3支持多个根,您可以(未经测试):

with recursive treeSum(root, id, value) AS (

select     node.id as root, node.id, node_value.value
from       node
inner join roots_to_select rts on (node.id = rts.id)
left join  node_value          on (node.id = node_value.id)

union

select     treeSum.root, node.id, node_value.value 
from       treeSum
inner join tree       on (treeSum.id = tree.parent)
inner join node       on (tree.child = node.id)
left join  node_value on (node.id    = node_value.id)
where      treeSum.value is null
)

select   root, sum(coalesce(value, 0))
from     treeSum
group by root