Как рекурсивного CTE работать, построчно?

Я думаю, что у меня есть формат рекурсивных CTEs достаточно хорошо, чтобы написать один, но все равно я разочарован до бесконечности, что я не могу вручную обработать его (притворяюсь, что сам SQL engine и достигаю результирующего набора с ручкой и бумагой). Я нашел это, что близко к тому, что я ищу, но недостаточно подробно. У меня нет проблем с трассировкой через рекурсивную функцию 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 

5 ответов


подумайте о рекурсивном 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 пытается запретить запросы, которые приведут к бесконечным наборам записей.

вы можете прочитать эту статью:


алгоритм, который использует CTE:

  1. выполнить якорную часть, получить результат r0
  2. выполните рекурсивную часть, используя r0 в качестве ввода и получить результат r1 (не null)
  3. выполните рекурсивную часть, используя r1 в качестве ввода и получить результат r2 (не null)
  4. выполните рекурсивную часть, используя r3 в качестве ввода и получить результат r3 (не ноль) ...
  5. выполните рекурсивную часть, используя r (n-1) как вход, так и выход rn (null). На этот раз rn равно null, поэтому мы используем ОБЪЕДИНЕНИЕ ВСЕХ объединить 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

Я думаю, что он ломается следующим образом:

  1. выполняется оператор привязки. Это дает вам набор результатов, называемый базовым набором или T0.

  2. выполняется рекурсивный оператор, используя T0 в качестве таблицы для выполнения запроса. Это происходит автоматически при запросе CTE.

  3. Если рекурсивный элемент возвращает некоторые результаты, он создает новый набор, T1. Затем выполняется рекурсивный элемент снова, используя T1 в качестве входных данных, создавая T2, если есть какие-либо результаты.

  4. Шаг 3 продолжается до тех пор, пока не будет сгенерировано больше результатов или не будет выполнено максимальное число рекурсий, заданное параметром MAX_RECURSION.

на этой странице вероятно, объясняет это лучше всего. Он имеет пошаговое пошаговое руководство по пути выполнения CTE.


вы, вероятно, хотели этой ссылке. Нет, якорь не выполняется несколько раз (это не может быть, тогда union all потребует, чтобы все результаты появились). Подробности по предыдущей ссылке.


Шаг 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 Уровень. Во время каждого уровня строки добавляются относительно того, что уже доступно. Надеюсь, это поможет.