递归查询CTE

时间:2016-09-06 20:26:03

标签: sql sql-server common-table-expression recursive-query

我坐的是“Murach的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;

此查询返回组织中员工的层次结构级别。

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

3 个答案:

答案 0 :(得分:2)

所以我们称之为"递归CTE"应该真的称为迭代CTE。我们的想法是,为了定义一个递归表(在这种情况下为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

生成更多行。我们这样做,直到该表达式不返回任何行。在此表达式中,EmployeesCTE引用该表的先前版本,并通过对其进行评估,我们计算该表的下一个版本。

因此,停止递归(或者说迭代)的条件是递归表达式没有产生新行。

现在让我们仔细看看以上所有内容如何应用于您提供的特定示例。我们的初始行包括没有经理的员工(我们称他们为1级员工)。然后我们找到上一步中找到的员工管理的所有员工(我们称他们为2级员工)。然后我们发现员工由2级员工管理,并且排名3,依此类推。最终我们将达到一个不会找到新员工的步骤(当然假设关系管理没有周期)。

答案 1 :(得分:1)

由于您熟悉C#,您可能会将其视为复杂的对象模式

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

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

这更类似于 top-down-cascade 。实际上用一个条件故意停止递归CTE是非常棘手的,因为你不会得到带有断开条件的最后一行。

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

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

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

请注意这样一个事实,即递归CTE - 按设计 - 是一种缓慢的方法,因为它是隐藏的RBAR

答案 2 :(得分:0)

可能有些需要执行自上而下的 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