Home > Mobile >  Recursive Sum of each level in SQL Server 2017
Recursive Sum of each level in SQL Server 2017

Time:12-25

I need to find out the recursive sum of each node of a tree. Please find the image below.

Here the number(assuming no of sales made) in blue is the sum of Parent Quantity Sum of its children and number in red is sale made by node itself . So if we take example of Node C = 44 , then its calculation is something like this.

Node C has a child F and F has again two children G and H. G & H got no children. But they made 5 and 6 number of sales respectively. Also their Parent F too made 11 sales on his own, so total sales of F will be 22 [ 11 his own and 11 from his children]. Also in the same line C, the parent of F , also got 22 sales of his own. So now total number of sales made by C will be 44 (his own 22 and 22 from his child and grand children).

And this hierarchy and calculation will go on. There's no restriction in depth and breadth of hierarchy as well. A node can have N number of children.

enter image description here

My database has 3 tables as shown in this screenshot:

FIG-2

  • StoreMaster: this table contains a master list of all stores.

  • StoreMaps: this is a mapping table which maintains the parent child hierarchies. Here MasterStoreId -> ParentId and SlaveStoreId -> ChildId. Both masterstoreid and slavestoreid references to storeid of the StoreMaster table. Also, this maps table supports N level of children in any hierarchy. There's no restrictions at all.

  • StoreSales: transaction table for StoreMaster. This table contains store specific sales.

What I want to achieve is that I need a recursive query that will help finding me TotalAmount and Quantity of Sales made by each and every store as shown in figure 1 [above].

I tried using CTE but not getting the desired output. Here's what I tried

;WITH ChildStores AS 
(
    SELECT SlaveStoreId
    FROM StoreMaps
    WHERE SlaveStoreId = 35
        
    UNION ALL
        
    SELECT t.SlaveStoreId
    FROM StoreMaps t
    INNER JOIN ChildStores r ON t.MasterStoreId = r.SlaveStoreId
)
INSERT INTO @StoreTable
    SELECT *
    FROM ChildStores

SELECT DISTINCT ss.StoreId, ss.StoreCode, ss.StoreName  
FROM StoreSales ss 
WHERE ss.StoreId IN (SELECT * FROM @StoreTable)

SELECT ss.StoreName, COUNT(ss.SaleId), SUM(ss.AmountPaid) 
FROM StoreSales ss 
WHERE ss.StoreId IN (SELECT * FROM @StoreTable)  
  AND ss.SaleStatus = 'Paid' 
  AND ss.PlanCode <> '12345'
GROUP BY ss.StoreName

Second query:

WITH cteAggregateCost AS 
(  
    SELECT 
        i.SlaveStoreId AS rootid, i.SlaveStoreId, i.MasterStoreId
    FROM
        StoreMaps  i 

    UNION ALL

    SELECT 
        rootid, i.SlaveStoreId, i.MasterStoreId
    FROM
        StoreMaps i 
    JOIN
        cteAggregateCost c ON i.MasterStoreId = c.SlaveStoreId
)
SELECT
    t.StoreName, SUM(t.AmountPaid) AS AggregateCost
FROM
    StoreMaps a
LEFT JOIN
    cteAggregateCost i ON a.SlaveStoreId = i.rootid 
LEFT JOIN 
    StoreSales t ON i.SlaveStoreId = t.StoreId
WHERE 
    t.SaleStatus = 'Paid' 
    AND t.PlanCode <> 12345
GROUP BY
    t.StoreName

Schema

CREATE TABLE StoreMaps
(
    [StoreMapId] [int] IDENTITY(1,1) NOT NULL,
    [MasterStoreId] [int] NULL,
    [MasterStoreCode] [varchar](10) NULL,
    [SlaveStoreId] [int] NULL,
    [SlaveStoreCode] [varchar](10) NULL,
    [IsActive] [bit] NOT NULL,

    CONSTRAINT [PK_StoreMaps] 
        PRIMARY KEY CLUSTERED ([StoreMapId] ASC)
                WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                      IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                      ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE StoreMaster
(
    [StoreId] [int] IDENTITY(1,1) NOT NULL,
    [StoreName] [varchar](150) NULL,
    [StoreCode] [varchar](10) NULL,
    [IsActive] [bit] NOT NULL,
    
    CONSTRAINT [PK_StoreMaster] 
        PRIMARY KEY CLUSTERED ([StoreId] ASC)
                WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                      IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                      ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE StoreSales
(
    [SaleId] [int] IDENTITY(1,1) NOT NULL,
    [StoreId] [int] NULL,
    [StoreCode] [varchar](10) NULL,
    [StoreName] [varchar](150) NULL,
    [SaleStatus] [varchar](20) NULL,
    [AmountPaid] [int] NULL,
    [IsActive] [bit] NOT NULL,

    CONSTRAINT [PK_StoreSales] 
        PRIMARY KEY CLUSTERED ([SaleId] ASC)
                WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,  
                      IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                      ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

Full query

DECLARE @StoreCode VARCHAR(10) = NULL, 
        @StartDate DATE = NULL, @EndDate DATE = NULL

SET NOCOUNT ON;

DECLARE @Description VARCHAR(50), 
        @NetSales DECIMAL(16, 2), 
        @Quantity TINYINT, 
        @PayoutPercent TINYINT, @PayableAmount DECIMAL(16, 2), 
        @StoreName VARCHAR(150), @PartnerId TINYINT, 
        @Rowcount INT, @BillingMonth VARCHAR(10)

DECLARE @StoreTable AS TABLE (StoreId INT);

--INSERT INTO SpDebug (ObjectName, ObjectParam)
--VALUES (OBJECT_NAME(@@PROCID), @JsonParams)
IF ISNULL(TRIM(@StoreCode), '') = ''
BEGIN
    SET @StoreCode = 'GW'
END

IF ISNULL(@StartDate, '') = ''
BEGIN
    SET @StartDate = DATEADD(DD, - 7, GETDATE());
END

IF ISNULL(@EndDate, '') = ''
BEGIN
    --Select * from @StoreTable
    SET @EndDate = DATEADD(dd, - 1, GETDATE());
END

BEGIN TRY
    BEGIN TRANSACTION;

    WITH ChildStores
    AS (
        SELECT SlaveStoreId
        FROM StoreMaps
        WHERE SlaveStoreCode = @StoreCode
        
        UNION ALL
        
        SELECT t.SlaveStoreId
        FROM StoreMaps t
        INNER JOIN ChildStores r
            ON t.MasterStoreId = r.SlaveStoreId
        )
    INSERT INTO @StoreTable
    SELECT *
    FROM ChildStores

    SELECT @Rowcount = COUNT(*)
    FROM @StoreTable;

    WHILE (@Rowcount > 0)
    BEGIN
        SELECT @Rowcount = @Rowcount - 1;

        SELECT @PartnerId = StoreId
        FROM @StoreTable
        ORDER BY StoreId DESC OFFSET @Rowcount ROWS

        FETCH NEXT 1 ROWS ONLY;

        IF EXISTS (
                SELECT 1
                FROM StoreMaster
                WHERE StoreId = @PartnerId
                    AND IsActive = 1
                    AND ExcludeFromPayout = 0
                )
        BEGIN
            SELECT @PayoutPercent = PayoutPercent, @StoreName = StoreName, @StoreCode = StoreCode
            FROM StoreMaster
            WHERE StoreId = @PartnerId
            

            SET @Description = CONCAT ('Payout for ', FORMAT(@StartDate, 'dd-MMM-yyyy'), ' & ', FORMAT(@EndDate, 'dd-MMM-yyyy'));

            SELECT @Quantity = COUNT(SaleId), @NetSales = SUM(AmountPaid / 1.18)
            FROM StoreSales
            WHERE StoreId = @PartnerId
                AND (
                    CAST(ModifiedOn AS DATE) BETWEEN CAST(@StartDate AS DATE)
                        AND CAST(@EndDate AS DATE)
                    )
                AND SaleStatus = 'Paid'
                AND PlanCode <> '12345'

            IF NOT EXISTS (
                    SELECT *
                    FROM PartnerInvoices
                    WHERE (
                            (
                                @StartDate BETWEEN PayoutStartDate
                                    AND PayoutEndDate
                                )
                            OR (
                                @EndDate BETWEEN PayoutStartDate
                                    AND PayoutEndDate
                                )
                            )
                        AND PartnerId = @PartnerId
                    )
            BEGIN
                SET @Quantity = ISNULL(@Quantity, 0)
                SET @NetSales = ISNULL(@NetSales, 0)
                SET @PayableAmount = ISNULL((@NetSales * @PayoutPercent * 0.01), 0)
                SET @BillingMonth = FORMAT(GETDATE(), 'MMyyyy')

                INSERT INTO PartnerInvoices (PartnerId, StoreName, PayoutStartDate, PayoutEndDate, BillingMonth, TxnRefNumber, [Description], NetSales, Quantity, PayoutPercent, PayableAmount)
                VALUES (@PartnerId, @StoreName, @StartDate, @EndDate, @BillingMonth, '', @Description, @NetSales, @Quantity, @PayoutPercent, @PayableAmount)
            END
                    --ELSE
                    --BEGIN
                    --  PRINT 'Invoice already generated for this duration.';
                    --END
        END
    END

    COMMIT TRANSACTION

    SELECT 1;
END TRY

BEGIN CATCH
    ROLLBACK TRANSACTION

    INSERT INTO AppErrors (ErrorMessage, ObjectName, [Parameters])
    VALUES (ERROR_MESSAGE(), OBJECT_NAME(@@procid), @PartnerId);

    THROW
END CATCH

Please help me fixing this query as this is a for extracting our channel partners payout and i cant spend much time in having R&D. This is a critical bug.

CodePudding user response:

Is something like this what you're looking for?

WITH CTE AS (
    SELECT NULL AS MasterID, @MasterStoreID AS SlaveID
    UNION ALL
    SELECT M.MasterStoreId, M.SlaveStoreId AS SlaveID
    FROM CTE C
        INNER   JOIN StoreMaps M ON M.MasterStoreId = C.SlaveID
)
SELECT @MasterStoreID AS StoreID, SUM(ISNULL(S.AmountPaid, 0)) AS AmountPaidTotal
FROM CTE C
    LEFT    JOIN StoreSales ON S.StoreId = C.SlaveID;

Where @MasterStoreID is the ID of the store you want the total sales of.

Edit: This query will return AmountPaid sum for every store in StoreMaster table, without the @MasterStoreID parameter:

WITH CTE AS (
    SELECT StoreID AS RootID, NULL AS MasterID, StoreID AS SlaveID
    FROM StoreMaster
    UNION ALL
    SELECT C.RootID, M.MasterStoreid, M.SlaveStoreId AS SlaveID
    FROM CTE C
        INNER   JOIN StoreMaps M ON M.MasterStoreid = C.SlaveID
)
SELECT C.RootID AS StoreID, SUM(ISNULL(S.AmountPaid, 0)) AS AmountPaidTotal
FROM CTE C
    LEFT    JOIN StoreSales S ON S.StoreId = C.SlaveID
GROUP BY C.RootID;
  • Related