我坐的是“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中的相应部分在哪里?请帮我理解逻辑。
答案 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