如何使用SQL计算树中的值总和
我需要对用户树所获得的每个级别的积分进行求和。 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语法和功能最好...
解决方案
我会说:创建一个存储过程,可能具有最佳性能。
或者,如果我们具有最大数量的级别,则可以创建子查询,但是它们的性能会很差。
(或者,我们可以获取MS SQL Server 2008并获取新的层次结构函数...;))
如果我们使用的是Oracle DBMS,那将非常简单,因为Oracle支持使用CONNECT BY / STARTS WITH语法的树查询。对于SQL Server,我认为我们可能会发现公用表表达式很有用
树无法很好地与SQL配合使用。如果我们很少(非常)有写访问权限,则可以将树实现更改为使用嵌套集,这将使此查询变得异常简单。
示例(如果我没记错的话):
SELECT SUM(points) FROM users where left > x and right < y
但是,树上的任何更改都需要触摸大量的行。只在客户端中进行递归可能更好。
我们有两种选择:
- 使用游标和用户定义的递归函数调用(相当慢)
- 创建一个缓存表,使用触发器在INSERT上对其进行更新(这是最快的解决方案,但如果对主表进行了大量更新,则可能会出现问题)
- 进行客户端递归计算(如果我们没有太多记录,则更可取)
如果我们正在使用关系数据库中存储的树,则建议我们查看"嵌套集"或者"修改后的预排序树遍历"。 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或者使用google。
我们可以编写一个简单的递归函数来完成这项工作。我的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
就像其他人所说,SQL通常不能很好地处理这种关系。通常,需要一个替代"关系"表(id,parent_id,(id,parent_id)上的唯一键),其中:
- 每次在"表"中添加记录时,我们:
INSERT INTO关系(id,parent_id)VALUES([current_id],[current_id]); current_parent_id]);
插入到关系中(id,parent_id)SELECT [current_id],parent_id
FROM关系`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;
好的,这会为我们提供所需的结果,但是不能保证我不会错过任何东西。认为这是一个起点。我使用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
下表:
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
我希望这有帮助。