递归 CTE 如何消除重复项?

How does a recursive CTE eliminate duplicates?

我正在使用 SQL Server 2014 Express 在 AdventureWorks2012 数据库中学习递归 CTE。我想我得到的主要是下面的例子(取自 Beginning T-SQL 第 3 版),但我不太明白为什么递归 CTE 不产生重复项。

下面是我试图理解的递归 CTE,它是一个标准的员工 - 经理层次结构。

;with orgchart (employeeid, managerid, title, level, node) as (
    --Anchor
    select employeeid
    , managerid
    , title
    , 0
    , convert(varchar(30),'/') 'node'
    from employee
    where managerid is null 
    union all
    --Recursive
    select emp.employeeid
    , emp.managerid
    , emp.title
    , oc.level + 1
    , convert(varchar(30), oc.node + convert(varchar(30),emp.managerid) + '/')
    from employee emp
    inner join orgchart oc on oc.employeeid = emp.managerid 
    )
select employeeid
, managerid
, space(level * 3) + title 'title'
, level
, node
from orgchart
order by node;

它工作正常,但是当我试图通过 temp tables 重新创建它来了解发生了什么时,问题就来了。我创建了一系列临时 tables 以将一个输出插入下一个查询的输入并重新创建递归 CTE 的功能。

--Anchor (Level 0)
select employeeid
, managerid
, title
, 0
, convert(varchar(30),'/') 'node'
into #orgchart
from employee
where managerid is null

然后我使用那个 temp table 重新创建第一级递归,此时它只是递归 CTE,但有 temp tables。

--Anchor + 1 level
select *
into #orgchart2
from #orgchart
union all
select emp.employeeid
, emp.managerid
, emp.title
, oc.level + 1
, convert(varchar(30), oc.node + convert(varchar(30),emp.managerid) + '/') 
from employee emp
inner join #orgchart oc on oc.employeeid = emp.managerid

到目前为止一切顺利,结果很有意义。然后我又做了一次,但这是它开始崩溃的地方:

--Anchor + 2 levels
select *
into #orgchart3
from #orgchart2
union all
select emp.employeeid
, emp.managerid
, emp.title
, oc.level + 1
, convert(varchar(30), oc.node + convert(varchar(30),emp.managerid) + '/')
from employee emp
inner join #orgchart2 oc on oc.employeeid = emp.managerid

此输出开始为 1 级员工的 return 重复行(所有字段重复)。这是有道理的 - UNION ALL 之后的第二个查询将 return 以前的级别以及新的递归级别,并且 UNION ALL 不会重复。如果我再做一轮递归,2级员工也重复,依此类推。

我知道我可以将 UNION ALL 更改为 UNION 以删除重复项,但我想了解为什么递归 CTE 也不会生成重复项?它使用 UNION ALL 所以我不明白重复数据删除的来源。删除重复项是递归 CTE 的固有部分吗?

我正在尝试 post 所有结果集,但如果需要他们来理解问题,请告诉我,我会 post 他们。提前致谢。

不同之处在于,当您填充#orgchart2 时,您将包括#orgchart 中的所有行。因此,现在当您创建#orgchart3(代表第 3 级递归)时,您将加入来自#orgchart 和#orgchart2 的行。

所以当您在#orgchart3 中创建第三层时,它与#orgchart 和#orgchart2 中的行相关,而它应该只与#orgchart2 相关。相反,您的第三级包括比第二级高一级的行,但也包括比锚定级高一级的行,因此您正在复制行,因为第二级中已经有比锚定级高一级的行。

优化器知道不使用递归 CTE 执行此操作。每一层递归只看前一层,而忽略所有在它之前的递归。因此不会创建重复项。

如果您在填充#orgchart2 和#orgchart3 时省略了 UNION ALL 的上半部分,然后最终生成所有三个临时表的单个 UNION ALL,那么您将模拟优化器的操作。