如何在SQL Server中获取分层CTE以使用父子逻辑进行过滤

时间:2012-06-28 04:58:23

标签: sql sql-server tsql common-table-expression

我遇到了一个令人烦恼的问题,分层CTE和一些我们需要解决的奇怪逻辑,我真的希望有人可以帮助指出我用CTE解决这个问题我做错了什么。

以下是我们在此示例中处理的分层数据: enter image description here

这是有问题的SQL,后面是问题描述和用于创建包含数据的测试表的SQL语句:

    DECLARE @UserId nvarchar(50);
    SET @UserId = 'A';

    DECLARE @StatusType int;
    SET @StatusType = '2';

     ;WITH recursiveItems (Id, Depth)
     AS
     (
        SELECT Id, 0 AS Depth 
        FROM dbo.CteTest 
        WHERE UserId = @UserId 
                    --AND StatusType = @StatusType
                    -- This would also be incorrect for the issue
        AND ParentId IS NULL
        UNION ALL
        SELECT dbo.CteTest.Id, Depth + 1 
        FROM dbo.CteTest 
            INNER JOIN recursiveItems 
                ON dbo.CteTest.ParentId = recursiveItems.Id
        WHERE UserId = @UserId 
        AND StatusType = @StatusType
     )

    SELECT A.*, recursiveItems.Depth
    FROM recursiveItems
    INNER JOIN dbo.CteTest A WITH(NOLOCK) ON
        recursiveItems.Id = A.Id
        ORDER BY A.Id

这不会返回所需的数据。当前返回的数据位于下图中的NOT CORRECT部分​​。 Id为10的行是我们要省略的行。

基本上逻辑应该是任何父级记录(带子级的记录),其中任何子级的状态类型等于2应该与其子级一起返回。在示例中,这是具有Ids的行:1,5,6,7,9。

目前CTE / SQL / Code正在返回所有父记录,无论如何,

应该返回Id 1的记录,即使它的状态类型是1,因为它的子项,子项,孙子项等中至少有一个的状态类型等于2.

不应返回ID为10的记录,因为它的状态不等于2或任何子级。如果记录的状态类型为2,当它没有子记录时,也应该返回。

Example of Not Desired and Desired Results

这是创建测试表的DDL,有助于显示问题:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[CteTest](
[Id] [int] IDENTITY(1,1) NOT NULL,
[StatusType] [int] NOT NULL,
[UserId] [nvarchar](50) NOT NULL,
[ParentId] [int] NULL,
 CONSTRAINT [PK_CteTest] PRIMARY KEY CLUSTERED 
(
[Id] 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

这是表格的种子数据,可以证明问题:

INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (1,'A',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (1,'B',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'B',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (1,'A',1)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'A',1)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'A',5)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'A',6)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (3,'A',6)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'A',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (4,'A',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (3,'A',10)

1 个答案:

答案 0 :(得分:4)

问题是您的基本案例包括所有null(无父对象)项目,并且以后无法将其过滤掉。

因为您只查找具有特定statustype的项目,所以您可能想要重构CTE;而不是将基本情况作为根值,您可以将其作为具有给定statustype的所有项目,然后以递归方式查找父项。在下面的解决方案中,我的深度是一个负数,与给定树中值为2的项目的距离(所以负高度,而不是深度。)。

DECLARE @UserId nvarchar(50);
SET @UserId = 'A';

DECLARE @StatusType int;
SET @StatusType = '2';

WITH recursiveItems (Id, ParentID, Depth)
 AS
 (
    SELECT Id, ParentID, 0 AS Depth 
    FROM dbo.CteTest 
    WHERE UserId = @UserId AND StatusType = @StatusType
    UNION ALL
    SELECT dbo.CteTest.Id, CteTest.ParentID, Depth - 1 
    FROM dbo.CteTest 
        INNER JOIN recursiveItems 
            ON dbo.CteTest.Id = recursiveItems.ParentId
    WHERE UserId = @UserId 
 )
     SELECT A.Id, A.StatusType, A.UserId, A.ParentId, min(recursiveItems.Depth)
FROM recursiveItems
INNER JOIN dbo.CteTest A WITH(NOLOCK) ON
    recursiveItems.Id = A.Id
    group by A.Id, A.StatusType, A.UserId, A.ParentId
    ORDER BY A.Id