3

階層型CTEに厄介な問題があり、対処する必要のある奇妙なロジックがあります。CTEを使用してこのシナリオに対処するために、私が間違っていることを誰かが指摘するのを手伝ってくれることを本当に望んでいます。

この例で扱っている階層データは次のとおりです。 ここに画像の説明を入力してください

これは問題のある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

これは目的のデータを返していません。現在返されるデータは、下の画像の「正しくない」セクションにあります。Idが10の行は、省略したい行です。

基本的に、ロジックは、子のいずれかのステータスタイプが2に等しい親レコード(子を持つレコード)を子と一緒に返す必要があるというものである必要があります。この例では、これはIDが1、5、6、7、9の行です。

現在、CTE / SQL / Codeは、何があってもすべての親レコードを返します。

ID 1のレコードは、ステータスタイプが1であっても、その子、その子、孫などの少なくとも1つが2に等しいステータスタイプを持っているため、返される必要があります。

Idが10のレコードは、ステータスが2または子ではないため、返されません。子レコードがないときにレコードのステータスタイプが2の場合も、レコードを返す必要があります。

望ましくない結果と望ましい結果の例

これは、問題を示すのに役立つテストテーブルを作成するための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)
4

1 に答える 1

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
于 2012-06-29T15:28:20.403 に答える