SQL 列的服务器层次总和
SQL Server Hierarchical Sum of column
我按照图表设计了数据库。
Category
table是自引用父子关系
Budget
将为每个类别定义所有类别和金额
Expense
table 将包含已花费金额的类别的条目(考虑此 table 中的 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
基本上应该会产生这样的结果。
经过大量研究并使用测试数据,我能够从层次结构的底部开始获得 运行 总数。
解决方案由两个步骤组成。
创建一个 scalar-valued 函数来决定一个 categoryId 是另一个 categoryId 的直接子代还是间接子代。这是在第一个code-snippet中给出的。请注意,为此使用了递归查询,因为这是在 SQL 服务器中处理层次结构的最佳方法。
编写 运行 总计查询,该查询将根据您对所有类别的要求给出总计。如果您想对此查询进行过滤,可以按类别进行过滤。第二个代码片段提供了这个查询。
Scalar-valued 判断子类别是另一个类别的直接或间接子类别的函数
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;
这是一个有趣的问题。我将使用 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];
我按照图表设计了数据库。
Category
table是自引用父子关系Budget
将为每个类别定义所有类别和金额Expense
table 将包含已花费金额的类别的条目(考虑此 table 中的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
基本上应该会产生这样的结果。
经过大量研究并使用测试数据,我能够从层次结构的底部开始获得 运行 总数。
解决方案由两个步骤组成。
创建一个 scalar-valued 函数来决定一个 categoryId 是另一个 categoryId 的直接子代还是间接子代。这是在第一个code-snippet中给出的。请注意,为此使用了递归查询,因为这是在 SQL 服务器中处理层次结构的最佳方法。
编写 运行 总计查询,该查询将根据您对所有类别的要求给出总计。如果您想对此查询进行过滤,可以按类别进行过滤。第二个代码片段提供了这个查询。
Scalar-valued 判断子类别是另一个类别的直接或间接子类别的函数
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;
这是一个有趣的问题。我将使用 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];