嗯,递归 CTE 的简短介绍:
递归 CTE 是一种迭代,而不是真正的递归。锚查询被用来获取一些初始结果集。有了这套,我们可以更深入地潜水。试试这些简单的案例:
只是一个计数器,甚至不需要 JOIN...
锚点的 1 将导致 UNION ALL 中的 2。这个 2 再次传递到 UNION ALL 中,并将作为 3 返回,依此类推...
WITH recCTE AS
(
SELECT 1 AS Mycounter
UNION ALL
SELECT recCTE.MyCounter+1
FROM recCTE
WHERE recCTE.MyCounter<10
)
SELECT * FROM recCTE;
2 列的计数器
这和上面的完全一样。但是我们有两列,分别处理。
WITH recCTE AS
(
SELECT 1 AS Mycounter1, 10 AS MyCounter2
UNION ALL
SELECT recCTE.MyCounter1+1,recCTE.MyCounter2+1
FROM recCTE
WHERE recCTE.MyCounter1<10
)
SELECT * FROM recCTE;
现在我们在初始查询中有两行
单独运行,初始查询将返回两行。两者都有 counter==1 和 Nmbr 列的两个不同值
WITH recCTE AS
(
SELECT MyCounter=1, Nmbr FROM(VALUES(1),(10)) A(Nmbr)
UNION ALL
SELECT recCTE.MyCounter+1, recCTE.Nmbr+1
FROM recCTE
WHERE recCTE.MyCounter<10
)
SELECT * FROM recCTE ORDER BY MyCounter,Nmbr;
现在我们得到了 20 行,而不是之前示例中的 10 行。这是因为锚的两行都是独立使用的。
我们可以在 JOIN 中使用递归 CTE
在本例中,我们将首先创建一个派生集,然后将其连接到递归 CTE。猜猜为什么第一行带有“X”而不是“A”?
WITH SomeSet AS (SELECT * FROM (VALUES(1,'A'),(2,'B'),(3,'C'),(4,'D'),(5,'E'),(6,'F'),(7,'G'),(8,'H'),(9,'I'),(10,'J')) A(id,Letter))
,recCTE AS
(
SELECT MyCounter=1, Nmbr,'X' AS Letter FROM(VALUES(1),(10)) A(Nmbr)
UNION ALL
SELECT recCTE.MyCounter+1, recCTE.Nmbr+1, SomeSet.Letter
FROM SomeSet
INNER JOIN recCTE ON SomeSet.id=recCTE.MyCounter+1
WHERE recCTE.MyCounter<10
)
SELECT * FROM recCTE ORDER BY MyCounter,Nmbr;
这将使用自引用连接来模拟您的层次结构,但使用一个无间隙链
WITH SomeSet AS (SELECT * FROM (VALUES(1,'A',NULL),(2,'B',1),(3,'C',2),(4,'D',3),(5,'E',4),(6,'F',5),(7,'G',6),(8,'H',7),(9,'I',8),(10,'J',9)) A(id,Letter,Previous))
,recCTE AS
(
SELECT id,Letter,Previous,' ' PreviousLetter FROM SomeSet WHERE Previous IS NULL
UNION ALL
SELECT SomeSet.id,SomeSet.Letter,SomeSet.Previous,recCTE.Letter
FROM SomeSet
INNER JOIN recCTE ON SomeSet.Previous=recCTE.id
)
SELECT * FROM recCTE:
现在几乎和以前一样了,但有几个元素具有相同的“previous”。
这是 - 原则上 - 你的层次结构
WITH SomeSet AS (SELECT * FROM (VALUES(1,'A',NULL),(2,'B',1),(3,'C',2),(4,'D',2),(5,'E',2),(6,'F',3),(7,'G',3),(8,'H',4),(9,'I',1),(10,'J',9)) A(id,Letter,Previous))
,recCTE AS
(
SELECT id,Letter,Previous,' ' PreviousLetter FROM SomeSet WHERE Previous IS NULL
UNION ALL
SELECT SomeSet.id,SomeSet.Letter,SomeSet.Previous,recCTE.Letter
FROM SomeSet
INNER JOIN recCTE ON SomeSet.Previous=recCTE.id
)
SELECT * FROM recCTE
结论
重点
- 锚查询必须返回至少一行,但可能返回多行
- 第二部分必须与列列表匹配(与任何
UNION ALL 查询一样)
- 第二部分必须在其
FROM子句中引用cte
- 第二部分将使用之前调用的结果反复调用
- 每一行单独处理(一个隐藏的RBAR)
- 您可以从经理(top-most-node)开始,然后通过查询具有此经理 ID 的员工来向下走,或者
- 您可以从层次结构中最低的开始(没有其他行的那些,使用它们的 id 作为经理 id)并向上移动列表
- 由于它是一个隐藏的 RBAR,您可以将它用于 逐行 操作,例如字符串累积。
最后一条语句的示例
查看 LetterPath 列是如何构建的。
WITH SomeSet AS (SELECT * FROM (VALUES(1,'A',NULL),(2,'B',1),(3,'C',2),(4,'D',2),(5,'E',2),(6,'F',3),(7,'G',3),(8,'H',4),(9,'I',1),(10,'J',9)) A(id,Letter,Previous))
,recCTE AS
(
SELECT id,Letter,Previous,' ' PreviousLetter,CAST(Letter AS VARCHAR(MAX)) AS LetterPath FROM SomeSet WHERE Previous IS NULL
UNION ALL
SELECT SomeSet.id,SomeSet.Letter,SomeSet.Previous,recCTE.Letter,recCTE.LetterPath + SomeSet.Letter
FROM SomeSet
INNER JOIN recCTE ON SomeSet.Previous=recCTE.id
)
SELECT * FROM recCTE