Search code examples
sqlsql-serverdatabasereportingdatabase-administration

Hierarchical cumulative sum in sql query (id,parentId) structure


I have two tables that I want to get all accounts and their Credit sum and Debit sum even parent records have to sum thier child values

I'm implementing Id ParentId Structure .

1- [AccountChart] Table with these fields :

Id smallint
ParentId smallint Null
AccountName nvarchar(100)

2- [Transaction] Table with these fields

AccountId smallInt
TransactionDate DateTime
Debit decimal(19,5)
Credit decimal(19,5)

here is what I tried

    Create table [AccountChart] (
    id int not null,
    parentId int null,
    AccountName nvarchar(100)

    PRIMARY KEY (ID)
      )

    Create table [TransactionData] (
    id int not null,
    TransactionDate DateTime not null,
    AccountId int not null,
    Credit decimal(19,5),
    Debit decimal(19,5)

    PRIMARY KEY (ID)
    )

    insert into [AccountChart] (id,parentId,AccountName) values (1,null,'level 0');
    insert into [AccountChart] (id,parentId,AccountName) values (2,1,'level 2');
    insert into [AccountChart] (id,parentId,AccountName) values (3,2,'level 3 - 1');
    insert into [AccountChart] (id,parentId,AccountName) values (4,2,'level 3 - 2 ');


    insert into [TransactionData] (id,TransactionDate,AccountId,Credit,Debit) values (1,'2020-03-17',3,1000.0,0.0)
    insert into [TransactionData] (id,TransactionDate,AccountId,Credit,Debit) values (2,'2020-03-17',3,0.0,1000.0)
    insert into [TransactionData] (id,TransactionDate,AccountId,Credit,Debit) values (3,'2020-03-17',4,4000.0,0.0)
    insert into [TransactionData] (id,TransactionDate,AccountId,Credit,Debit) values (4,'2020-03-17',3,6000.0,0.0)
    insert into [TransactionData] (id,TransactionDate,AccountId,Credit,Debit) values (5,'2020-03-17',4,3000.0,0.0)


    ;WITH cteBalances (AccountId, ParentId, Credit, Debit)
    AS (SELECT
      AccountChart.Id,
      AccountChart.ParentId,
      SUM([TransactionData].Credit) AS Credit,
      SUM([TransactionData].Debit) AS Debit
     FROM [TransactionData]
     INNER JOIN AccountChart ON ([TransactionData].AccountId = AccountChart.Id)
     GROUP BY AccountChart.Id,AccountChart.ParentId)

    SELECT
      AccountChart.id,
      AccountChart.ParentId,
      AccountChart.AccountName,
      cteBalances.Credit,
      cteBalances.Debit
    FROM  AccountChart 
    left JOIN cteBalances ON (AccountChart.ID = cteBalances.AccountID)

the problem that I couldn't bring the summary values for the parent records.

the results I get is :

id  ParentId    AccountName   Credit        Debit
1   NULL        level 0       NULL          NULL
2   1           level 2       NULL          NULL
3   2           level 3 - 1   7000.00000    1000.00000
4   2           level 3 - 2   7000.00000    0.00000

but I excpect to be calculate the childs in parent as this

id  ParentId    AccountName   Credit        Debit
1   NULL        level 0       14000.00000   1000.00000
2   1           level 2       14000.00000   1000.00000
3   2           level 3 - 1   7000.00000    1000.00000
4   2           level 3 - 2   7000.00000    0.00000

Solution

  • I like the technique of using Range Keys. If you have a slow moving hierarchy, I would create a table to store these values

    Just to expand, the range keys facilitate selection, and variable depth aggregation.

    You may notice that the SEQ has an Order by AccountName. In my GL systems, we have a column for presentation sequence. This controls the order rather than ALPHA on name or significant digits on the account.

    Example

    Declare @Top  int         =  null   --<<  Sets top of Hier Try 2
    Declare @Nest varchar(25) = '|---'  --<<  Optional: Added for readability
    
    ;with cteP as (
          Select Seq  = cast(10000+Row_Number() over (Order by AccountName) as varchar(500))
                ,ID
                ,ParentID 
                ,Lvl=1
                ,AccountName
          From   [AccountChart] 
          Where  IsNull(@Top,-1) = case when @Top is null then isnull(ParentID ,-1) else ID end
          Union  All
          Select Seq  = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.AccountName)) as varchar(500))
                ,r.ID
                ,r.ParentID 
                ,p.Lvl+1
                ,r.AccountName
          From   [AccountChart] r
          Join   cteP p on r.ParentID  = p.ID)
         ,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
         ,cteR2 as (Select A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
    Select A.Lvl
          ,A.R1  
          ,B.R2
          ,A.ID
          ,A.ParentID 
          ,AccountName = max(Replicate(@Nest,A.Lvl-1) + A.AccountName)
          ,Credit      = sum(C.Credit)
          ,Debit       = sum(C.Debit)
     From cteR1 A
     Join cteR2 B on A.ID=B.ID
     Join (Select _R1=A.R1,B.* From cteR1 A Join [TransactionData] B on A.ID=B.AccountID ) C on (C._R1 between A.R1 and B.R2)
     Group By A.R1,B.R2,A.Lvl,A.ID,A.ParentID
     Order By A.R1
    

    Returns

    enter image description here