SQL Server Hierarchical列的总和

时间:2016-01-06 05:44:04

标签: sql-server hierarchical-data recursive-query

我按照图表进行了数据库设计。

enter image description here

  • Category表是自引用父子关系
  • Budget将为每个类别定义所有类别和金额
  • Expense表将包含已花费金额的类别的条目(请参阅此表中的Total列。)

我想写一个select语句,它将使用下面给出的列检索数据集:

ID   
CategoryID   
CategoryName   
TotalAmount (Sum of Amount Column of all children hierarchy From BudgetTable  )   
SumOfExpense (Sum of Total Column of Expense  all children hierarchy from expense table)

我尝试使用CTE但无法产生任何有用的东西。感谢您的帮助。 :)

更新

我只是结合并简化了我用下面的查询创建了一个视图的数据。

SELECT        
    dbo.Budget.Id, dbo.Budget.ProjectId, dbo.Budget.CategoryId, 
    dbo.Budget.Amount, 
    dbo.Category.ParentID, dbo.Category.Name, 
    ISNULL(dbo.Expense.Total, 0) AS CostToDate
FROM
    dbo.Budget 
INNER JOIN
    dbo.Category ON dbo.Budget.CategoryId = dbo.Category.Id 
LEFT OUTER JOIN
    dbo.Expense ON dbo.Category.Id = dbo.Expense.CategoryId

基本上应该产生这样的结果。

enter image description here

2 个答案:

答案 0 :(得分:2)

经过大量研究和使用测试数据,我能够从层次结构底部开始获得运行总计。

解决方案由两个步骤组成。

  1. 创建一个标量值函数,用于决定categoryId是否是另一个categoryId的直接子项或间接子项。这是在第一个代码片段中给出的。请注意,递归查询用于此,因为这是在SQL Server中处理层次结构时的最佳方法。

  2. 根据您对所有类别的要求,编写将提供总计的运行总计查询。如果要对此查询进行排序,可以按类别进行筛选。第二个代码段提供此查询。

  3. 标量值函数,用于指示子类别是否为其他类别的直接或间接子级

    CREATE FUNCTION dbo.IsADirectOrIndirectChild(
                   @childId int, @parentId int)
    RETURNS int
    AS
    BEGIN
    
        DECLARE @isAChild int;
        WITH h(ParentId, ChildId)
        -- CTE name and columns
             AS (
             SELECT TOP 1 @parentId, @parentId
             FROM dbo.Category AS b
             UNION ALL
             SELECT b.ParentId, b.Id AS ChildId
             FROM h AS cte
                  INNER JOIN
                  Category AS b
                  ON b.ParentId = cte.ChildId AND
                     cte.ChildId IS NOT NULL)
             SELECT @isAChild = ISNULL(ChildId, 0)
             FROM h
             WHERE ChildId = @childId AND
                   ParentId <> ChildId
             OPTION(MAXRECURSION 32000);
        IF @isAChild > 0
        BEGIN
            SET @isAChild = 1;
        END;
        ELSE
        BEGIN
            SET @isAChild = 0;
        END;
        RETURN @isAChild;
    END;
    GO
    

    从层次结构底部开始查询运行总计

    SELECT c.Id AS CategoryId, c.Name AS CategoryName,
    (
        SELECT SUM(ISNULL(b.amount, 0))
        FROM dbo.Budget AS b
        WHERE dbo.IsADirectOrIndirectChild( b.CategoryId, c.Id ) = 1 OR
              b.CategoryId = c.Id
    ) AS totalAmount,
    (
        SELECT SUM(ISNULL(e.total, 0))
        FROM dbo.Expense AS e
        WHERE dbo.IsADirectOrIndirectChild( e.CategoryId, c.Id ) = 1 OR
              e.CategoryId = c.Id
    ) AS totalCost
    FROM dbo.Category AS c;
    

答案 1 :(得分:2)

这是一个有趣的问题。我将用hierarchyid解决它。首先,设置:

USE tempdb;
IF OBJECT_ID('dbo.Hierarchy') IS NOT NULL
    DROP TABLE dbo.[Hierarchy];

CREATE TABLE dbo.Hierarchy 
(
    ID INT NOT NULL PRIMARY KEY,
    ParentID INT NULL,
        CONSTRAINT [FK_parent] FOREIGN KEY ([ParentID]) REFERENCES dbo.Hierarchy([ID]),
    hid HIERARCHYID,
    Amount INT NOT null
);

INSERT INTO [dbo].[Hierarchy]
        ( [ID], [ParentID], [Amount] )
VALUES  
    (1, NULL, 100 ),
    (2, 1, 50),
    (3, 1, 50),
    (4, 2, 58),
    (5, 2, 7),
    (6, 3, 10),
    (7, 3, 20)
SELECT * FROM dbo.[Hierarchy] AS [h];

接下来,使用适当的hiearchyid值更新hid列。我将使用沼泽标准递归cte

WITH cte AS (
    SELECT  [h].[ID] ,
            [h].[ParentID] ,
            CAST('/' + CAST(h.[ID] AS VARCHAR(10)) + '/' AS VARCHAR(MAX)) AS [h],
            [h].[hid]
    FROM    [dbo].[Hierarchy] AS [h]
    WHERE   [h].[ParentID] IS NULL

    UNION ALL

    SELECT  [h].[ID] ,
            [h].[ParentID] ,
            CAST([c].[h] + CAST(h.[ID] AS VARCHAR(10)) + '/' AS VARCHAR(MAX)) AS [h],
            [h].[hid]
    FROM    [dbo].[Hierarchy] AS [h]
    JOIN    [cte] AS [c]
            ON [h].[ParentID] = [c].[ID]
)
UPDATE [h]
SET hid = [cte].[h]
FROM cte
JOIN dbo.[Hierarchy] AS [h]
    ON [h].[ID] = [cte].[ID];

现在已经完成了繁重的工作,你几乎可以获得所需的结果:

SELECT p.id, SUM([c].[Amount])
FROM dbo.[Hierarchy] AS [p]
JOIN [dbo].[Hierarchy] AS [c]
    ON c.[hid].IsDescendantOf(p.[hid]) = 1
GROUP BY [p].[ID];