SQL Server 2017 中每个级别的递归总和

Recursive Sum of each level in SQL Server 2017

我需要找出树的每个节点的递归和。请找到下面的图片。

这里蓝色的数字(假设没有销售)是 Parent 数量的总和 + 其 children 的总和,红色的数字是节点本身的销售。所以如果我们以 Node C = 44 为例,那么它的计算是这样的。

节点 C 有一个 child F 并且 F 又有两个children GHG & H 没有 children。但他们分别取得了 5 和 6 的销售额。另外他们ParentF也靠自己11的销量,所以F的总销量是22[ 11 个是他自己的,11 个来自他的 children]。同样在同一行CF的parent,也得到了22 sales他自己的。所以现在 C 的销售总数将是 44他自己的 2222 来自他的 child 和 grand children).

并且这种层次结构和计算将继续下去。层次结构的深度和广度也没有限制。一个节点可以有N个children.

我的数据库有 3 tables,如截图所示:

我想要实现的是,我需要一个递归查询来帮助找到我 TotalAmountQuantity of Sales 由每个商店制作,如图 1 [上] 所示。

我尝试使用 CTE,但没有获得所需的输出。这是我试过的

;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

第二次查询:

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

架构

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

完整查询

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

请帮我解决这个问题,因为这是为了提取我们的渠道合作伙伴支出,我不能花太多时间进行研发。这是一个严重的错误。

您正在寻找这样的东西吗?

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 S ON S.StoreId = C.SlaveID;

其中 @MasterStoreID 是您想要总销售额的商店的 ID。

编辑: 此查询将 return AmountPaidStoreMaster table 中的每个商店求和,不带 @MasterStoreID 参数:

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;