SQL Server 中的递归 CTE 有 2 个部分:
锚点:是递归的起点。这是一个将通过递归连接进一步扩展的集合。
SELECT
EMPID,
FULLNAME,
MANAGERID,
1 AS ORGLEVEL
FROM
RECURSIVETBL
WHERE
MANAGERID IS NULL
似乎它正在获取所有没有任何经理的员工(可能是最高老板,或树关系的根源)。
递归:与UNION ALL 链接,此集合必须引用声明的CTE(从而使其递归)。把它想象成你将如何将锚的结果扩展到下一个级别。
UNION ALL
SELECT
A.EMPID,
A.FULLNAME,
A.MANAGERID,
B.[ORGLEVEL] + 1
FROM
RECURSIVETBL A
JOIN RECURSIVECTE B -- Notice that we are referencing "RECURSIVECTE" which is the CTE we are declaring
ON A.MANAGERID = B.EMPID
在此示例中,我们正在(在第一次迭代中)获取锚结果集(所有没有经理的员工)并将它们与RECURSIVETBL 到MANAGERID 连接起来,因此A.EMPID 将保存先前的员工选定的经理。只要每个最后一个结果集都可以生成新行,这种连接就会一直持续下去。
对递归部分的内容有一些限制(例如,没有分组或其他嵌套递归)。此外,由于它以 UNION ALL 开头,因此它的规则也适用(列数和数据类型必须匹配)。
关于 ORGLEVEL,它从设置为 1 的锚点开始(那里是硬编码的)。当它在递归集上进一步扩展时,它会获取前一个集合(第一次迭代时的锚点)并加 1,因为它的表达式是 B.[ORGLEVEL] + 1,B 是前一个集合。这意味着它从 1(最高老板)开始,并不断为每个后代加 1,从而代表组织的所有级别。
当您在ORGLEVEL = 3 找到员工时,意味着他有 2 个经理在他之上。
一步一步的工作示例
让我们按照这个例子:
EmployeeID ManagerID
1 NULL
2 1
3 1
4 2
5 2
6 1
7 6
8 6
9 NULL
10 3
11 3
12 10
13 9
14 9
15 13
-
主播:没有经理的员工 (ManagerID IS NULL)。这将从贵公司的所有顶级坏蛋开始。需要注意的是,如果锚集为空,则整个递归 CTE 将为空,因为没有起点,也没有可加入的递归集。
SELECT
EmployeeID = E.EmployeeID,
ManagerID = NULL, -- Always null by WHERE filter
HierarchyLevel = 1,
HierarchyRoute = CONVERT(VARCHAR(MAX), E.EmployeeID)
FROM
Employee AS E
WHERE
E.ManagerID IS NULL
这些是什么:
EmployeeID ManagerID HierarchyLevel HierarchyRoute
1 (null) 1 1
9 (null) 1 9
-
递归 N°1:使用此 UNION ALL 递归:
UNION ALL
SELECT
EmployeeID = E.EmployeeID,
ManagerID = E.ManagerID,
HierarchyLevel = R.HierarchyLevel + 1,
HierarchyRoute = R.HierarchyRoute + ' -> ' + CONVERT(VARCHAR(10), E.EmployeeID)
FROM
RecursiveCTE AS R
INNER JOIN Employee AS E ON R.EmployeeID = E.ManagerID
对于这个INNER JOIN,RecursiveCTE 有 2 行(锚集),员工 ID 为 1 和 9。所以这个JOIN实际上会返回这个结果。
HierarchyLevel EmployeeID ManagerID HierarchyRoute
2 2 1 1 -> 2
2 3 1 1 -> 3
2 6 1 1 -> 6
2 13 9 9 -> 13
2 14 9 9 -> 14
看看HierarchyRoute 如何从 1 和 9 开始并移动到每个后代?我们还将HierarchyLevel 增加了 1。
由于结果是通过UNION ALL链接的,此时我们有以下结果(步骤1 + 2):
HierarchyLevel EmployeeID ManagerID HierarchyRoute
1 1 (null) 1
1 9 (null) 9
2 2 1 1 -> 2
2 3 1 1 -> 3
2 6 1 1 -> 6
2 13 9 9 -> 13
2 14 9 9 -> 14
这里是棘手的部分,对于以下每个迭代,对RecursiveCTE 的递归引用将只包含最后一个迭代结果集,而不包含累积集。这意味着对于下一次迭代,RecursiveCTE 将代表这些行:
HierarchyLevel EmployeeID ManagerID HierarchyRoute
2 2 1 1 -> 2
2 3 1 1 -> 3
2 6 1 1 -> 6
2 13 9 9 -> 13
2 14 9 9 -> 14
-
递归 N°2:遵循相同的递归表达式...
UNION ALL
SELECT
EmployeeID = E.EmployeeID,
ManagerID = E.ManagerID,
HierarchyLevel = R.HierarchyLevel + 1,
HierarchyRoute = R.HierarchyRoute + ' -> ' + CONVERT(VARCHAR(10), E.EmployeeID)
FROM
RecursiveCTE AS R
INNER JOIN Employee AS E ON R.EmployeeID = E.ManagerID
考虑到在这一步中RecursiveCTE只保存带有HierarchyLevel = 2的行,那么如果这个JOIN的结果如下(3级!):
HierarchyLevel EmployeeID ManagerID HierarchyRoute
3 4 2 1 -> 2 -> 4
3 5 2 1 -> 2 -> 5
3 7 6 1 -> 6 -> 7
3 8 6 1 -> 6 -> 8
3 10 3 1 -> 3 -> 10
3 11 3 1 -> 3 -> 11
3 15 13 9 -> 13 -> 15
这个集合(而且只有这个!)将在下面的递归步骤中用作RecursiveCTE,并将添加到累积的总计中,即现在:
HierarchyLevel EmployeeID ManagerID HierarchyRoute
1 1 (null) 1
1 9 (null) 9
2 2 1 1 -> 2
2 3 1 1 -> 3
2 6 1 1 -> 6
2 13 9 9 -> 13
2 14 9 9 -> 14
3 4 2 1 -> 2 -> 4
3 5 2 1 -> 2 -> 5
3 7 6 1 -> 6 -> 7
3 8 6 1 -> 6 -> 8
3 10 3 1 -> 3 -> 10
3 11 3 1 -> 3 -> 11
3 15 13 9 -> 13 -> 15
-
递归 N°3:从我们工作集中的第 3 级开始,连接的结果是:
HierarchyLevel EmployeeID ManagerID HierarchyRoute
4 12 10 1 -> 3 -> 10 -> 12
这成为我们下一个递归步骤的工作集。
-
递归 N°4:从上一步中唯一的第 4 行开始,连接的结果不会产生任何行(没有员工的 EmployeeID 为 12 作为 ManagerID)。不返回任何行标志着迭代的结束。
最终的结果集很高:
HierarchyLevel EmployeeID ManagerID HierarchyRoute
1 1 (null) 1
1 9 (null) 9
2 2 1 1 -> 2
2 3 1 1 -> 3
2 6 1 1 -> 6
2 13 9 9 -> 13
2 14 9 9 -> 14
3 4 2 1 -> 2 -> 4
3 5 2 1 -> 2 -> 5
3 7 6 1 -> 6 -> 7
3 8 6 1 -> 6 -> 8
3 10 3 1 -> 3 -> 10
3 11 3 1 -> 3 -> 11
3 15 13 9 -> 13 -> 15
4 12 10 1 -> 3 -> 10 -> 12
这是完整的fiddle 和代码:
CREATE TABLE Employee (EmployeeID INT, ManagerID INT)
INSERT INTO Employee (EmployeeID, ManagerID)
VALUES
(1, NULL),
(2, 1),
(3, 1),
(4, 2),
(5, 2),
(6, 1),
(7, 6),
(8, 6),
(9, NULL),
(10, 3),
(11, 3),
(12, 10),
(13, 9),
(14, 9),
(15, 13)
WITH RecursiveCTE AS
(
SELECT
EmployeeID = E.EmployeeID,
ManagerID = NULL, -- Always null by WHERE filter
HierarchyLevel = 1,
HierarchyRoute = CONVERT(VARCHAR(MAX), E.EmployeeID)
FROM
Employee AS E
WHERE
E.ManagerID IS NULL
UNION ALL
SELECT
EmployeeID = E.EmployeeID,
ManagerID = E.ManagerID,
HierarchyLevel = R.HierarchyLevel + 1,
HierarchyRoute = R.HierarchyRoute + ' -> ' + CONVERT(VARCHAR(10), E.EmployeeID)
FROM
RecursiveCTE AS R
INNER JOIN Employee AS E ON R.EmployeeID = E.ManagerID
)
SELECT
R.HierarchyLevel,
R.EmployeeID,
R.ManagerID,
R.HierarchyRoute
FROM
RecursiveCTE AS R
ORDER BY
R.HierarchyLevel,
R.EmployeeID