web-dev-qa-db-ja.com

再帰CTEはどのように行ごとに実行されますか?

再帰的CTEの形式は十分に理解できていると思いますが、それでも手動で処理できないことに不満を感じています(自分でSQLエンジンのふりをして、ペンと紙で結果セットに到達します) 。 私はこれを見つけました 、これは私が探しているものに近いですが、十分に詳細ではありません。 C++再帰関数をトレースして、その実行方法を理解することに問題はありませんが、SQLの場合、エンジンが停止する理由や方法を理解していません。アンカーと再帰ブロックは毎回呼び出されますか、それとも後の反復でアンカーがスキップされますか? (疑わしいですが、ジャンプのように見える方法について混乱を表現しようとしています。)アンカーが毎回呼び出された場合、最終結果にアンカーが複数回表示されないようにするにはどうすればよいですか?誰かが1行目2行目などを分解できることを願っています。結果セットが蓄積されると、何が起こり、何が「メモリ内」にあるのかがわかります。

私は自分の このページの例 を盗む自由を取りました。それが最も理解しやすいように思われるからです。

DECLARE @tbl TABLE ( 
      Id INT 
    , [Name] VARCHAR(20) 
    , ParentId INT 
) 

INSERT INTO @tbl( Id, Name, ParentId ) 
VALUES 
     (1, 'Europe', NULL) 
    ,(2, 'Asia',   NULL) 
    ,(3, 'Germany',   1) 
    ,(4, 'UK',        1) 
    ,(5, 'China',     2) 
    ,(6, 'India',     2) 
    ,(7, 'Scotland',  4) 
    ,(8, 'Edinburgh', 7) 
    ,(9, 'Leith',     8)  
; 

WITH abcd 
    AS ( 
        -- anchor 
        SELECT  id, Name, ParentID, 
                CAST(Name AS VARCHAR(1000)) AS Path 
        FROM    @tbl 
        WHERE   ParentId IS NULL 
        UNION ALL 
          --recursive member 
        SELECT  t.id, t.Name, t.ParentID, 
                CAST((a.path + '/' + t.Name) AS VARCHAR(1000)) AS "Path"
        FROM    @tbl AS t 
                JOIN abcd AS a 
                  ON t.ParentId = a.id 
       )
SELECT * FROM abcd 
42
justin w

無限のUNION ALLのように再帰的なCTEを考えてみてください。

WITH    rows AS
        (
        SELECT  *
        FROM    mytable
        WHERE   anchor_condition
        ),
        rows2 AS
        (
        SELECT  *
        FROM    set_operation(mytable, rows)
        ),
        rows3 AS
        (
        SELECT  *
        FROM    set_operation(mytable, rows2)
        ),
        …
SELECT  *
FROM    rows
UNION ALL
SELECT  *
FROM    rows2
UNION ALL
SELECT  *
FROM    rows3
UNION ALL
…

あなたの場合、それは次のようになります。

WITH    abcd1 AS
        ( 
        SELECT  *
        FROM    @tbl t
        WHERE   ParentId IS NULL 
        ),
        abcd2 AS
        ( 
        SELECT  t.*
        FROM    abcd1
        JOIN    @tbl t
        ON      t.ParentID = abcd1.id
        ),
        abcd3 AS
        ( 
        SELECT  t.*
        FROM    abcd2
        JOIN    @tbl t
        ON      t.ParentID = abcd2.id
        ),
        abcd4 AS
        ( 
        SELECT  t.*
        FROM    abcd3
        JOIN    @tbl t
        ON      t.ParentID = abcd3.id
        ),
        abcd5 AS
        ( 
        SELECT  t.*
        FROM    abcd4
        JOIN    @tbl t
        ON      t.ParentID = abcd4.id
        ),
        abcd6 AS
        ( 
        SELECT  t.*
        FROM    abcd5
        JOIN    @tbl t
        ON      t.ParentID = abcd5.id
        )
SELECT  *
FROM    abcd1
UNION ALL
SELECT  *
FROM    abcd2
UNION ALL
SELECT  *
FROM    abcd3
UNION ALL
SELECT  *
FROM    abcd4
UNION ALL
SELECT  *
FROM    abcd5
UNION ALL
SELECT  *
FROM    abcd6

abcd6は結果を生成しないため、これは停止状態を意味します。

理論的には、再帰的なCTEは無限大になる可能性がありますが、実際には、SQL Serverは無限のレコードセットにつながるクエリを禁止しようとします。

あなたはこの記事を読みたいと思うかもしれません:

39
Quassnoi

CTEが使用するアルゴリズムは次のとおりです。

  1. アンカー部分を実行し、結果を取得しますr
  2. rを入力として使用して再帰部分を実行し、結果を取得しますr1(nullではありません)
  3. r1を入力として使用して再帰部分を実行し、結果を取得しますr2(nullではありません)
  4. rを入力として使用して再帰部分を実行し、結果を取得しますr(nullではありません).。
  5. r(n-1)を入力として使用し、出力rn(null)を使用して、再帰部分を実行します。今回はrnがnullなので、NION ALLを使用してr0、r1、r2 ... r(n-1)を結合します。最終結果

例を見てみましょう:

WITH    cte ( value )
          AS (
               SELECT   1
               UNION ALL
               SELECT   value + 1
               FROM     cte
               WHERE    value < 4
             )
    SELECT  *
    FROM    cte

このクエリの結果は次のとおりです。

value
-----------
1
2
3
4

(4 row(s) affected)

それを段階的に調べてみましょう:

Execute anchor query (SELECT 1), we got:
  r0 = 1
  cte = r0 = 1

    |
    |
    V

Now we execute
SELECT value + 1 FROM cte WHERE value < 4
Since cte is r0 (only has 1), we got:
  r1 = 2
  cte = r1 = 2

    |
    |
    V

Now we execute
SELECT value + 1 FROM cte WHERE value < 4
Since cte is r1 (only has 2), we got:
  r2 = 3
  cte = r2 = 3

    |
    |
    V

Now we execute
SELECT value + 1 FROM cte WHERE value < 4
Since cte is r2 (only has 3), we got:
  r3 = 4
  cte = r3 = 4

    |
    |
    V

Now we execute
SELECT value + 1 FROM cte WHERE value < 4
Since cte is r3 (only has 4), we got:
  r4 = NULL (because r3 (4) is equal to 4, not less than 4)
Now we stop the recursion!

    |
    |
    V

Let's calculate the final result:
R = r0 union all
    r1 union all
    r2 union all
    r3 union all
  = 1 union all
    2 union all
    3 union all
    4 union all
  = 1
    2
    3
    4
37
Just a learner

私はそれがこのように崩壊すると思います:

  1. アンカーステートメントが実行されます。これにより、基本セットまたはT0と呼ばれる一連の結果が得られます。

  2. クエリを実行するテーブルとしてT0を使用して、再帰ステートメントが実行されます。これは、CTEを照会すると自動的に発生します。

  3. 再帰メンバーが何らかの結果を返す場合、新しいセットT1を作成します。次に、再帰メンバーが実行されますagain、入力としてT1を使用し、結果がある場合はT2を作成します。

  4. ステップ3は、結果が生成されなくなるまで続きます。OR MAX_RECURSIONオプションで設定された、再帰の最大数に達しました。

このページ おそらくそれを最もよく説明しています。 CTEの実行パスのステップバイステップのウォークスルーがあります。

7
womp

ステップ1:

1 Europe NULL Europe
2 Asia   NULL Asia

ステップ2:

1 Europe  NULL Europe
2 Asia    NULL Asia
3 Germany 1    Europe/Germany
4 UK      1    Europe/UK
5 China   2    Asia/China
6 India   2    Asia/India

ステップ3:

1 Europe   NULL Europe
2 Asia     NULL Asia
3 Germany  1    Europe/Germany
4 UK       1    Europe/UK
5 China    2    Asia/China
6 India    2    Asia/India
7 Scotland 4    Europe/UK/Scotland

ステップ4:

1 Europe    NULL Europe
2 Asia      NULL Asia
3 Germany   1    Europe/Germany
4 UK        1    Europe/UK
5 China     2    Asia/China
6 India     2    Asia/India
7 Scotland  4    Europe/UK/Scotland
8 Edinburgh 7    Europe/UK/Scotland/Edinburgh

ステップ5:

1 Europe    NULL Europe                             0
2 Asia      NULL Asia                               0
3 Germany   1    Europe/Germany                     1
4 UK        1    Europe/UK                          1
5 China     2    Asia/China                         1
6 India     2    Asia/India                         1
7 Scotland  4    Europe/UK/Scotland                 2
8 Edinburgh 7    Europe/UK/Scotland/Edinburgh       3
9 Leith     8    Europe/UK/Scotland/Edinburgh/Leith 4

ステップ5の最後の列はレベルです。各レベルで、すでに使用可能なものに関して行が追加されます。お役に立てれば。

1
Baaju

あなたはおそらく欲しかった このリンク 。いいえ、アンカーは複数回実行されません(実行できなかった場合は、union allすべての結果が表示される必要があります)。詳細は前のリンクにあります。

1
Donnie