递归查询 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
我正在看 "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