在SQL Server中,递归CTE有两个部分:
锚点:是您递归的起始点。它是一个集合,将通过递归连接进一步扩展。
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
ON A.MANAGERID = B.EMPID
在这个例子中,我们首先获取锚定结果集(所有没有经理的员工),并通过MANAGERID将它们与RECURSIVETBL连接起来,因此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
Anchor: Employees without managers (ManagerID IS NULL
). This will start with all the top badass of your company. It's crucial to note that if the anchor set is empty, then the whole recursive CTE will be empty, as there is no starting point and no recursive set to join to.
SELECT
EmployeeID = E.EmployeeID,
ManagerID = NULL,
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
Recursion N°1: Using this UNION ALL
recursion:
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
Recursion N°2: Following the same recursive expression...
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 的结果是以下内容(第三级!):
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
Recursion N°3: Starting with level 3s in our working set, the result of the join is:
HierarchyLevel EmployeeID ManagerID HierarchyRoute
4 12 10 1 -> 3 -> 10 -> 12
这将成为下一递归步骤的工作集。
- 第4次递归:从上一步仅有的第4行级别开始,连接的结果没有返回任何行(没有员工的EmployeeID是12且ManagerID也是12)。返回零行标记着迭代的结束。
最终结果集如下:
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
以下是完整的示例代码:
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,
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