2013-05-20 41 views
0

首先,我非常熟悉LINQ查詢,但是在編寫直接SQL查詢時是一個完全新手。通過CTE通過LINQ進行Sql遞歸SelectMany

我希望能夠做到以下幾點:

  • 對於任何給定的項目Id,遍歷它的子項,直到非常基礎的子項已經被選中。

每個項目都屬於一個容器,它具有指定的基礎容器Id(或父容器Id)(如果它是基本容器,則爲NULL)。一個容器只能有一個父容器,但它可以有多個子容器。

目前我一直在做類似如下:

using (MyEntities db = new MyEntities()) 
{ 
    var theItem = db.Find(itemId); 
    var theContainer = theItem.Container.BaseContainer; 
    var theBaseItems = theItem.BaseItems.Where(bi => bi.ContainerId == theContainer.ContainerId).ToList(); 

    while (theContainer.BaseContainerId != null) 
    { 
     theContainer = theContainer.BaseContainer; 
     theBaseItems = theBaseItems.SelectMany(bi => bi.BaseItems.Where(i => i.ContainerId == theContainer.ContainerId)).ToList(); 
    } 
} 

這種運行良好和相當迅速,但是當數據筒是相當高的環比上漲,我注意到查詢到荒謬號該數據庫由SelectMany引起。例如,如果1000個項目屬於父容器中的100個項目,並且這100個項目屬於該容器父容器中的10個項目,並且最終那些屬於鏈接頂部的1個項目,則Select Many將運行10 + 100個查詢,每次取平均結果以檢索1000個基本項目 - 預計AFAIK。

因此,我懷疑(經過大量研究)Sql CTE可能是一個更好的選擇,不僅僅是輕輕點擊數據庫,但可能更快 - 這是一個糟糕的假設嗎?

然而,我仍然努力去處理CTE的語法,並希望有人能在我的問題上擺脫他們的智慧並幫助我。

重新創建方案

USE [TestDatabase] 
GO 
/****** Object: Table [dbo].[Containers] Script Date: 20/05/2013 14:17:20 ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TABLE [dbo].[Containers](
    [ContainerId] [int] IDENTITY(1,1) NOT NULL, 
    [BaseContainerId] [int] NULL, 
    [Name] [nvarchar](50) NOT NULL, 
CONSTRAINT [PK_Containers] PRIMARY KEY CLUSTERED 
(
    [ContainerId] 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 
/****** Object: Table [dbo].[ItemRelationships] Script Date: 20/05/2013 14:17:20 ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TABLE [dbo].[ItemRelationships](
    [ChildItemId] [int] NOT NULL, 
    [ParentItemId] [int] NOT NULL, 
CONSTRAINT [PK_ItemRelationships] PRIMARY KEY CLUSTERED 
(
    [ChildItemId] ASC, 
    [ParentItemId] 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 
/****** Object: Table [dbo].[Items] Script Date: 20/05/2013 14:17:20 ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TABLE [dbo].[Items](
    [ItemId] [int] IDENTITY(1,1) NOT NULL, 
    [ContainerId] [int] NOT NULL, 
    [Name] [nvarchar](50) 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 
SET IDENTITY_INSERT [dbo].[Containers] ON 

INSERT [dbo].[Containers] ([ContainerId], [BaseContainerId], [Name]) VALUES (1, NULL, N'Level 1') 
INSERT [dbo].[Containers] ([ContainerId], [BaseContainerId], [Name]) VALUES (2, 1, N'Level 2') 
INSERT [dbo].[Containers] ([ContainerId], [BaseContainerId], [Name]) VALUES (3, 1, N'Level 2b') 
INSERT [dbo].[Containers] ([ContainerId], [BaseContainerId], [Name]) VALUES (4, 2, N'Level 3') 
INSERT [dbo].[Containers] ([ContainerId], [BaseContainerId], [Name]) VALUES (5, NULL, N'TypeB') 
SET IDENTITY_INSERT [dbo].[Containers] OFF 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (1, 13) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (2, 13) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (3, 13) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (4, 14) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (5, 14) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (6, 14) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (7, 15) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (8, 15) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (9, 15) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (10, 16) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (11, 16) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (12, 16) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (13, 17) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (14, 17) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (15, 17) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (1007, 13) 
INSERT [dbo].[ItemRelationships] ([ChildItemId], [ParentItemId]) VALUES (1008, 17) 
SET IDENTITY_INSERT [dbo].[Items] ON 

INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (1, 1, N'A') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (2, 1, N'B') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (3, 1, N'C') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (4, 1, N'D') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (5, 1, N'E') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (6, 1, N'F') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (7, 1, N'G') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (8, 1, N'H') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (9, 1, N'I') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (10, 1, N'J') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (11, 1, N'K') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (12, 1, N'L') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (13, 2, N'A2') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (14, 2, N'A2') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (15, 2, N'C2') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (16, 3, N'D2B') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (17, 4, N'A3') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (1007, 5, N'TypeB1') 
INSERT [dbo].[Items] ([ItemId], [ContainerId], [Name]) VALUES (1008, 5, N'TypeB2') 
SET IDENTITY_INSERT [dbo].[Items] OFF 
ALTER TABLE [dbo].[Containers] WITH CHECK ADD CONSTRAINT [FK_Containers_Containers] FOREIGN KEY([BaseContainerId]) 
REFERENCES [dbo].[Containers] ([ContainerId]) 
GO 
ALTER TABLE [dbo].[Containers] CHECK CONSTRAINT [FK_Containers_Containers] 
GO 
ALTER TABLE [dbo].[ItemRelationships] WITH CHECK ADD CONSTRAINT [FK_ItemRelationships_ChildItems] FOREIGN KEY([ParentItemId]) 
REFERENCES [dbo].[Items] ([ItemId]) 
GO 
ALTER TABLE [dbo].[ItemRelationships] CHECK CONSTRAINT [FK_ItemRelationships_ChildItems] 
GO 
ALTER TABLE [dbo].[ItemRelationships] WITH CHECK ADD CONSTRAINT [FK_ItemRelationships_ParentItems] FOREIGN KEY([ChildItemId]) 
REFERENCES [dbo].[Items] ([ItemId]) 
GO 
ALTER TABLE [dbo].[ItemRelationships] CHECK CONSTRAINT [FK_ItemRelationships_ParentItems] 
GO 
ALTER TABLE [dbo].[Items] WITH CHECK ADD CONSTRAINT [FK_Items_Containers] FOREIGN KEY([ContainerId]) 
REFERENCES [dbo].[Containers] ([ContainerId]) 
ON UPDATE CASCADE 
ON DELETE CASCADE 
GO 
ALTER TABLE [dbo].[Items] CHECK CONSTRAINT [FK_Items_Containers] 
GO 
USE [master] 
GO 
ALTER DATABASE [TestDatabase] SET READ_WRITE 
GO 

下面的SQL查詢正確返回直接子項:

DECLARE @itemId BIGINT = 17; 

SELECT 
    Items.ItemId 
FROM 
    [TestDatabase].[dbo].[ItemRelationships] 
INNER JOIN 
    [TestDatabase].[dbo].Items 
ON 
    ItemRelationships.ChildItemId = Items.ItemId 
INNER JOIN 
    [TestDatabase].[dbo].[Containers] 
ON 
    Items.ContainerId = Containers.ContainerId 
WHERE 
    ItemRelationships.ParentItemId = @itemId 
AND 
    Items.ContainerId = 
    (
    SELECT 
     BaseContainerId 
    FROM 
     [TestDatabase].[dbo].[Items] 
    INNER JOIN 
     [TestDatabase].[dbo].[Containers] 
    ON 
     Items.ContainerId = Containers.ContainerId 
    WHERE 
     Items.ItemId = @itemId 
    ) 

結果

項目編號17屬於容器4 。集裝箱4的基地集裝箱是集裝箱2,集裝箱2的基地容器是容器1,容器1的基本容器是NULL。

上述查詢返回容器2中的ItemIds 13,14和15(這是正確的)。但是,我需要此查詢來自動然後查找容器2的基本容器並獲取Item的基本項的所有ItemId 13,14和15(在這種情況下應該產生項目ID 1到9)。

  • 作爲一個項可被附接(通過itemrelationships),以從容器無關項,當前項的檢查(或多個)容器的容器基必須存在。
  • 如果傳遞的ItemId位於基本容器內,則查詢應簡單地返回傳遞的ItemId。
  • CTE是首選,因爲結果實際上將用作針對另一個表的另一個查詢的一部分(但不在此問題的範圍之內)。

我希望有人能提前幫助和感謝您的努力。

回答

1

我不知道我完全理解你的模型和ItemRelationships表的位置,所以查詢可能需要一些調整 - 但它應該給你一個想法如何使用遞歸CTE。

DECLARE @itemID INT 
Set @itemID = 17 

;WITH CTE_Containers AS 
(
    SELECT c.ContainerId, c.BaseContainerId, i.ItemID AS ChildItemId, NULL AS ParentItemID, i.Name 
    FROM Items i 
    INNER JOIN Containers c ON i.ContainerId = c.ContainerId 
    WHERE i.ItemId = @itemID 

    UNION ALL 

    SELECT c.ContainerId, c.BaseContainerId, ir.ChildItemId, ir.ParentItemId, i.Name 
    FROM CTE_Containers cte 
    INNER JOIN dbo.Containers c ON cte.BaseContainerId = c.ContainerId 
    INNER JOIN dbo.ItemRelationships ir ON ir.ParentItemId = cte.ChildItemId 
    INNER JOIN dbo.Items i ON ir.ChildItemId = i.ItemID 
) 
SELECT * FROM CTE_Containers 

如您所見 - 遞歸CTE由兩部分組成。第一個(基本)部分 - 您爲給定的@itemID選擇行,並在第二個(遞歸)部分中將您的基本部分加入到表中以獲取子項。

這將運行,直到在遞歸部分中沒有任何選擇 - 或者您可能施加的其他某種條件得到滿足。

+0

非常有幫助,一個很好的開始 - 謝謝你。然而,我想確保的一些額外數據的測試在回報中避免了一些額外的記錄,這些記錄不應包含在最終結果中。我更新了數據庫創建腳本以提供這些記錄 - 實質上,應該不包含項目ID 1007和1008,因爲它們的容器不是項目標識17的容器層次結構的層次結構的一部分。註釋1007和1008鏈接到ID 13和17以模擬此。 –

+0

標記爲答案,雖然沒有提供所需的確切結果,但爲CTE提供了寶貴的見解(簡單解釋)。謝謝。 –

+0

@JonBellamy嗨,希望你能得到你需要的解決方案。對於不屬於子容器的子項目,請添加'WHERE c.ContainerId = i.ContainerId'到CTE的遞歸部分 –