【问题标题】:How to calculate the sum of values in a tree using SQL如何使用 SQL 计算树中值的总和
【发布时间】:2008-09-18 10:04:26
【问题描述】:

我需要汇总用户树获得的每个级别的积分。级别 1 是用户下一级用户的用户积分之和。 Level 2 是比用户低 2 级的用户的 Level 1 点数,等等……

计算在非生产服务器上每月进行一次,不用担心性能。

SQL 会是什么样子?

如果你感到困惑,别担心,我也是!

用户表:

ID    ParentID    Points
1     0           230
2     1           150
3     0           80
4     1           110
5     4           54
6     4           342

Tree:
0
|---\
1    3
| \
2  4---
    \  \
     5  6

输出应该是:

ID    Points    Level1     Level2
1     230       150+110    150+110+54+342
2     150
3     80
4     110       54+342
5     54
6     342

SQL Server 语法和函数最好...

【问题讨论】:

    标签: sql tree


    【解决方案1】:

    如果您使用的是 Oracle DBMS,那将非常简单,因为 Oracle 支持使用 CONNECT BY/STARTS WITH 语法的树查询。对于 SQL Server,我认为您可能会发现 Common Table Expressions 很有用

    【讨论】:

      【解决方案2】:

      树不适用于 SQL。如果您有非常(非常非常)很少的写访问权限,您可以更改树实现以使用嵌套集,这将使此查询非常容易。

      示例(如果我没记错的话):

      SELECT SUM(points) 
      FROM users 
      where left > x and right < y 
      

      但是,对树进行任何更改都需要涉及大量行。最好只在您的客户端中进行递归。

      【讨论】:

        【解决方案3】:

        我会说:创建一个存储过程,可能性能最好。 或者,如果您有最大级别数,您可以创建子查询,但它们的性能会很差。

        (或者您可以获取 MS SQL Server 2008 并获取新的层次结构函数...;))

        【讨论】:

        • 我的想法也是如此,但程序会是什么样子?
        【解决方案4】:

        一般来说,SQL 就像其他人所说的那样,不能很好地处理这种关系。通常,需要一个代理“关系”表(id、parent_id、(id, parent_id) 上的唯一键),其中:

        • 每次在“表”中添加记录时,您:

          INSERT INTO relations (id, parent_id) VALUES ([current_id], [current_id]);

          INSERT INTO relations (id, parent_id) VALUES ([current_id], [current_parent_id]);

          INSERT INTO relations (id, parent_id) SELECT [current_id], parent_id FROM relations WHERE id = [current_parent_id];

        • 有避免循环的逻辑

        • 确保“关系”上的更新、删除由存储过程处理

        鉴于该表,您想要:

        SELECT rel.parent_id, SUM(tbl.points)
        FROM table tbl INNER JOIN relations rel ON tbl.id=rel.id
        WHERE rel.parent_id <> 0
        GROUP BY rel.parent_id;
        

        【讨论】:

          【解决方案5】:

          好的,这将为您提供您正在寻找的结果,但不能保证我没有错过任何内容。把它当作一个起点。我用SQL 2005来做这个,SQL 2000不支持CTE的

          WITH Parent (id, GrandParentId, parentId, Points, Level1Points, Level2Points)
          AS
          (
              -- Find root
              SELECT id,  
                      0 AS GrandParentId,
                      ParentId,
                      Points,
                      0 AS Level1Points,
                      0 AS Level2Points
              FROM tblPoints ptr
              WHERE ptr.ParentId = 0
          
              UNION ALL (
              -- Level2 Points
              SELECT pa.GrandParentId AS Id,
                      NULL AS GrandParentId,
                      NULL AS ParentId,
                      0 AS Points, 
                      0 AS Level1Points,
                      pa.Points  AS Level2Points
              FROM tblPoints pt
                      JOIN Parent pa ON pa.GrandParentId = pt.Id 
              UNION  ALL
              -- Level1 Points
              SELECT pt.ParentId AS Id,
                      NULL AS GrandParentId,
                      NULL AS ParentId,
                      0 AS Points, 
                      pt.Points AS Level1Points,
                      0 AS Level2Points
              FROM tblPoints pt
                      JOIN Parent pa ON pa.Id = pt.ParentId AND pa.ParentId IS NOT NULL 
              UNION  ALL
              -- Points
              SELECT pt.id,
                      pa.ParentId AS GrandParentId,
                      pt.ParentId,
                      pt.Points, 
                      0 AS Level1Points,
                      0 AS Level2Points
              FROM tblPoints pt
                      JOIN Parent pa ON pa.Id = pt.ParentId AND pa.ParentId IS NOT NULL )
          )
          SELECT id, 
              SUM(Points) AS Points,  
              SUM(Level1Points) AS Level1Points,
              CASE WHEN SUM(Level2Points) > 0 THEN  SUM(Level1Points) + SUM(Level2Points) ELSE 0 END AS Level2Points
          FROM Parent
          GROUP BY id 
          ORDER by id
          

          【讨论】:

            【解决方案6】:

            如果您正在使用存储在关系数据库中的树,我建议您查看“嵌套集”或“修改的预序树遍历”。 SQL 就这么简单:

            SELECT id, 
                   SUM(value) AS value 
            FROM table 
            WHERE left>left\_value\_of\_your\_node 
              AND right<$right\_value\_of\_your\_node;
            

            ...为您感兴趣的每个节点执行此操作。

            也许这会对您有所帮助: http://www.dbazine.com/oracle/or-articles/tropashko4 或使用谷歌。

            【讨论】:

              【解决方案7】:

              你有几个选择:

              1. 使用游标和递归的用户定义函数调用(很慢)
              2. 创建一个缓存表,在 INSERT 上使用触发器对其进行更新(这是最快的解决方案,但如果您对主表进行大量更新,则可能会出现问题)
              3. 做一个客户端递归计算(如果你没有太多记录最好)

              【讨论】:

                【解决方案8】:

                您可以编写一个简单的递归函数来完成这项工作。我的 MSSQL 有点生锈了,但它看起来像这样:

                CREATE FUNCTION CALC
                (
                @node integer,
                )
                returns 
                (
                @total integer
                )
                as
                begin
                    select @total = (select node_value from yourtable where node_id = @node);
                
                    declare @children table (value integer);
                    insert into @children   
                    select calc(node_id) from yourtable where parent_id = @node;
                
                    @current = @current + select sum(value) from @children;
                    return
                end
                

                【讨论】:

                • 好的,函数会是什么样子?
                • 我这里没有 MSSQL 安装,但它会类似于:getsum(parentNode int) sum = select value where node = parentNode; foreach row in select children from table where parent = parentNode sum = sum + getsum(childnode) 您可以在顶部节点上调用它。
                【解决方案9】:

                下表:

                Id   ParentId
                1   NULL
                11    1
                12    1
                110 11
                111 11
                112 11
                120 12
                121 12
                122 12
                123 12
                124 12
                

                以及以下金额表:

                Id     Val
                110 500
                111 50
                112 5
                120 3000
                121 30000
                122 300000
                

                只有叶子(最后一级)ID 有定义的值。 获取数据的 SQL 查询如下所示:

                ;WITH Data (Id, Val) AS
                (
                    select t.Id, SUM(v.val) as Val from dbo.TestTable t
                    join dbo.Amount v on t.Id = v.Id
                    group by t.Id
                )
                
                select cd.Id, ISNULL(SUM(cd.Val), 0) as Amount FROM
                (
                    -- level 3
                    select t.Id, d.val from TestTable t
                    left join Data d on d.id = t.Id
                
                    UNION
                
                    -- level 2
                    select t.parentId as Id, sum(y.Val) from TestTable t
                    left join Data y on y.id = t.Id
                    where t.parentId is not null
                    group by t.parentId
                
                    UNION
                
                    -- level 1
                    select t.parentId as Id, sum(y.Val) from TestTable t
                    join TestTable c on c.parentId = t.Id
                    left join Data y on y.id = c.Id
                    where t.parentId is not null
                    group by t.parentId
                ) AS cd
                group by id
                

                这会导致输出:

                Id     Amount
                1     333555
                11   555
                12   333000
                110 500
                111 50
                112 5
                120 3000
                121 30000
                122 300000
                123 0
                124 0
                

                我希望这会有所帮助。

                【讨论】:

                  猜你喜欢
                  • 2019-05-25
                  • 1970-01-01
                  • 2017-01-19
                  • 2016-05-31
                  • 2020-07-22
                  • 2019-05-05
                  • 2019-06-22
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多