递归 CTE 在 SQL Server 中如何工作?

问题描述 投票:0回答:2

谁能帮我理解这个递归 CTE 是如何工作的?

WITH
RECURSIVECTE (EMPID, FULLNAME, MANAGERID, [ORGLEVEL]) AS
    (SELECT EMPID,
            FULLNAME,
            MANAGERID,
            1
     FROM RECURSIVETBL
     WHERE MANAGERID IS NULL
     UNION ALL
     SELECT A.EMPID,
            A.FULLNAME,
            A.MANAGERID,
            B.[ORGLEVEL] + 1
     FROM RECURSIVETBL A
          JOIN RECURSIVECTE B ON A.MANAGERID = B.EMPID)
SELECT *
FROM RECURSIVECTE;
sql sql-server recursion common-table-expression
2个回答
15
投票

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
  1. :没有经理的员工 (

    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
  1. 递归 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
  1. 递归 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
  1. 递归 N°3:从工作集中的第 3 级开始,连接的结果是:

    HierarchyLevel  EmployeeID  ManagerID   HierarchyRoute
    4               12          10          1 -> 3 -> 10 -> 12
    

这将成为我们下一步递归步骤的工作集。

  1. 递归 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

1
投票

如果您的人数超过顶级经理 [ORGLEVEL] 将始终从 1 开始。

没有发布数据无法提供详细信息。

© www.soinside.com 2019 - 2024. All rights reserved.