2010-07-06 148 views
34

我認爲遞歸CTE的格式足夠寫下來,但仍然發現自己無法手動處理它(假裝自己是SQL引擎並使用筆訪問結果集,紙)。 I've found this,這與我正在尋找的接近,但不夠詳細。我沒有問題通過C++遞歸函數進行追蹤並理解其運行方式 - 但對於SQL,我不明白引擎知道爲什麼或如何停止。是否每次都調用錨點和遞歸塊,還是在稍後的迭代中跳過錨點? (我懷疑它,但我試圖表達我對它似乎跳躍的方式的困惑。)如果每次都調用錨點,那麼錨點在最終結果中不會多次出現?我希望有人可以做一個細分第1行第2行,等等。結果集累積會發生什麼以及「內存中」是什麼。遞歸CTE如何逐行運行?

我偷了我的example from this page的自由,因爲它似乎是最容易理解的。

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 

回答

31

,想出一遞歸CTE爲環形UNION ALL的:

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會嘗試禁止導致無限記錄集的查詢。

您可能需要閱讀這篇文章:

+0

可愛的解釋Quassnoi! – Baaju 2010-07-06 16:05:05

+0

優秀示範! – 2016-12-12 09:45:03

1

你很可能希望this link。不,錨點不會執行多次(不可能,那麼union all會要求所有結果都出現)。在上一個鏈接的細節。

6

我認爲它打破了這樣的:執行

  1. 錨聲明。這給你一組結果,稱爲基本集合,或T0。

  2. 執行遞歸語句,使用T0作爲表來執行查詢。這在查詢CTE時會自動發生。

  3. 如果遞歸成員返回一些結果,它會創建一個新的集合T1。遞歸成員然後再次執行,使用T1作爲輸入,如果有任何結果,則創建T2。

  4. 繼續執行第3步,直到沒有更多結果生成,或者符合MAX_RECURSION選項設置的遞歸最大數目。

This page可能是最好的解釋。它有一個循序漸進的CTE執行路徑。

+0

這是我們三個人現在鏈接到這篇文章:-) – 2010-07-06 15:59:37

1

步驟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中的最後一列是級別。在每個級別中,行將相對於已經可用的內容添加。希望這可以幫助。

27

的算法CTE使用方法是:

  1. 執行錨部分,得到導致R0
  2. 執行遞歸部分,使用R0作爲輸入,並得到導致R1(未null)
  3. 執行遞歸部分,使用r1作爲輸入,並得到結果r2(非空)
  4. 執行遞歸部分,使用R3作爲輸入,並取得導致R3(NOT NULL) ...
  5. 執行遞歸部分,使用R(N-1)作爲輸入,輸出(null)。這一次RN是空的,所以我們用UNION ALLR0,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