递归 CTE 如何逐行运行?
Posted
技术标签:
【中文标题】递归 CTE 如何逐行运行?【英文标题】:How does a Recursive CTE run, line by line? 【发布时间】: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
【问题讨论】:
【参考方案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
会尝试禁止会导致无限记录集的查询。
您可能想阅读这篇文章:
SQL Server: are the recursive CTE’s really set-based?【讨论】:
可爱的解释 Quassnoi ! 加 1 我明白那个参考【参考方案2】:CTE使用的算法是:
-
执行anchor部分,得到结果r0
执行递归部分,使用r0作为输入,得到结果r1(非空)
执行递归部分,使用r1作为输入,得到结果r2(非空)
执行递归部分,使用r3作为输入,得到结果r3(非空)
...
执行递归部分,使用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】:我认为它是这样分解的:
执行锚语句。这会为您提供一组结果,称为基本集或 T0。
执行递归语句,使用 T0 作为执行查询的表。这会在您查询 CTE 时自动发生。
如果递归成员返回一些结果,它会创建一个新集合 T1。然后再次执行递归成员,使用 T1 作为输入,如果有任何结果,则创建 T2。
第 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 步中的最后一列是级别。在每个级别中,根据已经可用的内容添加行。希望这会有所帮助。
【讨论】:
以上是关于递归 CTE 如何逐行运行?的主要内容,如果未能解决你的问题,请参考以下文章