简体   繁体   English

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

[英]How To Get A Hierarchical CTE In SQL Server To Filter With Parent and Child Logic

I'm having a vexing problem with a hierarchical CTE and some strange logic that we need to address that I really hope someone could assist with pointing out what I'm doing wrong to address this scenario with a CTE. 我有一个令人烦恼的问题,分层CTE和一些奇怪的逻辑,我们需要解决,我真的希望有人可以协助指出我做错了用CTE来解决这个问题。

Here is the hierarchical data we're dealing with in this example: 以下是我们在此示例中处理的分层数据: 在此输入图像描述

This is the problematic SQL followed by the description of the problem and SQL statements to create a test table with data: 这是有问题的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

This is not returning the desired data. 这不会返回所需的数据。 The data that is currently returned is in the NOT CORRECT section of the image below. 当前返回的数据位于下图中的NOT CORRECT部分​​。 The row with the Id of 10 is the row that we want to omit. Id为10的行是我们要省略的行。

Essentially the logic should be that any parent record (record with children) where the status type of any of its children is equal to 2 should be returned along with its children. 基本上逻辑应该是任何父级记录(带子级的记录),其中任何子级的状态类型等于2应该与其子级一起返回。 In the example this is the rows with Ids: 1, 5, 6, 7, 9. 在示例中,这是具有Ids的行:1,5,6,7,9。

Currently the CTE/SQL/Code is returning ALL parent records no matter what, 目前CTE / SQL / Code无论如何返回所有父记录,

The record with the Id 1 should be returned, even though it's status type is 1 because at least one of its children, their children, grandchildren, etc. have a status type that is equal to 2. 应返回Id 1的记录,即使其状态类型为1,因为其子项,子项,孙子项等中至少有一个状态类型等于2。

The record with the Id of 10 should not be returned because it does not have a status that is equal to 2 or any children. 不应返回Id为10的记录,因为它的状态不等于2或任何子项。 If the record had a status type of 2 when it has no child records it should also be returned. 如果记录在没有子记录时的状态类型为2,则还应返回该记录。

不期望和期望结果的示例

This is the DDL to create a test table that helps to show the problem: 这是创建测试表的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

This is the seed data for the table, that can demonstrate the issue: 这是表的种子数据,可以证明问题:

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)

The issue is that your base case includes all null (parentless) items, and there is no way to filter them out later. 问题是你的基本案例包括所有null(无父)项,并且以后无法过滤它们。

Because you are looking for only items with a particular statustype , you may want to refactor the CTE; 因为您只查找具有特定statustype项目,所以您可能想要重构CTE; Instead of having a base case be the root values, you can have it be all items with the given statustype , and then recursively find the parents. 而不是将基本情况作为根值,您可以将其作为具有给定statustype所有项目,然后递归地查找父项。 In the solution below, I have depth be a negative number, for distance from the item with a value of 2 in the given tree (so negative height, instead of depth.). 在下面的解决方案中,我的深度为负数,与给定树中值为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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM