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

Posted

技术标签:

【中文标题】SQL Server 2017 中每个级别的递归总和【英文标题】:Recursive Sum of each level in SQL Server 2017 【发布时间】:2022-01-23 22:47:18 【问题描述】:

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

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

节点 C 有一个孩子 F 并且 F 又有两个孩子 GH。 G & H 没有孩子。但他们分别取得了 5 和 6 的销售额。此外,他们的父母 F 自己也完成了 11 次销售,所以 F 的总销售额将是 22 [他自己的 11 次,他的孩子的 11 次]。同样在同一行中,F 的父级 C 也获得了自己的 22 个销售额。所以现在 C 的总销售额将是 44他自己的 22他的孩子和孙子的 22强>)。

这种层次结构和计算将继续进行。层次结构的深度和广度也没有限制。一个节点可以有 N 个子节点。

我的数据库有 3 个表格,如下图所示:

StoreMaster:此表包含所有商店的主列表。

StoreMaps:这是一个维护父子层次结构的映射表​​。这里MasterStoreId -> ParentIdSlaveStoreId -> ChildId。 masterstoreid 和 slavestoreid 都引用了 StoreMaster 表的 storeid。此外,此映射表支持任何层次结构中的 N 级子级。完全没有限制。

StoreSales:StoreMaster 的事务表。此表包含特定商店的销售额。

我想要实现的是,我需要一个递归查询,这将有助于找到每个商店制作的 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

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

【问题讨论】:

【参考方案1】:

您正在寻找类似的东西吗?

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。

编辑: 此查询将为StoreMaster 表中的每个商店返回AmountPaid sum,不带@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;

【讨论】:

谢谢亚当,稍微调整一下第一个查询就达到了我的预期。再次感谢。

以上是关于SQL Server 2017 中每个级别的递归总和的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 2017 高可用性

SQL SERVER 下:1递归查询父分类下的各个子分类。 2查询每个商品分类中最贵的前两个商品SQL

SQL Server 兼容性级别和基数估计

SQL Server 兼容性级别和基数估计

SQL Server 兼容性级别和基数估计

SQL Server 递归自联接