SQL Server CTE 和递归示例
Posted
技术标签:
【中文标题】SQL Server CTE 和递归示例【英文标题】:SQL Server CTE and recursion example 【发布时间】:2012-12-25 20:16:52 【问题描述】:我从不使用带有递归的 CTE。我只是在读一篇关于它的文章。本文借助 Sql server CTE 和递归显示员工信息。它基本上显示员工及其经理信息。我无法理解这个查询是如何工作的。这是查询:
WITH
cteReports (EmpID, FirstName, LastName, MgrID, EmpLevel)
AS
(
SELECT EmployeeID, FirstName, LastName, ManagerID, 1
FROM Employees
WHERE ManagerID IS NULL
UNION ALL
SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,
r.EmpLevel + 1
FROM Employees e
INNER JOIN cteReports r
ON e.ManagerID = r.EmpID
)
SELECT
FirstName + ' ' + LastName AS FullName,
EmpLevel,
(SELECT FirstName + ' ' + LastName FROM Employees
WHERE EmployeeID = cteReports.MgrID) AS Manager
FROM cteReports
ORDER BY EmpLevel, MgrID
我在这里发布关于输出如何显示的信息:
我只需要知道它是如何首先显示经理,然后是他的下属的循环。 我猜第一个 sql 语句只触发一次并返回所有员工 ID。
第二个查询重复触发,使用当前经理 ID 查询员工所在的数据库。
请解释一下 sql 语句是如何在内部循环中执行的,并告诉我 sql 的执行顺序。谢谢。
我的第二阶段问题
;WITH Numbers AS
(
SELECT n = 1
UNION ALL
SELECT n + 1
FROM Numbers
WHERE n+1 <= 10
)
SELECT n
FROM Numbers
Q 1) N 的值是如何增加的?如果每次都将值分配给 N,则 N 值可以递增,但仅在第一次初始化 N 值时。
Q 2) CTE 和员工关系的递归:
当我添加了两个经理并在第二个经理下增加了一些员工时,问题就开始了。
我想显示第一个经理详细信息,并在下一行中仅显示与该经理的下属相关的员工详细信息。
假设
ID Name MgrID Level
--- ---- ------ -----
1 Keith NULL 1
2 Josh 1 2
3 Robin 1 2
4 Raja 2 3
5 Tridip NULL 1
6 Arijit 5 2
7 Amit 5 2
8 Dev 6 3
我想用 CTE 表达式以这种方式显示结果。请告诉我要在我这里给出的 sql 中修改什么,以便拉动经理-员工关系。谢谢。
我希望输出是这样的:
ID Name MgrID nLevel Family
----------- ------ ----------- ----------- --------------------
1 Keith NULL 1 1
3 Robin 1 2 1
2 Josh 1 2 1
4 Raja 2 3 1
5 Tridip NULL 1 2
7 Amit 5 2 2
6 Arijit 5 2 2
8 Dev 6 3 2
这可能吗...?
【问题讨论】:
【参考方案1】:我没有测试你的代码,只是想帮助你理解它在注释中是如何运作的;
WITH
cteReports (EmpID, FirstName, LastName, MgrID, EmpLevel)
AS
(
-->>>>>>>>>>Block 1>>>>>>>>>>>>>>>>>
-- In a rCTE, this block is called an [Anchor]
-- The query finds all root nodes as described by WHERE ManagerID IS NULL
SELECT EmployeeID, FirstName, LastName, ManagerID, 1
FROM Employees
WHERE ManagerID IS NULL
-->>>>>>>>>>Block 1>>>>>>>>>>>>>>>>>
UNION ALL
-->>>>>>>>>>Block 2>>>>>>>>>>>>>>>>>
-- This is the recursive expression of the rCTE
-- On the first "execution" it will query data in [Employees],
-- relative to the [Anchor] above.
-- This will produce a resultset, we will call it R1 and it is JOINed to [Employees]
-- as defined by the hierarchy
-- Subsequent "executions" of this block will reference Rn-1
SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,
r.EmpLevel + 1
FROM Employees e
INNER JOIN cteReports r
ON e.ManagerID = r.EmpID
-->>>>>>>>>>Block 2>>>>>>>>>>>>>>>>>
)
SELECT
FirstName + ' ' + LastName AS FullName,
EmpLevel,
(SELECT FirstName + ' ' + LastName FROM Employees
WHERE EmployeeID = cteReports.MgrID) AS Manager
FROM cteReports
ORDER BY EmpLevel, MgrID
我能想到的最简单的递归CTE
例子来说明它的操作是;
;WITH Numbers AS
(
SELECT n = 1
UNION ALL
SELECT n + 1
FROM Numbers
WHERE n+1 <= 10
)
SELECT n
FROM Numbers
Q 1) N 的值如何增加。如果每次都为 N 赋值,那么 N 值可以递增,但只有第一次初始化 N 值。
A1:
在这种情况下,N
不是变量。 N
是别名。它相当于SELECT 1 AS N
。这是个人喜好的语法。在T-SQL
中的CTE
中有两种主要的列别名方法。我在Excel
中包含了一个简单的CTE
的模拟,以尝试以更熟悉的方式说明正在发生的事情。
-- Outside
;WITH CTE (MyColName) AS
(
SELECT 1
)
-- Inside
;WITH CTE AS
(
SELECT 1 AS MyColName
-- Or
SELECT MyColName = 1
-- Etc...
)
Q 2) 现在在这里讨论 CTE 和员工关系的递归 当我添加两名经理并在第二名经理下添加更多员工时,问题就开始了。 我想显示第一个经理的详细信息,在接下来的行中,只有那些员工的详细信息才会出现在该经理的下属中
A2:
这段代码能回答你的问题吗?
--------------------------------------------
-- Synthesise table with non-recursive CTE
--------------------------------------------
;WITH Employee (ID, Name, MgrID) AS
(
SELECT 1, 'Keith', NULL UNION ALL
SELECT 2, 'Josh', 1 UNION ALL
SELECT 3, 'Robin', 1 UNION ALL
SELECT 4, 'Raja', 2 UNION ALL
SELECT 5, 'Tridip', NULL UNION ALL
SELECT 6, 'Arijit', 5 UNION ALL
SELECT 7, 'Amit', 5 UNION ALL
SELECT 8, 'Dev', 6
)
--------------------------------------------
-- Recursive CTE - Chained to the above CTE
--------------------------------------------
,Hierarchy AS
(
-- Anchor
SELECT ID
,Name
,MgrID
,nLevel = 1
,Family = ROW_NUMBER() OVER (ORDER BY Name)
FROM Employee
WHERE MgrID IS NULL
UNION ALL
-- Recursive query
SELECT E.ID
,E.Name
,E.MgrID
,H.nLevel+1
,Family
FROM Employee E
JOIN Hierarchy H ON E.MgrID = H.ID
)
SELECT *
FROM Hierarchy
ORDER BY Family, nLevel
另一种具有树结构的sql
SELECT ID,space(nLevel+
(CASE WHEN nLevel > 1 THEN nLevel ELSE 0 END)
)+Name
FROM Hierarchy
ORDER BY Family, nLevel
【讨论】:
CTE 递归查询不会以我想要的方式返回结果。我想显示第一个经理姓名,然后显示他的所有下属再次显示第二个经理姓名,然后显示他的所有下属。我希望输出应该是这种方式。如果可能,请更新您的查询。谢谢 [家庭] 列已添加。立即检查。 在这里我以我想要显示结果的方式给出输出。请检查并告诉我是否可能...如果是,那么在您的 sql 中进行必要的修改。感谢您的努力。 如果没有前面的语句以分号终止,CTE 将无法运行,因此它已成为一种习惯 :) @SiKni8 - 链接好像失效了【参考方案2】:想概述一个与已经正确答案平行的简短语义。
用“简单”的术语来说,递归 CTE 可以在语义上定义为以下部分:
1:CTE 查询。也称为 ANCHOR。
2:使用 UNION ALL(或 UNION 或 EXCEPT 或 INTERSECT)对 (1) 中的 CTE 进行递归 CTE 查询,因此相应地返回最终结果。
3:拐角/终止条件。默认情况下,当递归查询不再返回行/元组时。
一个让图片清晰的简短示例:
;WITH SupplierChain_CTE(supplier_id, supplier_name, supplies_to, level)
AS
(
SELECT S.supplier_id, S.supplier_name, S.supplies_to, 0 as level
FROM Supplier S
WHERE supplies_to = -1 -- Return the roots where a supplier supplies to no other supplier directly
UNION ALL
-- The recursive CTE query on the SupplierChain_CTE
SELECT S.supplier_id, S.supplier_name, S.supplies_to, level + 1
FROM Supplier S
INNER JOIN SupplierChain_CTE SC
ON S.supplies_to = SC.supplier_id
)
-- Use the CTE to get all suppliers in a supply chain with levels
SELECT * FROM SupplierChain_CTE
说明: 第一个 CTE 查询返回不直接向任何其他供应商供应的基本供应商(如叶子)(-1)
第一次迭代中的递归查询获取所有供应商的供应商,这些供应商由 ANCHOR 返回。 这个过程一直持续到条件返回元组。
UNION ALL 返回总递归调用中的所有元组。
另一个很好的例子可以找到here。
PS:要使递归 CTE 起作用,关系必须具有分层(递归)条件才能起作用。例如:elementId = elementParentId.. 你明白了。
【讨论】:
【参考方案3】:执行过程确实与递归CTE混淆,我在https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx找到了最佳答案,CTE执行过程摘要如下。
递归执行的语义如下:
-
将 CTE 表达式拆分为锚和递归成员。
运行创建第一个调用或基本结果集 (T0) 的锚成员。
以 Ti 作为输入,Ti+1 作为输出运行递归成员。
重复第 3 步,直到返回一个空集。
返回结果集。这是 T0 到 Tn 的 UNION ALL。
【讨论】:
【参考方案4】: --DROP TABLE #Employee
CREATE TABLE #Employee(EmpId BIGINT IDENTITY,EmpName VARCHAR(25),Designation VARCHAR(25),ManagerID BIGINT)
INSERT INTO #Employee VALUES('M11M','Manager',NULL)
INSERT INTO #Employee VALUES('P11P','Manager',NULL)
INSERT INTO #Employee VALUES('AA','Clerk',1)
INSERT INTO #Employee VALUES('AB','Assistant',1)
INSERT INTO #Employee VALUES('ZC','Supervisor',2)
INSERT INTO #Employee VALUES('ZD','Security',2)
SELECT * FROM #Employee (NOLOCK)
;
WITH Emp_CTE
AS
(
SELECT EmpId,EmpName,Designation, ManagerID
,CASE WHEN ManagerID IS NULL THEN EmpId ELSE ManagerID END ManagerID_N
FROM #Employee
)
select EmpId,EmpName,Designation, ManagerID
FROM Emp_CTE
order BY ManagerID_N, EmpId
【讨论】:
这是一个纯代码答案,甚至不回答问题,因为其中没有 recursive CTE。以上是关于SQL Server CTE 和递归示例的主要内容,如果未能解决你的问题,请参考以下文章