递归查询 CTE

Recursive Queries CTE

我正在看 "Murach's SQL Server 2016 for developers" 书中的一个例子。该示例说明了如何在 SQL 中编写递归 CTS 代码。我非常了解递归函数(在 C# 中),但我无法以某种方式理解 sql 递归逻辑的工作原理。 这是示例:

USE Examples;

WITH EmployeesCTE AS
(
        -- Anchor member
        SELECT EmployeeID, 
            FirstName + ' ' + LastName As EmployeeName, 
            1 As Rank
        FROM Employees
        WHERE ManagerID IS NULL
    UNION ALL
        -- Recursive member
        SELECT Employees.EmployeeID, 
            FirstName + ' ' + LastName, 
            Rank + 1
        FROM Employees
            JOIN EmployeesCTE
            ON Employees.ManagerID = EmployeesCTE.EmployeeID
)
SELECT *
FROM EmployeesCTE
ORDER BY Rank, EmployeeID;

此查询 returns 组织中员工的层级。

我的问题:在递归函数中,您会看到一个递减变量终止递归(通过达到基本情况)。我的问题是:EmployeesCTE中对应的部分在哪里?请帮助我理解逻辑。

所以我们所说的 "recursive CTE" 实际上应该称为迭代 CTE。这个想法是,为了定义递归 table(在本例中为 EmployeesCTE),我们首先创建一些初始行,在本例中,这是由

完成的
   SELECT EmployeeID, 
        FirstName + ' ' + LastName As EmployeeName, 
        1 As Rank
    FROM Employees
    WHERE ManagerID IS NULL

(注意这不包含对 EmployeesCTE 的引用,因此它不是递归的),然后我们迭代一个表达式,在本例中为

    SELECT Employees.EmployeeID, 
        FirstName + ' ' + LastName, 
        Rank + 1
    FROM Employees
        JOIN EmployeesCTE
        ON Employees.ManagerID = EmployeesCTE.EmployeeID

生成更多行。我们这样做,直到该表达式 returns 没有行。在这个表达式中 EmployeesCTE 指的是 table 的前一个版本,通过计算它,我们计算出 table.

的下一个版本

所以停止递归(或迭代)的条件是递归表达式没有产生新行。

现在让我们仔细看看以上所有内容如何适用于您给出的特定示例。我们的初始行集由没有经理的员工组成(我们称他们为 1 级员工)。然后我们找到所有由上一步找到的员工管理的员工(我们称他们为 2 级员工)。然后我们发现员工由2级员工管理并称为3级,依此类推。最终我们将到达找不到新员工的步骤(当然假设 managed by 关系没有周期)。

如果您熟悉 C#,您可能会认为这是一个复杂的 对象模型

想象一下带有控件的简单 Windows.Forms.Form。每个控件本身都有一个控件集合。在数据库中,您可以想到自引用 table,其中每一行都指向其父行(顶部对象指向 NULL),就像您的员工指向层次结构中的下一个老板一样。

有一个顶部对象的方法 Refresh()。当您调用它时,该函数会对自己的内容执行某些操作,并在其内部集合上调用 Refresh()。该集合对其所有成员调用 Refresh()。他们都做了一些事情并在他们的内部集合上调用 Refresh() 。这会沿着嵌套模型向下运行,直到您到达具有空 Controly 集合的控件。

这更像是自上而下的级联。实际上,有意停止带有条件的递归 CTE 可能非常棘手,因为您不会获得包含中断条件的最后一行。

递归 CTE 的第二部分自然结束,当 JOIN 操作没有 return 任何行时...

在您的情况下,您可以将其解读为

  • anchor:获取所有没有老板的员工(最高级别)
  • 现在询问所有员工的名单,其中一位是他们的经理(二级)
  • 往下逐行取出所有以二级人员为经理的员工
  • 继续,直到没有更多依赖员工

请注意,递归 CTE 在设计上是一种缓慢的方法,因为它是 隐藏的 RBAR

可能需要做 TOP-DOWN MS SQL 以将 TOP 级别值向下传播到所有级别(在某些情况下,只有最高级别更好;-)

    SELECT  ID      AS ID
        ,   parent  AS parent
        ,   limit   AS limit
        ,   NULL    AS maxParentLimit -- place holder
        INTO #StructLimit_CTE
        FROM StructLimit_CTE
    CREATE INDEX #StructLimit_CTE_id ON #StructLimit_CTE(ID)
    --SELECT COUNT(*) FROM #StructLimit_CTE -- 1000000+ in my case, so MS needs index  :-)

    ;WITH maxParentLimit (ID, ParentLimit, limit) as 
    ( select ID 
            , limit 
            , limit             
            from #StructLimit_CTE
            where parent IS NULL -- = 0
        union all
            select o.ID                                     
                , IIF( o.limit > ParentLimit, o.limit, ParentLimit) -- take the biggst, when we have one, think about NULL-s
                , o.limit   
            from #StructLimit_CTE o
            join maxParentLimit n on n.ID = o.parent -- recursion
    )
    UPDATE #StructLimit_CTE SET maxParentLimit = m.ParentLimit 
        FROM #StructLimit_CTE   AS o    
        JOIN maxParentLimit AS m    ON o.ID = m.ID
    -- or use (LEFT) JOIN when proper