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

时间:2018-07-04 15:26:40

标签: sql sql-server recursion common-table-expression

有人可以帮助我了解此递归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;

2 个答案:

答案 0 :(得分:4)

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  -- Notice that we are referencing "RECURSIVECTE" which is the CTE we are declaring
    ON A.MANAGERID = B.EMPID

在此示例中,我们正在获取(第一次迭代)锚定结果集(所有没有经理的员工),并将它们与RECURSIVETBLMANAGERID一起加入,因此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. 第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 JOINRecursiveCTE有2行(锚集),雇员ID为19。因此,此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. 第3递归:从工作集中的3s级别开始,联接的结果为:

    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 :(得分:1)

如果您的职位超过高层管理人员,[ORGLEVEL]将始终从1开始。

没有过帐数据无法提供详细信息。