【问题标题】:SQL Server CTE for Aggregating Costs at Each Level of Hierarchy用于在每个层次结构级别聚合成本的 SQL Server CTE
【发布时间】:2017-05-22 07:18:24
【问题描述】:

我不知道如何编写一个 CTE,它将成本从事务表汇总到自联接表,以便在层次结构的每个级别为我提供总成本。我整理了一个非常简单的例子来说明这个问题。 以下是 DDL 和插入脚本,以便您可以重现该问题,如果您愿意帮助我的话:

CREATE TABLE [Items](
    [ItemId] [int] IDENTITY(1,1) NOT NULL,
    [ParentId] [int] NULL,
    [ItemName] [varchar](100) NOT NULL,
 CONSTRAINT [PK_Items] PRIMARY KEY CLUSTERED 
(
    [ItemId] 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
CREATE TABLE [Transactions](
    [TransactionId] [int] IDENTITY(1,1) NOT NULL,
    [ItemId] [int] NOT NULL,
    [Amount] [money] NOT NULL,
 CONSTRAINT [PK_Transactions] PRIMARY KEY CLUSTERED 
(
    [TransactionId] 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
SET IDENTITY_INSERT [Items] ON 
GO
INSERT [Items] ([ItemId], [ParentId], [ItemName]) VALUES (1, NULL, N'Warehouse')
GO
INSERT [Items] ([ItemId], [ParentId], [ItemName]) VALUES (3, 1, N'Bin 1')
GO
INSERT [Items] ([ItemId], [ParentId], [ItemName]) VALUES (4, 1, N'Bin 2')
GO
INSERT [Items] ([ItemId], [ParentId], [ItemName]) VALUES (5, 3, N'Item 1.1')
GO
INSERT [Items] ([ItemId], [ParentId], [ItemName]) VALUES (6, 3, N'Item 1.2')
GO
INSERT [Items] ([ItemId], [ParentId], [ItemName]) VALUES (7, 4, N'Item 2.1')
GO
INSERT [Items] ([ItemId], [ParentId], [ItemName]) VALUES (8, 4, N'Item 2.2')
GO
SET IDENTITY_INSERT [Items] OFF
GO
SET IDENTITY_INSERT [Transactions] ON 
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (1, 5, 10.0000)
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (2, 5, 10.0000)
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (3, 6, 10.0000)
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (4, 6, 10.0000)
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (5, 4, 10.0000)
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (6, 7, 10.0000)
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (7, 7, 10.0000)
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (8, 8, 10.0000)
GO
INSERT [Transactions] ([TransactionId], [ItemId], [Amount]) VALUES (9, 8, 10.0000)
GO
SET IDENTITY_INSERT [Transactions] OFF
GO
ALTER TABLE [Items]  WITH CHECK ADD  CONSTRAINT [FK_Items_Items] FOREIGN KEY([ParentId])
REFERENCES [Items] ([ItemId])
GO
ALTER TABLE [Items] CHECK CONSTRAINT [FK_Items_Items]
GO
ALTER TABLE [Transactions]  WITH CHECK ADD  CONSTRAINT [FK_Transactions_Items] FOREIGN KEY([ItemId])
REFERENCES [Items] ([ItemId])
GO
ALTER TABLE [Transactions] CHECK CONSTRAINT [FK_Transactions_Items]
GO

这是我一直在研究的 CTE:

With cteAggregateCost
as  
(  
    select i.itemId, i.ParentId, t.Amount
    from Items i join Transactions t on i.ItemId = t.ItemId
union all
    select i.itemId, i.ParentId, t.Amount
    from Items i join cteAggregateCost c on i.ItemId = c.ParentId
    join Transactions t on i.ItemId = t.ItemId
)
select i.ParentId, i.ItemId, i.ItemName, sum(Amount) As AggregateCost
from Items i left join cteAggregateCost c on i.ItemId = c.ItemId
group by i.ParentId, i.ItemId, i.ItemName

这是我得到的结果:

这是我希望得到的结果:

如您所见,除前两行外,所有行都正常工作,这对容器没有成本,只有它们包含的项目。

非常感谢您提供的任何指导!

【问题讨论】:

  • 这些值从何而来? 90和40?对于 (itemid =1 and parentid = null)(itemid=3, parentid = 1) 的数据组合。在您提供的示例中,Transaction 表中的这些记录没有匹配值。
  • @Rigerta sum ItemId 的孩子。 ItemId 1 是 ItemId 3 和 4 的父级,总共有 40 和 50 个,其中 sum 最多 90 个。ItemId 3 的总共 40 个来自孩子 5 和 6 的 sum 20个。 ItemId 4 是 50,因为您需要包括两个孩子(ItemId 7 和 8)以及 ItemId 4 本身的单个事务。

标签: sql-server common-table-expression hierarchical-data


【解决方案1】:

你可以像这样使用recursive CTE

;WITH temp AS 
(
   SELECT i.*, sum(isnull(t.Amount,0)) AS Amount 
   FROM @Items i
   LEFT JOIN @Transactions t ON t.ItemId = i.ItemId
   GROUP BY i.ItemId, i.ParentId, i.ItemName
)
,cteAggregateCost
as  
(  
   select i.ItemId, i.ItemId AS RootId, i.Amount  
   from temp i     
union all
   select i.ItemId, c.RootId, i.Amount 
   from cteAggregateCost c
   INNER JOIN temp i ON i.ParentId = c.ItemId
)
select i.*, ca.TotalAmount
from  @Items i 
CROSS APPLY 
(
   SELECT Sum(cac.Amount) AS TotalAmount
   FROM cteAggregateCost cac WHERE i.ItemId = cac.RootId
) ca
OPTION (MAXRECURSION 0)

演示链接:http://rextester.com/XMK96314

【讨论】:

    【解决方案2】:

    谢谢你,TriV。你的回答太棒了!但是,我确实找到了Xi Jin on the SQL Server forum 发布的更简单的答案。这是他的解决方案:

        With cteAggregateCost
        as  
        (  
            select  i.itemId as rootid,i.itemid, i.ParentId
            from Items i 
            union all
            select rootid, i.itemId, i.ParentId
            from Items i join cteAggregateCost c on i.ParentId = c.ItemId
        )
    
        select a.parentid, a.ItemId , a.ItemName , sum(t.Amount) As AggregateCost
        from items a
        left join cteAggregateCost i on a.itemid = i.rootid 
        left join Transactions t on i.ItemId = t.ItemId
        group by a.parentid, a.ItemId, a.ItemName
    

    当针对具有多层层次关系的更大数据集进行测试时,这两种解决方案都给出了相同的正确结果。对我来说,西林的回答比较容易理解。我只是不知道如何添加 rootID 技术来保留没有自己成本且只有子项目成本的项目的值。

    【讨论】:

    • 使用 RootId 的目的是获取表中 每个 节点的所有子节点。它是通过递归 CTE 完成的,剩下的一件事是 left jointransactions 以保证左表中的所有项目都存在。您的第一个查询不正确,因为内部连接到 transactions 表。而recursive CTE不允许使用left join,所以必须移到外面。
    • 感谢 TriV。现在我看到了最终答案,这很有意义。非常感谢您的解释。
    猜你喜欢
    • 2013-11-07
    • 2017-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-30
    • 1970-01-01
    • 2021-05-19
    相关资源
    最近更新 更多