如何在T-SQL中开发递归CTE?

时间:2013-03-08 19:24:39

标签: sql-server tsql recursion common-table-expression

我是递归CTE的新手。我正在尝试开发一个CTE,它将返回每个经理名下的所有员工。所以我有两个表:people_rvstaff_rv

People_rv表包含所有人员,包括经理和员工。 Staff_rv仅包含经理信息。 Uniqueidentifier员工值存储在Staff_rv中。 Uniqueidentifier员工值存储在people_rv中。 People_rv包含经理和员工的varchar名字和姓氏值。

但是当我运行以下CTE时,我收到一个错误:

WITH
cteStaff (ClientID, FirstName, LastName, SupervisorID, EmpLevel)
AS
(
    SELECT p.people_id, p.first_name, p.last_name, s.supervisor_id,1
    FROM people_rv p JOIN staff_rv s on s.people_id = p.people_id
    WHERE s.supervisor_id = '95E16819-8C3A-4098-9430-08F0E3B764E1' 
    UNION ALL
    SELECT p2.people_id, p2.first_name, p2.last_name, s2.supervisor_id, r.EmpLevel + 1
    FROM people_rv p2 JOIN staff_rv s2 on s2.people_id = p2.people_id
    INNER JOIN cteStaff r on s2.staff_id = r.ClientID
)
SELECT
    FirstName + ' ' + LastName AS FullName, 
    EmpLevel,
    (SELECT first_name + ' ' + last_name FROM people_rv p join staff_rv s on s.people_id = p.people_id 
    WHERE s.staff_id = cteStaff.SupervisorID) AS Manager
FROM cteStaff
OPTION (MAXRECURSION 0);

我的输出是:

Barbara G   1   Melanie K
Dawn P  1   Melanie K
Garrett M   1   Melanie K
Stephanie P 1   Melanie K
Amanda F    1   Melanie K
Amanda T    1   Melanie K
Stephanie G 1   Melanie K
Carlos H    1   Melanie K

因此它不会比第一级迭代更多。为什么不? Melanie是最受监管的人,但最左边一栏的每个人也都是主管。所以这个查询也应该返回2级。

4 个答案:

答案 0 :(得分:3)

据我所知,你的问题是你没有将经理连接到他们的员工。

此联接

INNER JOIN cteStaff r on r.StaffID = s2.staff_id

刚刚加入同一个初级1级员工。

更新:

仍然不太正确!你有一个supervisor_id,但你仍然没有真正使用它来加入CTE。

因此,对于此CTE的每次递归,您需要(不包括名称连接):

select {Level 1 Boss}, NULL (no supervisor)
union
select {new employee}, {that employee's boss}

因此,连接必须将CTE的ClientID(1级boss)连接到第二个UNION查询的 supervisor 字段,该字段看起来是 supervisor_id ,而不是 staff_id

完成第二项任务的JOIN是(从我可以看出你的staff_rv表模式):

 SELECT p2.people_id, p2.first_name, p2.last_name, s2.supervisor_id, r.EmpLevel + 1
    FROM people_rv p2 JOIN staff_rv s2 on s2.people_id = p2.people_id
    INNER JOIN cteStaff r on s2.supervisor_id = r.ClientID

注意底部连接将r.ClientID(1级boss)连接到staffer的supervisor_id字段。

(注意:我认为你的staff_id和supervisor_id模仿了people_rv表中的people_id值,所以这个联接应该可以正常工作。但如果它们不同(即职员的supervisor_id不是主管的people_id)那么你需要编写联接,以便职员的supervisor_id可以加入他们的people_id,你将其作为ClientID存储在CTE中。)

答案 1 :(得分:3)

您的加入可能会处于无限循环中。我会检查你希望表格实际下降多少级别。通常你加入一个类似于do

的递归
 ID = ParentID

包含在表格或表达式中的内容。请记住,如果您必须弥补您的关系,您还可以在递归CTE之前创建CTE。

这是一个自我执行的例子,它可能有所帮助。

Declare @table table ( PersonId int identity, PersonName varchar(512), Account int, ParentId int, Orders int);

insert into @Table values ('Brett', 1, NULL, 1000),('John', 1, 1, 100),('James', 1, 1, 200),('Beth', 1, 2, 300),('John2', 2, 4, 400);

select 
    PersonID
,   PersonName
,   Account
,   ParentID
from @Table

; with recursion as 
    (
    select 
        t1.PersonID
    ,   t1.PersonName
    ,   t1.Account
    --, t1.ParentID
    ,   cast(isnull(t2.PersonName, '')
            + Case when t2.PersonName is not null then '\' + t1.PersonName else t1.PersonName end
            as varchar(255)) as fullheirarchy
    ,   1 as pos
    ,   cast(t1.orders + 
            isnull(t2.orders,0) -- if the parent has no orders than zero
            as int) as Orders
    from @Table t1
        left join @Table t2 on t1.ParentId = t2.PersonId
    union all
    select 
        t.PersonID
    ,   t.PersonName
    ,   t.Account
    --, t.ParentID
    ,   cast(r.fullheirarchy + '\' + t.PersonName as varchar(255))
    ,   pos + 1  -- increases
    ,   r.orders + t.orders
    from @Table t
        join recursion r on t.ParentId = r.PersonId
    )
, b as 
    (
    select *, max(pos) over(partition by PersonID) as maxrec  -- I find the maximum occurrence of position by person
    from recursion
    )
select *
from b
where pos = maxrec  -- finds the furthest down tree
-- and Account = 2  -- I could find just someone from a different department

答案 2 :(得分:2)

这是一个很好的简单的递归CTE来审查(它可能不是答案,但其他人在搜索如何进行递归CTE可能需要它):

-- Recursive CTE
;
WITH    Years ( myYear )
          AS (
               -- Base case
      SELECT    DATEPART(year, GETDATE())
               UNION ALL
      -- Recursive
               SELECT   Years.myYear - 1
               FROM     Years
               WHERE    Years.myYear >= 2002
             )
    SELECT  *
    FROM    Years

答案 3 :(得分:1)

请注意,这可能无法解决您的问题,但可以帮助您查看原始查询中出错的位置。

默认为100级递归 - 您可以使用{CT}选择的MAXRECURSION查询提示将其设置为无限制:

...
FROM cteStaff
OPTION (MAXRECURSION 0);

来自MSDN

  

MAXRECURSION编号

     

指定此查询允许的最大递归数。 number是0到32767之间的非负整数。当0为时   指定,不应用限制。如果未指定此选项,则   服务器的默认限制为100。

     

在查询执行期间达到MAXRECURSION限制的指定或默认数量时,查询结束并且错误是   返回。

     

由于此错误,将回滚该语句的所有效果。如果语句是SELECT语句,则部分结果或否   结果可能会被退回。返回的任何部分结果可能不包括   递归级别上的所有行超出指定的最大递归   水平。