0

首先,我相当精通 LINQ 查询,但在编写直接 SQL 查询方面完全是新手。

我希望能够做到以下几点:

  • 对于任何给定的 ItemId,循环遍历它的子项,直到选择了最基础的子项。

每个项目都属于一个容器,该容器具有指定的基本(或父)容器 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 引起的对数据库的查询数量荒谬。例如,如果 1000 个项目属于父容器中的 100 个项目,而这 100 个项目属于该容器父容器中的 10 个项目,最后这 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
    )

结果

Item Id 17 属于容器 4。容器 4 的基础容器是容器 2,容器 2 的基础容器是容器 1,容器 1 的基础容器是 NULL。

上面的查询在容器 2 中返回 ItemIds 13、14 和 15(这是正确的)。但是我需要这个查询来自动查找容器 2 的基本容器并获取项目 13、14 的基本项目的所有 ItemId 和15(在这种情况下应该产生项目 ID 1 到 9)。

笔记

  • 由于可以(通过 itemrelationships)将项目附加到不相关容器中的项目,因此必须存在对当前项目容器的基础容器的检查。
  • 如果传递的 ItemId 在基本容器中,则查询应该简单地返回传递的 ItemId。
  • 首选 CTE,因为结果实际上将用作针对另一个表的另一个查询的一部分(但这超出了此问题的范围)。

我希望有人可以提供帮助,并提前感谢您的努力。

4

1 回答 1

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 选择行,在第二个(递归)部分中,您将基本部分加入表以获取子项。

这将一直运行,直到在递归部分没有选择任何内容 - 或者您可能施加的其他条件得到满足。

于 2013-05-20T10:13:06.140 回答