Как рекурсивного 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:
- выполнить якорную часть, получить результат r0
- выполните рекурсивную часть, используя r0 в качестве ввода и получить результат r1 (не null)
- выполните рекурсивную часть, используя r1 в качестве ввода и получить результат r2 (не null)
- выполните рекурсивную часть, используя r3 в качестве ввода и получить результат r3 (не ноль) ...
- выполните рекурсивную часть, используя 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
Я думаю, что он ломается следующим образом:
выполняется оператор привязки. Это дает вам набор результатов, называемый базовым набором или T0.
выполняется рекурсивный оператор, используя T0 в качестве таблицы для выполнения запроса. Это происходит автоматически при запросе CTE.
Если рекурсивный элемент возвращает некоторые результаты, он создает новый набор, T1. Затем выполняется рекурсивный элемент снова, используя T1 в качестве входных данных, создавая T2, если есть какие-либо результаты.
Шаг 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 Уровень. Во время каждого уровня строки добавляются относительно того, что уже доступно. Надеюсь, это поможет.