【问题标题】:How to SUM all subchildren tree in SQL recursively?如何递归求和 SQL 中的所有子树?
【发布时间】:2022-10-05 11:52:18
【问题描述】:

美好的一天,我已经在这个问题上拉了一段时间的头发><"

我有4类在树形结构中。

租户类别事务视图:

我想在每个类别中获得所有孩子的总和“sumSubtotal”

像这样的东西:

我已经非常接近了......但有些东西我没有得到><“

with recursive cte (sumSubtotal, sumQuantity, id, idParentCategory, treeSum, depth) as (

        select  root.sumSubtotal, -- STEP 1
                root.sumQuantity, 
                root.id, 
                root.idParentCategory, 
                root.sumSubtotal as treeSum,
                0 as depth
        from    tenant_category_transaction_view as root

        union all -- LOOP THROUGH ALL ROOT ROWS AND ADD ROWS TO THE CTE WITH THE INNER JOIN

        select  child.sumSubtotal, -- STEP 3
                child.sumQuantity, 
                child.id, 
                child.idParentCategory, 
                (cte.treeSum + child.sumSubtotal) AS treeSum,
                (cte.depth + 1) AS depth
        from    tenant_category_transaction_view AS child

        inner join cte on child.idParentCategory = cte.id -- STEP 2
)
select sumSubtotal, sumQuantity, id, idParentCategory, treeSum, depth -- STEP 4
from cte

上述查询的结果:

看来我正在生成正确的 treeSum 但只在一个分支中颠倒

你会这么好心帮我一把吗?

感谢您的时间 :)

【问题讨论】:

  • 在您看来,您应该使用 group by
  • 您的方法的问题:它以错误的方向求和,当前的小计是 this_value 加上 parent.subtotal。这会产生您看到的反向部分总计。另请注意,由于同样的问题,您的总计丢失了。
  • 您已将测试用例更改为不通过公共非空根链接两个顶级类别。正如我在回答中所展示的那样,您应该有一个顶级类别/根目录以允许它们被聚合。如果您有特殊的 null 情况需要单独汇总,这很不方便。只是一个建议。 ...当然,在问题中没有正确的细节,我将值放在不同的列中。我会调整的。
  • 如果您无法调整当前数据以消除空问题,我已经更新了我的答案以使用您现有的数据。答案包含用于解决您提出的直接问题的 SQL,以及最后的建议更改。

标签: mysql sql recursive-query children


【解决方案1】:

我已经更新了小提琴以包含问题中提供的确切架构/数据,包括 null 问题。它还包括我建议的更改的示例。

该解决方案基本上采用给定数据并在内部对其进行转换(在 CTE 术语 nodes 中),以便 2 个顶级类别行链接到 id 为 0 的公共行,以便我提供的原始逻辑可用于处理这是一个层次的类别列表。

首先,我们递归地找到所有的分支列表。每个分支都由相应的根标识。然后,我们聚合每个根/分支的节点数量。

The fiddle

WITH RECURSIVE nodes AS (
         SELECT id, COALESCE(idParentCategory, 0) AS idParentCategory
              , sumSubtotal, sumQuantity
           FROM tenant_category_transaction_view
          UNION
         SELECT 0, null, 0, 0
     )
   , cte AS (
        SELECT t.*, t.id AS root
             , idParentCategory AS idParentCategory0
             , sumSubtotal      AS sumSubtotal0
             , sumQuantity      AS sumQuantity0
          FROM nodes AS t
         UNION ALL
        SELECT t.* , t0.root
             , t0.idParentCategory0
             , t0.sumSubtotal0
             , t0.sumQuantity0
          FROM cte AS t0
          JOIN nodes AS t
            ON t.idParentCategory = t0.id
     )
SELECT root
     , MIN(idParentCategory0)   AS idParentCategory
     , MIN(sumSubtotal0)        AS sumSubtotal
     , MIN(sumQuantity0)        AS sumQuantity
     , SUM(t1.sumSubtotal)      AS total
  FROM cte AS t1
 GROUP BY root
 ORDER BY root
;

结果:

root idParentCategory sumSubtotal sumQuantity total
0 null 0 0 9890
1 0 9800 98 9800
4 0 20 1 90
5 4 30 1 70
6 5 40 1 40

设置:

CREATE TABLE tenant_category_transaction_view (
    id               int primary key
  , idParentCategory int
  , sumSubtotal      int
  , sumQuantity      int
);

INSERT INTO tenant_category_transaction_view VALUES
    (1, null, 9800, 98)
  , (4, null,   20,  1)
  , (5,    4,   30,  1)
  , (6,    5,   40,  1)
;

以下使用建议对原始表格和数据进行轻微调整。代替 id 为 1 和 4 的行的 2 个顶部空父引用,添加一个顶行(例如,id 为 99)并让 id 为 2 和 4 的行引用 parent = 99 的行。

WITH RECURSIVE cte AS (
        SELECT t.*, t.id AS root
          FROM tenant_category_transaction_view AS t
         UNION ALL
        SELECT t.*, t0.root
          FROM cte AS t0
          JOIN tenant_category_transaction_view AS t
            ON t.idParentCategory = t0.id
     )
SELECT root
     , MIN(t2.idParentCategory) AS idParentCategory
     , MIN(t2.sumSubtotal)      AS sumSubtotal
     , MIN(t2.sumQuantity)      AS sumQuantity
     , SUM(t1.sumSubtotal)      AS total
  FROM cte AS t1
  JOIN tenant_category_transaction_view AS t2
    ON t1.root = t2.id
 GROUP BY root
 ORDER BY root
;

结果:

root idParentCategory sumSubtotal sumQuantity total
99 null 0 0 9890
1 99 9800 98 9800
4 99 20 1 90
5 4 30 1 70
6 5 40 1 40

此外,这可以写入基于 t2.id 的聚合,这是主键,由于功能依赖性,允许稍微简化。

WITH RECURSIVE cte AS (
        SELECT t.*, t.id AS root
          FROM tenant_category_transaction_view AS t
         UNION ALL
        SELECT t.*, t0.root
          FROM cte AS t0
          JOIN tenant_category_transaction_view AS t
            ON t.idParentCategory = t0.id
     )
SELECT t2.id
     , t2.idParentCategory
     , t2.sumSubtotal
     , t2.sumQuantity
     , SUM(t1.sumSubtotal)      AS total
  FROM cte AS t1
  JOIN tenant_category_transaction_view AS t2
    ON t1.root = t2.id
 GROUP BY t2.id
 ORDER BY t2.id
;

最后,我们可以通过在递归逻辑中携带其他根值来删除最后一个 JOIN:

WITH RECURSIVE cte AS (
        SELECT t.*, t.id AS root
             , idParentCategory AS idParentCategory0
             , sumSubtotal      AS sumSubtotal0
             , sumQuantity      AS sumQuantity0
          FROM tenant_category_transaction_view AS t
         UNION ALL
        SELECT t.* , t0.root
             , t0.idParentCategory0
             , t0.sumSubtotal0
             , t0.sumQuantity0
          FROM cte AS t0
          JOIN tenant_category_transaction_view AS t
            ON t.idParentCategory = t0.id
     )
SELECT root
     , MIN(idParentCategory0)   AS idParentCategory
     , MIN(sumSubtotal0)        AS sumSubtotal
     , MIN(sumQuantity0)        AS sumQuantity
     , SUM(t1.sumSubtotal)      AS total
  FROM cte AS t1
 GROUP BY root
 ORDER BY root
;

设置:

DROP TABLE IF EXISTS tenant_category_transaction_view;
CREATE TABLE tenant_category_transaction_view (
    id               int primary key
  , idParentCategory int
  , sumSubtotal      int
  , sumQuantity      int
);

INSERT INTO tenant_category_transaction_view VALUES
    (99, null,    0,  0)
  , ( 1,   99, 9800, 98)
  , ( 4,   99,   20,  1)
  , ( 5,    4,   30,  1)
  , ( 6,    5,   40,  1)
;

【讨论】:

  • 耶稣@乔恩阿姆斯特朗你粉碎了我的问题,我正要把我的电脑扔到窗边哈哈干得好!
【解决方案2】:

解决方案实际上非常简单:-)

  1. 在 CTE 内添加一个表示根节点的硬编码行(其中 id 为 NULL)。请注意,我们可以在 CTE 中使用多个锚定查询(不引用 CTE 名称的查询)。
  2. 创建从所有节点到其所有后代(包括它们自己)的所有路径。每条路径由一行表示,其中包含起始节点 id、后代节点 id 和后代节点的数量。请注意使用空安全相等运算符 (<=>)
  3. 按起始节点 ID 聚合,并为每个节点汇总其所有后代的数量

    附言
    “递归”查询没有递归。这是一个误导性的名称。这些实际上是迭代查询,而不是递归的。


    with recursive cte (root_id, id, sumSubtotal)  as 
    (
      select     null
                ,null
                ,0
      
      union all
      
      select     id
                ,id
                ,sumSubtotal
    
      from      tenant_category_transaction_view as tctv
      
      union all
      
      select     cte.root_id
                ,tctv.id
                ,tctv.sumSubtotal
    
      from      cte 
                join    tenant_category_transaction_view as tctv 
                on      tctv.idParentCategory <=> cte.id
    )
    select   root_id
            ,sum(sumSubtotal)
    from     cte
    group by root_id
    

    +---------+-------------+
    | root_id | sumSubtotal |
    +---------+-------------+
    | null    |        9890 |
    | 1       |        9800 |
    | 4       |          90 |
    | 5       |          70 |
    | 6       |          40 |
    +---------+-------------+
    

    fiddle

【讨论】:

  • 它也有效,感谢您的快速回答@David דודו Markovitz!
【解决方案3】:

感谢这个伟大的解决方案,它对我的​​情况有很大帮助。

不过我确实有一个问题,在我的用例中,有几个节点没有 idParentCategory,在这种情况下,解决方案似乎分崩离析。我尝试了几种不同的方法,例如在“联合”顶部进行查询以获取 idParentCategory=null 问题,但到目前为止我还没有运气。关于在这种情况下我应该做什么的任何想法?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-25
    • 2010-10-02
    • 2022-01-27
    • 2017-09-19
    • 1970-01-01
    • 2012-02-10
    • 2022-06-10
    • 1970-01-01
    相关资源
    最近更新 更多