【问题标题】:How does a Recursive CTE run, line by line?递归 CTE 如何逐行运行?
【发布时间】:2010-07-06 15:45:57
【问题描述】:

我认为我已经很好地掌握了递归 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 

【问题讨论】:

    标签: sql recursive-query


    【解决方案1】:

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

    您可能想阅读这篇文章:

    【讨论】:

    • 可爱的解释 Quassnoi !
    • 加 1 我明白那个参考
    【解决方案2】:

    CTE使用的算法是:

    1. 执行anchor部分,得到结果r0
    2. 执行递归部分,使用r0作为输入,得到结果r1(非空)
    3. 执行递归部分,使用r1作为输入,得到结果r2(非空)
    4. 执行递归部分,使用r3作为输入,得到结果r3(非空) ...
    5. 执行递归部分,使用r(n-1)作为输入,输出rn (null)。这次 rn 为空,所以我们使用 UNION 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
    

    【讨论】:

    • 这阐明了它不是保持一个运行集,而是在每个递归级别获得一个单独的结果集。所以不是R1 = 1,那么R2 = (1 union all R1+1) = (1,2),然后R3 = (1 union all R2) = (1,1+1,2+1)=(1 ,2,3) 等。相反,它只使用前一个结果集查看结果并过滤它而不是整个联合。它只会在最后将它们联合起来。换句话说,in 仅使用递归成员的输出作为输入,而不是与递归成员联合的锚的输出。
    【解决方案3】:

    我认为它是这样分解的:

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

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

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

    4. 第 3 步继续进行,直到不再生成任何结果,或者达到了 MAX_RECURSION 选项设置的最大递归次数。

    This page 可能解释得最好。它对 CTE 的执行路径进行了分步演练。

    【讨论】:

    • 我们 3 个人现在链接到那篇文章 :-)
    【解决方案4】:

    您可能想要this link。不,锚不会多次执行(不可能,那么union all 将要求所有结果都出现)。上一个链接的详细信息。

    【讨论】:

      【解决方案5】:

      第 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 步中的最后一列是级别。在每个级别中,根据已经可用的内容添加行。希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 2014-08-04
        • 1970-01-01
        • 1970-01-01
        • 2023-03-17
        • 1970-01-01
        • 2013-05-24
        • 1970-01-01
        • 2023-02-07
        • 1970-01-01
        相关资源
        最近更新 更多