0

먼저 LINQ 쿼리에 대해 잘 알고 있지만 직접적인 SQL 쿼리를 작성하는 완전한 초보자입니다.LINQ SelectMany를 통한 CTE를 통한 Sql 재귀

나는 다음을 수행 할 수 있도록하려면 : 매우 기본 하위 항목이 선택 될 때까지이 자식 항목의

주어진 항목 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(); 
    } 
} 

이것은 ContainerId 체인까지 매우 높은 그러나 경우, 벌금과 매우 빠른 실행, 나는 쿼리의 말도 안되는 숫자를 발견했습니다 SelectMany에 의해 야기 된 데이터베이스. 예를 들어 상위 컨테이너의 항목 100 개에 1000 개의 항목이 속하고 그 상위 컨테이너의 항목 10 개에 속하는 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의 기본 컨테이너를 찾고 항목의 기본 항목에 대한 모든 ItemId를 가져와야합니다 13, 14 및 15 (이 시나리오에서 Item Ids 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은 컨테이너가 항목 ID 17의 컨테이너 계층 구조의 계층이 아니므로 포함하면 안됩니다. 메모 1007과 1008은 ID 13과 17에 연결되어이를 시뮬레이션합니다. –

+0

정확한 결과를 제공하지는 못했지만 답변으로 표시되어 CTE에 대한 간단한 통찰력을 제공합니다. 고맙습니다. –

+0

@JonBellamy 안녕하세요, 스스로에게 필요한 해결책이 있으시길 바랍니다. 하위 컨테이너에 속하지 않은 자식 항목을 추출하는 경우 - CTE의 순환 부분에'WHERE c.ContainerId = i.ContainerId'를 추가하십시오 –