我按照图表进行了数据库设计。
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
基本上应该产生这样的结果。
答案 0 :(得分:2)
经过大量研究和使用测试数据,我能够从层次结构底部开始获得运行总计。
解决方案由两个步骤组成。
创建一个标量值函数,用于决定categoryId是否是另一个categoryId的直接子项或间接子项。这是在第一个代码片段中给出的。请注意,递归查询用于此,因为这是在SQL Server中处理层次结构时的最佳方法。
根据您对所有类别的要求,编写将提供总计的运行总计查询。如果要对此查询进行排序,可以按类别进行筛选。第二个代码段提供此查询。
标量值函数,用于指示子类别是否为其他类别的直接或间接子级
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];