如何使用SQL计算树中值的总和

时间:2008-09-18 10:04:26

标签: sql tree

我需要在用户树所获得的每个级别上加分。级别1是用户1级以下用户的用户点数之和。 2级是用户等级2级以下用户的等级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语法和函数最好......

9 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

树与SQL不兼容。如果你有非常(非常非常)的写访问,你可以改变树实现以使用嵌套集,这将使这个查询非常容易。

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

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

但是,树上的任何更改都需要触摸大量行。在您的客户端进行递归可能更好。

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

如果您正在使用存储在关系数据库中的树,我建议您查看“嵌套集”或“修改前序树遍历”。 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或使用谷歌。

答案 4 :(得分:1)

像其他人说的那样,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 :(得分:1)

好的,这可以为您提供所需的结果,但无法保证我没有错过任何内容。认为这是一个起点。我使用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 :(得分:0)

您有几个选择:

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

答案 7 :(得分:0)

您可以编写一个简单的递归函数来完成这项工作。我的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

答案 8 :(得分:0)

下表:

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

以下Amount表:

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

我希望这会有所帮助。