递归CTE如何逐行运行?

时间:2010-07-06 15:45:57

标签: sql recursive-query

我认为我已经将递归CTE的格式设置得足够好以便写一个,但仍然发现自己感到沮丧,我不能手动处理一个(假装自己是SQL引擎并用笔到达结果集)和纸)。 I've found this,这与我正在寻找的相近,但不够详细。我没有问题跟踪C ++递归函数并了解它是如何运行的 - 但对于SQL我不明白为什么或如何引擎知道停止。每次调用锚点和递归块,还是在以后的迭代中跳过锚点? (我对此表示怀疑,但我试图表达我对它似乎跳转的方式的困惑。)如果每次调用锚点,锚点在最终结果中不会多次出现?我希望有人能够分解第1行第2行,等等。当结果集累积时,会发生什么以及“在内存中”。

我冒昧地偷了我的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 

5 个答案:

答案 0 :(得分:34)

想想一个递归的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会尝试禁止导致无限记录集的查询。

您可能想阅读这篇文章:

答案 1 :(得分:31)

CTE使用的算法是:

  1. 执行锚点部分,获得结果 r0
  2. 使用 r0 作为输入执行递归部分,并获得结果 r1 (非空)
  3. 使用 r1 作为输入执行递归部分,并获得结果 r2 (非空)
  4. 使用 r3 作为输入执行递归部分,并获得结果 r3 (非null) ...
  5. 使用 r(n-1)作为输入执行递归部分,并输出 rn (null)。这次 rn 为空,所以我们使用 UNION ALL 来组合 r0,r1,r2 ... r(n-1),那就是最终结果
  6. 让我们举一个例子:

    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
    

答案 2 :(得分:6)

我认为它会像这样崩溃:

  1. 执行锚语句。这会为您提供一组结果,称为基本集,或T0。

  2. 执行递归语句,使用T0作为执行查询的表。当您查询CTE时会自动发生这种情况。

  3. 如果递归成员返回某些结果,则会创建一个新集合T1。然后使用T1作为输入再次执行递归成员,如果有任何结果则创建T2。

  4. 继续执行步骤3,直到不再生成结果,或者达到最大递归次数,由MAX_RECURSION选项设置。

  5. This page最好解释一下。它有一个CTE执行路径的逐步演练。

答案 3 :(得分:1)

你可能想要this link。不,锚没有被执行多次(它不可能,然后union all将要求所有结果出现)。上一个链接的详细信息。

答案 4 :(得分: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步中的最后一列是Level。在每个级别中,相对于已有的行添加行。希望这会有所帮助。