sqlsql-serverhierarchical-data

Query to calculate SUM of below top level Childs calculated amount


I have a Table TestPoolCalc, I need to show current node amount + sum of below top level Childs calculated amount. Please see the expected output

Table Script and Data

 CREATE TABLE [dbo].[_TestPoolCalc](
    [PoolID] [varchar](50) NULL,
    [ParentPoolID] [varchar](50) NULL,
    [Amount] [numeric](18, 2) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 1
', N'ROOT', NULL)
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 1.1
', N'Pool 1
', NULL)
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 1.1.1
', N'Pool 1.1
', NULL)
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 1.1.1.1
', N'Pool 1.1.1
', CAST(-12500.00 AS Numeric(18, 2)))
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 1.1.1.2
', N'Pool 1.1.1
', CAST(-12500.00 AS Numeric(18, 2)))
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 1.1.2
', N'Pool 1.1
', CAST(-25000.00 AS Numeric(18, 2)))
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 1.2
', N'Pool 1
', CAST(25000.00 AS Numeric(18, 2)))
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 1.3', N'Pool 1
', CAST(-50000.00 AS Numeric(18, 2)))
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 2', N'ROOT', NULL)
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 2.1', N'Pool 2', NULL)
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 2.2', N'Pool 2', NULL)
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'Pool 2.3', N'Pool 2', CAST(75000.00 AS Numeric(18, 2)))
GO
INSERT [dbo].[_TestPoolCalc] ([PoolID], [ParentPoolID], [Amount]) VALUES (N'ROOT', NULL, NULL)
GO

Way I tried

  WITH p AS (SELECT            
   a.ParentPoolID, a.PoolID 
   , CAST(a.PoolID AS VARCHAR(MAX)) AS path    
   , len(CAST(a.PoolID AS VARCHAR(MAX))) lpath    
   , a.Amount    
   
  FROM _TestPoolCalc a    
  WHERE a.ParentPoolID = 'Root'    
        UNION ALL    
  SELECT    
   pp.ParentPoolID, pp.PoolID   
   , p_2.path + '>' + pp.PoolID AS path    
   , len(p_2.path + '>' + pp.PoolID) lpath    
   , pp.Amount   
    
  FROM _TestPoolCalc pp     
     
  JOIN p AS p_2 ON pp.ParentPoolID = p_2.PoolID 
  )    
SELECT PoolID,ParentPoolID, path    
,Amount
,isnull((select sum(isnull(p1.Amount,0.0)) from p p1 where left(p1.path,p.lpath) = p.path and p.poolid <> p1.PoolId ),0) CalculatedAmount     
FROM  p
order by path

Expected output

# PoolID (A) Amount (B) Calculated Amount (C)
1 Pool 1 NULL -75000 B1+C2+C7+C8
2 Pool 1.1 NULL -50000 B2+C3+C6
3 Pool 1.1.1 NULL -25000 B3+C4+C5
4 Pool 1.1.1.1 -12500.00 -12500 B4
5 Pool 1.1.1.2 -12500.00 -12500 B5
6 Pool 1.1.2 -25000.00 -25000 B6
7 Pool 1.2 25000.00 25000 B7
8 Pool 1.3 -50000.00 -50000 B8
9 Pool 2 NULL 75000 B9+C10+C11+C12
10 Pool 2.1 NULL 0 B10
11 Pool 2.2 NULL 0 B11
12 Pool 2.3 75000 75000 B12

Solution

  • Using your test data:

    WITH p AS (
        SELECT  poolid AS main, t.Amount, t.PoolID AS parent
        FROM    _TestPoolCalc t
        UNION ALL
        SELECT  p.main, tc.amount, tc.poolid
        FROM    p
        INNER JOIN _TestPoolCalc tc
            ON  tc.ParentPoolID = p.parent
      )    
    SELECT  Main, ISNULL(SUM(Amount),0) AS total
    FROM    p
    GROUP BY Main
    

    For each node, i gather it and all the children under it. By keeping the PoolID as main, it becomes very easy to gather the final result, although, this code does traverse levels multiple times which can have some performance implications.

    Output:

    Main Total
    Pool 1 -75000
    Pool 1.1 -50000
    Pool 1.1.1 -25000
    Pool 1.1.1.1 -12500
    Pool 1.1.1.2 -12500
    Pool 1.1.2 -25000
    Pool 1.2 25000
    Pool 1.3 -50000
    Pool 2 75000
    Pool 2.1 0
    Pool 2.2 0
    Pool 2.3 75000
    ROOT 0

    You can always remove the ROOT if you're not interested in it.