2

私の以前の編集は少し混乱していました。うまくいけば、これで解決します...

TL/DR -- 2 つのスクリプト ブロックをコピーして実行するだけで明らかになります。


カスケード データについて質問があります。基本的に、いくつかの事前定義された条件 (以下) に従って、ウォーターフォール効果でデータを下に移動しようとしています。私は 18 のシナリオのうち 15 を解決し、残りの 3 つのシナリオ (GID の 9、10、および 18 のシナリオ) を手伝っています。

ちょっとした見方をすると、私が取り組んでいるシステムでは、データは継続的にシステムにインポートされます。データはまばらで、完全なデータ セットを再構成してインポート プロセスを完了する作業を行っています。システム内のデータの形状、または提供されているデータの形状をほとんど制御できません:-/

最終的な問題は、以下の 5 つのカスケード ルールを満たすにはどうすればよいか、または、以下のスクリプトで提供したテスト ケース 18 をどのように解決するかということです。

カスケード ルール
この簡略化されたシナリオでは、カスケードの「ルール」は次のとおりです。

  • データは同じグループ内でのみカスケードされます ( GID)
  • データのグループは 1 から順に並べられます ( Seq)
  • IsLive列は 1 または 0 のいずれかになります
  • 次に、別の値またはnull以外の値がIsLive = 1見つかるまで、データを行の下に移動しますIsLive = 1IsLive = 0
  • 次に、別の値IsLive = 0にヒットするまで、データを行の下に移動しIsLive = 0ます。

注: 私のスクリプトは単純化された例ですが、完全なシナリオではN、カスケードする必要がある列があります。

ソリューション ノート
以下の SQL を実行すると、Input、Output - CTE の結果、Expected - 期待される結果、Result - 合格/不合格の 3 つの列が表示されます。サンプル テーブルを作成し、実行するだけでテスト ケースを説明するスクリプトを含めました。

  • 以下のテストケーススクリプトにはサンプルデータがあります
  • テスト ケース スクリプトには、正しい期待値のために追加した列があります。(INSERT スクリプトで GID=18 を探します。)

誰かが助けてくれることを願っています。そうでない場合は、SQL CLR SP ソリューションに頼る必要があるかもしれません。また、私はこの解決策に縛られていません。私の解決策を完全に破棄して、何か新しいものを考え出すこともできます。

テストケース

DECLARE @Test TABLE (GID int, Seq int, IsLive bit, 
                     Eff date, 
                     Name varchar(50), 
                     Expected varchar(50)) -- expected val should help debug!

INSERT INTO @Test VALUES (1, 1, 1, '01-08-2012', 'RTS', 'RTS')
INSERT INTO @Test VALUES (1, 2, 0, '01-09-2012', 'RTA', 'RTA')
INSERT INTO @Test VALUES (1, 3, 1, '01-10-2012', 'FSA', 'RTA')
INSERT INTO @Test VALUES (1, 4, 0, '01-11-2012',  NULL, 'RTA')
INSERT INTO @Test VALUES (1, 5, 1, '01-12-2012', 'FSA', 'RTA')
INSERT INTO @Test VALUES (2, 1, 1, '01-08-2012', 'RTS', 'RTS')
INSERT INTO @Test VALUES (2, 2, 0, '01-09-2012', 'RTA', 'RTA')
INSERT INTO @Test VALUES (2, 3, 1, '01-10-2012', 'FSA', 'RTA')
INSERT INTO @Test VALUES (2, 4, 0, '01-11-2012', 'GSM', 'GSM')
INSERT INTO @Test VALUES (2, 5, 1, '01-12-2012', 'FSA', 'GSM')
INSERT INTO @Test VALUES (3, 1, 1, '01-01-2012', 'FSA', 'FSA')
INSERT INTO @Test VALUES (3, 2, 0, '01-02-2012', NULL, 'FSA')
INSERT INTO @Test VALUES (4, 1, 1, '01-01-2012', NULL, NULL)
INSERT INTO @Test VALUES (4, 2, 0, '01-02-2012', 'FSA', 'FSA')
INSERT INTO @Test VALUES (4, 3, 0, '01-03-2012', NULL, 'FSA')
INSERT INTO @Test VALUES (5, 1, 0, '01-01-2012', NULL, NULL)
INSERT INTO @Test VALUES (5, 2, 1, '01-02-2012', 'LSI', 'LSI')
INSERT INTO @Test VALUES (5, 3, 0, '01-03-2012', NULL, 'LSI')
INSERT INTO @Test VALUES (6, 1, 1, '01-01-2012', NULL, NULL)
INSERT INTO @Test VALUES (6, 2, 0, '01-02-2012', 'LSI', 'LSI')
INSERT INTO @Test VALUES (6, 3, 1, '01-03-2012', NULL, 'LSI')
INSERT INTO @Test VALUES (7, 1, 1, '01-01-2012', 'FSA', 'FSA')
INSERT INTO @Test VALUES (7, 2, 0, '01-02-2012', NULL, 'FSA')
INSERT INTO @Test VALUES (7, 3, 1, '01-03-2012', 'RTA', 'RTA')
INSERT INTO @Test VALUES (8, 1, 1, '01-01-2012', 'FSA', 'FSA')
INSERT INTO @Test VALUES (8, 2, 0, '01-02-2012', NULL, 'FSA')
INSERT INTO @Test VALUES (8, 3, 1, '01-03-2012', NULL, NULL)
INSERT INTO @Test VALUES (9, 1, 1, '01-01-2012', 'FSA', 'FSA')
INSERT INTO @Test VALUES (9, 2, 1, '01-02-2012', NULL, NULL)
INSERT INTO @Test VALUES (9, 3, 1, '01-03-2012', 'RTS', 'RTS')
INSERT INTO @Test VALUES (10, 1, 1, '01-01-2012', 'FSA','FSA')
INSERT INTO @Test VALUES (10, 2, 1, '01-02-2012', 'GSM','GSM')
INSERT INTO @Test VALUES (10, 3, 1, '01-03-2012', 'RTS','RTS')  
INSERT INTO @Test VALUES (11, 1, 0, '01-01-2012', 'NOP','NOP')
INSERT INTO @Test VALUES (11, 2, 1, '01-02-2012', 'TAP','NOP')
INSERT INTO @Test VALUES (11, 3, 1, '01-03-2012', 'STG','NOP')
INSERT INTO @Test VALUES (12, 1, 1, '01-01-2012', 'RTS','RTS')
INSERT INTO @Test VALUES (12, 2, 0, '01-02-2012', 'RTM','RTM')
INSERT INTO @Test VALUES (12, 3, 1, '01-03-2012', 'LSA','RTM')
INSERT INTO @Test VALUES (12, 4, 1, '01-03-2012', 'LSA','RTM')
INSERT INTO @Test VALUES (12, 5, 1, '01-03-2012', 'GSM','RTM')
INSERT INTO @Test VALUES (13, 1, 1, '01-08-2012', 'BAR','BAR')
INSERT INTO @Test VALUES (13, 2, 0, '01-09-2012', NULL, 'BAR')
INSERT INTO @Test VALUES (13, 3, 1, '01-10-2012', 'TST','TST')
INSERT INTO @Test VALUES (14, 1, 1, '01-08-2012', 'BAR','BAR')
INSERT INTO @Test VALUES (14, 2, 0, '01-09-2012', 'GIP','GIP')
INSERT INTO @Test VALUES (14, 3, 1, '01-10-2012', 'TST','GIP')
INSERT INTO @Test VALUES (15, 1, 1, '01-01-2012', 'BAR','BAR')
INSERT INTO @Test VALUES (15, 2, 0, '01-02-2012', 'BAR','BAR')
INSERT INTO @Test VALUES (15, 3, 1, '01-02-2012', 'BAR','BAR')
INSERT INTO @Test VALUES (15, 4, 1, '01-02-2012', 'GYM','BAR')
INSERT INTO @Test VALUES (16, 1, 1, '01-02-2012', 'BAR','BAR')
INSERT INTO @Test VALUES (16, 2, 0, '01-03-2012', NULL, 'BAR')
INSERT INTO @Test VALUES (16, 3, 1, '01-03-2012', 'BAR','BAR')
INSERT INTO @Test VALUES (16, 4, 1, '01-03-2012', 'GYM','GYM')
INSERT INTO @Test VALUES (17, 1, 1, '01-02-2012', 'BAR', 'BAR')
INSERT INTO @Test VALUES (17, 2, 0, '01-03-2012', 'GIP', 'GIP')
INSERT INTO @Test VALUES (17, 3, 0, '01-03-2012', NULL,  'GIP') 
INSERT INTO @Test VALUES (17, 4, 1, '01-03-2012', 'TST', 'GIP') 

-- -------------------------------------------
-- Following is the GID=18 test case that fails
-- -------------------------------------------
INSERT INTO @Test VALUES (18, 1, 1, '01-02-2012', 'BAR', 'BAR')
INSERT INTO @Test VALUES (18, 2, 0, '01-03-2012', 'BAR', 'BAR')
INSERT INTO @Test VALUES (18, 3, 0, '01-03-2012', NULL, 'BAR') 
INSERT INTO @Test VALUES (18, 4, 1, '01-03-2012', 'TST', 'BAR') 

解決

DECLARE @PrevNonLiveSeq int = NULL

;WITH CTE AS ( 
    SELECT  T.GID, T.SEQ, T.IsLive, Expected
            , Name AS Name
            , CASE WHEN T.IsLive = 0 THEN T.SEQ ELSE NULL END As PrevNonLiveSeq
            , CASE WHEN T.IsLive = 1 THEN T.SEQ ELSE NULL END As PrevLiveSeq
            , NULL AS PerNonLiveSeqCalc
            , NULL AS PerLiveSeqCalc
            , 0 PrevSeq
            , CAST(NULL AS varchar(50)) PrevName
    FROM    @Test T
    WHERE   T.Seq = 1
    UNION ALL 
    SELECT  Curr.GID, Curr.SEQ, Curr.IsLive, Curr.Expected
            ,CASE   WHEN Curr.IsLive = 0 THEN ISNULL(Curr.Name, Prev.Name) 
                    ELSE CASE   WHEN PrevNonLive.Name IS NULL THEN 
                                    CASE WHEN Prev.Name <> PrevLive.Name THEN Prev.Name ELSE Curr.Name END
                                ELSE Prev.Name END
             END

            ,CASE WHEN Curr.IsLive = 0 THEN Curr.SEQ ELSE Prev.PrevNonLiveSeq END As PrevNonLiveSeq
            ,CASE WHEN Curr.IsLive = 1 THEN Curr.SEQ ELSE Prev.PrevLiveSeq END As PrevLiveSeq
            , ISNULL(Prev.PrevNonLiveSeq, Curr.SEQ) AS PerNonLiveSeqCalc
            , ISNULL(Prev.PrevLiveSeq, Curr.SEQ) AS PerLiveSeqCalc
            , Prev.Seq PrevSeq, Prev.Name PrevName
    FROM    CTE Prev 
    JOIN    @Test Curr ON Curr.GID = Prev.GID AND Curr.SEQ = Prev.SEQ+1
    JOIN    @Test PrevNonLive ON Prev.GID = PrevNonLive.GID AND PrevNonLive.SEQ = ISNULL(Prev.PrevNonLiveSeq, Curr.SEQ)
    JOIN    @Test PrevLive ON Prev.GID = PrevLive.GID AND PrevLive.SEQ = ISNULL(Prev.PrevLiveSeq, Curr.SEQ)
) 
SELECT CTE.GID, CTE.Seq, T.IsLive
        , T.Name Input, CTE.Name [Output]
        , CASE WHEN CTE.Name = CTE.Expected OR (CTE.Name IS NULL AND CTE.Expected IS NULL) THEN 'Pass' ELSE 'FAIL' END AS Result
        , CTE.Expected
FROM CTE 
INNER JOIN @Test T on CTE.GID = T.GID AND CTE.Seq = T.Seq
ORDER BY CTE.GID, CTE.Seq

結果

結果を得るには、コピーして SSMS で実行してください

ありがとう!

4

1 に答える 1

1

これはうまくいくはずで、再帰的な CTE は必要ありません。「カスケード」したい実際のフィールドごとに COALESCE を実行するだけで済みます。

SELECT crrnt.*, COALESCE(cscd.Name, crrnt.Name) AS [Output]
FROM @Test crrnt
OUTER APPLY (
            SELECT  TOP 1 *
            FROM    @Test prir
            WHERE   prir.GID = crrnt.GID
            AND     prir.Seq < crrnt.Seq
            AND     (
                        (
                            crrnt.IsLive = 1
                        AND prir.IsLive = 0
                        AND prir.Name IS NOT NULL
                        )
                    OR  (
                            crrnt.IsLive = 0 
                        AND crrnt.Name IS NULL
                        AND (
                                (
                                    prir.IsLive = 0
                                AND prir.Name IS NOT NULL
                                )
                            OR  (
                                    prir.IsLive = 1
                                AND NOT EXISTS(
                                                SELECT  *
                                                FROM    @Test confirm
                                                WHERE   confirm.GID = prir.GID
                                                AND     confirm.Seq < prir.Seq
                                                AND     confirm.IsLive = 0
                                                AND     confirm.Name IS NOT NULL
                                            )
                                )
                            )
                        )
                    )
            ORDER BY    prir.Seq DESC
            ) cscd

編集:
一般に、クエリのパフォーマンスをテストすることをお勧めします。そのため、次のようにします。テストは次のように構成さ
れます。 1. 最初に投稿されたクエリとサンプル データから開始します
。 2. 一時変数を一時テーブルに変更します (クエリは最終的に実際のユーザー テーブルにヒットします)
。 3. 一時テーブルにクラスター化インデックスを作成します。
4. データを複製しますが、GID 値は高くなります (18 行を 6,300,063 行に変換します)
。 5. DBCC FREEPROCCACAHE と DBCC DROPCLEANBUFFERS を使用して同等の環境を確保します
。 6. STATISTICS IO と STATISTICS TIME を使用 します。

SET NOCOUNT ON
-- DROP TABLE #Test
IF (OBJECT_ID('tempdb.dbo.#Test') IS NULL)
BEGIN
    CREATE TABLE #Test (GID INT NOT NULL, Seq INT NOT NULL, IsLive BIT NOT NULL, 
                     Eff date, 
                     Name varchar(50), 
                     Expected varchar(50), -- expected val should help debug!
                     PRIMARY KEY(GID, Seq)
                     )

INSERT INTO #Test VALUES (1, 1, 1, '01-08-2012', 'RTS', 'RTS')
INSERT INTO #Test VALUES (1, 2, 0, '01-09-2012', 'RTA', 'RTA')
INSERT INTO #Test VALUES (1, 3, 1, '01-10-2012', 'FSA', 'RTA')
INSERT INTO #Test VALUES (1, 4, 0, '01-11-2012',  NULL, 'RTA')
INSERT INTO #Test VALUES (1, 5, 1, '01-12-2012', 'FSA', 'RTA')
INSERT INTO #Test VALUES (2, 1, 1, '01-08-2012', 'RTS', 'RTS')
INSERT INTO #Test VALUES (2, 2, 0, '01-09-2012', 'RTA', 'RTA')
INSERT INTO #Test VALUES (2, 3, 1, '01-10-2012', 'FSA', 'RTA')
INSERT INTO #Test VALUES (2, 4, 0, '01-11-2012', 'GSM', 'GSM')
INSERT INTO #Test VALUES (2, 5, 1, '01-12-2012', 'FSA', 'GSM')
INSERT INTO #Test VALUES (3, 1, 1, '01-01-2012', 'FSA', 'FSA')
INSERT INTO #Test VALUES (3, 2, 0, '01-02-2012', NULL, 'FSA')
INSERT INTO #Test VALUES (4, 1, 1, '01-01-2012', NULL, NULL)
INSERT INTO #Test VALUES (4, 2, 0, '01-02-2012', 'FSA', 'FSA')
INSERT INTO #Test VALUES (4, 3, 0, '01-03-2012', NULL, 'FSA')
INSERT INTO #Test VALUES (5, 1, 0, '01-01-2012', NULL, NULL)
INSERT INTO #Test VALUES (5, 2, 1, '01-02-2012', 'LSI', 'LSI')
INSERT INTO #Test VALUES (5, 3, 0, '01-03-2012', NULL, 'LSI')
INSERT INTO #Test VALUES (6, 1, 1, '01-01-2012', NULL, NULL)
INSERT INTO #Test VALUES (6, 2, 0, '01-02-2012', 'LSI', 'LSI')
INSERT INTO #Test VALUES (6, 3, 1, '01-03-2012', NULL, 'LSI')
INSERT INTO #Test VALUES (7, 1, 1, '01-01-2012', 'FSA', 'FSA')
INSERT INTO #Test VALUES (7, 2, 0, '01-02-2012', NULL, 'FSA')
INSERT INTO #Test VALUES (7, 3, 1, '01-03-2012', 'RTA', 'RTA')
INSERT INTO #Test VALUES (8, 1, 1, '01-01-2012', 'FSA', 'FSA')
INSERT INTO #Test VALUES (8, 2, 0, '01-02-2012', NULL, 'FSA')
INSERT INTO #Test VALUES (8, 3, 1, '01-03-2012', NULL, NULL)
INSERT INTO #Test VALUES (9, 1, 1, '01-01-2012', 'FSA', 'FSA')
INSERT INTO #Test VALUES (9, 2, 1, '01-02-2012', NULL, NULL)
INSERT INTO #Test VALUES (9, 3, 1, '01-03-2012', 'RTS', 'RTS')
INSERT INTO #Test VALUES (10, 1, 1, '01-01-2012', 'FSA','FSA')
INSERT INTO #Test VALUES (10, 2, 1, '01-02-2012', 'GSM','GSM')
INSERT INTO #Test VALUES (10, 3, 1, '01-03-2012', 'RTS','RTS')  
INSERT INTO #Test VALUES (11, 1, 0, '01-01-2012', 'NOP','NOP')
INSERT INTO #Test VALUES (11, 2, 1, '01-02-2012', 'TAP','NOP')
INSERT INTO #Test VALUES (11, 3, 1, '01-03-2012', 'STG','NOP')
INSERT INTO #Test VALUES (12, 1, 1, '01-01-2012', 'RTS','RTS')
INSERT INTO #Test VALUES (12, 2, 0, '01-02-2012', 'RTM','RTM')
INSERT INTO #Test VALUES (12, 3, 1, '01-03-2012', 'LSA','RTM')
INSERT INTO #Test VALUES (12, 4, 1, '01-03-2012', 'LSA','RTM')
INSERT INTO #Test VALUES (12, 5, 1, '01-03-2012', 'GSM','RTM')
INSERT INTO #Test VALUES (13, 1, 1, '01-08-2012', 'BAR','BAR')
INSERT INTO #Test VALUES (13, 2, 0, '01-09-2012', NULL, 'BAR')
INSERT INTO #Test VALUES (13, 3, 1, '01-10-2012', 'TST','TST')
INSERT INTO #Test VALUES (14, 1, 1, '01-08-2012', 'BAR','BAR')
INSERT INTO #Test VALUES (14, 2, 0, '01-09-2012', 'GIP','GIP')
INSERT INTO #Test VALUES (14, 3, 1, '01-10-2012', 'TST','GIP')
INSERT INTO #Test VALUES (15, 1, 1, '01-01-2012', 'BAR','BAR')
INSERT INTO #Test VALUES (15, 2, 0, '01-02-2012', 'BAR','BAR')
INSERT INTO #Test VALUES (15, 3, 1, '01-02-2012', 'BAR','BAR')
INSERT INTO #Test VALUES (15, 4, 1, '01-02-2012', 'GYM','BAR')
INSERT INTO #Test VALUES (16, 1, 1, '01-02-2012', 'BAR','BAR')
INSERT INTO #Test VALUES (16, 2, 0, '01-03-2012', NULL, 'BAR')
INSERT INTO #Test VALUES (16, 3, 1, '01-03-2012', 'BAR','BAR')
INSERT INTO #Test VALUES (16, 4, 1, '01-03-2012', 'GYM','GYM')
INSERT INTO #Test VALUES (17, 1, 1, '01-02-2012', 'BAR', 'BAR')
INSERT INTO #Test VALUES (17, 2, 0, '01-03-2012', 'GIP', 'GIP')
INSERT INTO #Test VALUES (17, 3, 0, '01-03-2012', NULL,  'GIP') 
INSERT INTO #Test VALUES (17, 4, 1, '01-03-2012', 'TST', 'GIP') 

-- -------------------------------------------
-- Following is the GID=18 test case that fails
-- -------------------------------------------
INSERT INTO #Test VALUES (18, 1, 1, '01-02-2012', 'BAR', 'BAR')
INSERT INTO #Test VALUES (18, 2, 0, '01-03-2012', 'BAR', 'BAR')
INSERT INTO #Test VALUES (18, 3, 0, '01-03-2012', NULL, 'BAR') 
INSERT INTO #Test VALUES (18, 4, 1, '01-03-2012', 'TST', 'BAR') 

CHECKPOINT

INSERT INTO #Test (GID, Seq, IsLive, Eff, Name, Expected)
    SELECT  tmp.GID + (multiplier.Num * 20) AS [GID], tmp.Seq, tmp.IsLive, tmp.Eff, tmp.Name, tmp.Expected
    FROM    #Test tmp
    CROSS JOIN (
                    SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS [Num]
                    FROM    master.sys.objects so1
                    CROSS JOIN  master.sys.objects so2
                    CROSS JOIN  master.sys.objects so3
                ) multiplier
    WHERE   multiplier.Num <= 100000

CHECKPOINT

SELECT COUNT(*) FROM #Test

ALTER INDEX ALL ON #Test REBUILD

-- SELECT TOP 1000 * FROM #Test ORDER BY GID, Seq

END /* IF (OBJECT_ID('tempdb.dbo.#Test') IS NULL) */
-----------------------------------------------------------------------------

DBCC FREEPROCCACHE WITH NO_INFOMSGS
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS

PRINT '-- Original solution (Recursive CTE):'
PRINT ''

SET STATISTICS IO ON
SET STATISTICS TIME ON

;WITH CTE AS ( 
    SELECT  T.GID, T.SEQ, T.IsLive, Expected
            , Name AS Name
            , CASE WHEN T.IsLive = 0 THEN T.SEQ ELSE NULL END As PrevNonLiveSeq
            , CASE WHEN T.IsLive = 1 THEN T.SEQ ELSE NULL END As PrevLiveSeq
            , NULL AS PerNonLiveSeqCalc
            , NULL AS PerLiveSeqCalc
            , 0 PrevSeq
            , CAST(NULL AS varchar(50)) PrevName
    FROM    #Test T
    WHERE   T.Seq = 1
    UNION ALL 
    SELECT  Curr.GID, Curr.SEQ, Curr.IsLive, Curr.Expected
            ,CASE   WHEN Curr.IsLive = 0 THEN ISNULL(Curr.Name, Prev.Name) 
                    ELSE CASE   WHEN PrevNonLive.Name IS NULL THEN 
                                    CASE WHEN Prev.Name <> PrevLive.Name THEN Prev.Name ELSE Curr.Name END
                                ELSE Prev.Name END
             END

            ,CASE WHEN Curr.IsLive = 0 THEN Curr.SEQ ELSE Prev.PrevNonLiveSeq END As PrevNonLiveSeq
            ,CASE WHEN Curr.IsLive = 1 THEN Curr.SEQ ELSE Prev.PrevLiveSeq END As PrevLiveSeq
            , ISNULL(Prev.PrevNonLiveSeq, Curr.SEQ) AS PerNonLiveSeqCalc
            , ISNULL(Prev.PrevLiveSeq, Curr.SEQ) AS PerLiveSeqCalc
            , Prev.Seq PrevSeq, Prev.Name PrevName
    FROM    CTE Prev 
    JOIN    #Test Curr ON Curr.GID = Prev.GID AND Curr.SEQ = Prev.SEQ+1
    JOIN    #Test PrevNonLive ON Prev.GID = PrevNonLive.GID AND PrevNonLive.SEQ = ISNULL(Prev.PrevNonLiveSeq, Curr.SEQ)
    JOIN    #Test PrevLive ON Prev.GID = PrevLive.GID AND PrevLive.SEQ = ISNULL(Prev.PrevLiveSeq, Curr.SEQ)
) 
SELECT CTE.GID, CTE.Seq, T.IsLive
        , T.Name Input, CTE.Name [Output]
        , CASE WHEN CTE.Name = CTE.Expected OR (CTE.Name IS NULL AND CTE.Expected IS NULL) THEN 'Pass' ELSE 'FAIL' END AS Result
        , CTE.Expected
FROM CTE 
INNER JOIN #Test T on CTE.GID = T.GID AND CTE.Seq = T.Seq
ORDER BY CTE.GID, CTE.Seq

SET STATISTICS TIME OFF
SET STATISTICS IO OFF

PRINT '=================================================='
------------------------------------------------------

DBCC FREEPROCCACHE WITH NO_INFOMSGS
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS

PRINT '-- Proposed solution (OUTER APPLY):'
PRINT ''

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT crrnt.GID, crrnt.Seq, crrnt.IsLive,
        COALESCE(cscd.Name, crrnt.Name) AS [Output],
        CASE
            WHEN COALESCE(COALESCE(cscd.Name, crrnt.Name), '~~~') = COALESCE(crrnt.Expected, '~~~') THEN 'Pass'
            ELSE 'FAIL'
        END AS [Result],
        crrnt.Expected
FROM #Test crrnt
OUTER APPLY (
            SELECT  TOP 1 *
            FROM    #Test prir
            WHERE   prir.GID = crrnt.GID
            AND     prir.Seq < crrnt.Seq
            AND     (
                        (
                            crrnt.IsLive = 1
                        AND prir.IsLive = 0
                        AND prir.Name IS NOT NULL
                        )
                    OR  (
                            crrnt.IsLive = 0 
                        AND crrnt.Name IS NULL
                        AND (
                                (
                                    prir.IsLive = 0
                                AND prir.Name IS NOT NULL
                                )
                            OR  (
                                    prir.IsLive = 1
                                AND NOT EXISTS(
                                                SELECT  *
                                                FROM    #Test confirm
                                                WHERE   confirm.GID = prir.GID
                                                AND     confirm.Seq < prir.Seq
                                                AND     confirm.IsLive = 0
                                                AND     confirm.Name IS NOT NULL
                                            )
                                )
                            )
                        )
                    )
            ORDER BY    prir.Seq DESC
            ) cscd

SET STATISTICS TIME OFF
SET STATISTICS IO OFF
-----------------------------------

上記のテストを実行すると、次のようになります。

  • 元のクエリ: CPU 時間 = 173031 ミリ秒、経過時間 = 252708 ミリ秒、論理読み取り = 97,538,739
  • 提案されたクエリ = CPU 時間 = 49125 ミリ秒、経過時間 = 74003 ミリ秒、論理読み取り = 17,747,775

したがって、元のクエリは、CPU と経過時間の両方で約 3.5 倍遅くなり、提案したクエリよりも約 5 倍多くの論​​理読み取りが行われます。再帰的な CTE には注意してください ;-)。

于 2013-02-15T08:00:06.317 に答える