Во-первых, я довольно хорошо разбираюсь в запросах LINQ, но новичок в написании прямых SQL-запросов.Рекурсия Sql через CTE над LINQ SelectMany
Я хочу, чтобы иметь возможность сделать следующее:
- Для любого заданного ItemId, петля через это дочерние элементы до тех пор, пока не будут выбраны очень базовые элементы ребенка.
Каждый элемент принадлежит контейнеру, в котором указан идентификатор контейнера базы (или родительского) (или 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. Например, если 1000 элементов принадлежали 100 элементам в родительском контейнере, а эти 100 элементов принадлежали 10 элементам в родительском контейнере контейнеров, и, наконец, эти 10 принадлежат одному элементу в верхней части цепочки, 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 Query правильно возвращает непосредственные дочерние элементы:
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
)
Результаты
Item Id 17 принадлежит к контейнеру 4 Контейнер 4 контейнера Контейнер - контейнер 2, основание контейнера 2 контейнер - контейнер 1, а контейнер контейнера 1 - NULL.
Вышеуказанный запрос возвращает ItemIds 13, 14 и 15 (что является правильным) в контейнере 2. Однако мне нужен этот запрос, чтобы автоматически искать базовый контейнер для контейнера 2 и получить все ItemId для базовых элементов Items 13, 14 и 15 (в этом сценарии должны указываться идентификаторы с 1 по 9).
Примечание
- Как элемент может быть присоединен (через itemrelationships) к элементам из неродственных контейнеров, проверка для текущего элемента базового контейнер (ов) контейнера должна присутствовать.
- Если ItemId прошел в базовом контейнере, тогда запрос должен просто вернуть переданный ItemId.
- CTE предпочтительнее, поскольку результаты будут фактически использоваться как часть другого запроса к другой таблице (но это выходит за рамки этого вопроса).
Я надеюсь, что кто-то может помочь и поблагодарить вас заранее за ваши усилия.
Очень полезно, и отличное начало - спасибо. Однако при тестировании с некоторыми дополнительными данными, которые я хочу обеспечить, во избежание возврата возвращается несколько дополнительных записей, которые не должны включаться в окончательные результаты. Я обновил сценарий создания базы данных для предоставления этих записей - по существу, идентификаторы предметов 1007 и 1008 не должны включаться, так как их контейнер не является частью иерархии иерархии контейнеров Item ID 17. Примечание 1007 и 1008 связаны с идентификаторами 13 и 17, чтобы имитировать это. –
Отмечено в качестве ответа, в то же время не доставляя точных результатов, предоставляя ценную информацию (с простым объяснением) в CTE. Спасибо. –
@JonBellamy Привет, надеюсь, что у вас есть решение, в котором вы нуждаетесь. Для exculding дочерних элементов, не принадлежащих дочерним контейнерам, - добавьте 'WHERE c.ContainerId = i.ContainerId' в рекурсивную часть CTE –