递归CTE没有得到理想的结果。如何只分配锚点?

时间:2018-02-14 10:52:12

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

该应用程序用于员工ID获取所有经理

declare @emp table (id  int primary key, mgr int);  
insert into @emp values 
       (1, null)
     , (2, 1)
     , (3, 2)
     , (4, null)
     , (5, 4);
select * from @emp;

; with cte as 
( select e.id, e.mgr, cnt = 1 
  from @emp e  
  union all 
  select e.id, e.mgr, cnt + 1
  from @emp e 
  join cte 
    on cte.mgr = e.id 
) 

 select id, mgr, cnt  
 from cte 
 where id = 3;

以上只返回id = 3的单行。我得到的是预期但不是我想要的。我想在3点开始(锚定)并获得经理链。

如果我对锚点进行硬编码,我会得到所需的结果 见下文:

; with cte as 
( select e.id, e.mgr, cnt = 1 
  from @emp e 
  where e.id = 3 
  union all 
  select e.id, e.mgr, cnt + 1
  from @emp e 
  join cte 
    on cte.mgr = e.id 
) 

 select id, mgr, cnt  
 from cte;

我的问题是如何仅在cte上的where分配锚点(顶部)? 如果不在where中,是否还有另一种方法来分配锚点(不是在cte中硬编码)?

3 个答案:

答案 0 :(得分:3)

您需要在cte中保留起始位置:

declare @emp table (id  int primary key, mgr int);  
insert into @emp values 
       (1, null)
     , (2, 1)
     , (3, 2)
     , (4, null)
     , (5, 4);

; with cte as 
( select e.id ori, e.id, e.mgr, cnt = 1 
  from @emp e  
  union all 
  select cte.ori,  e.id, e.mgr, cnt + 1
  from @emp e 
  join cte 
    on cte.mgr = e.id 
) 

 select ori, id, mgr, cnt  
 from cte 
 where cte.ori = 3;

结果:

+-----+----+------+-----+
| ori | id | mgr  | cnt |
+-----+----+------+-----+
|   3 |  3 | 2    |   1 |
|   3 |  2 | 1    |   2 |
|   3 |  1 | NULL |   3 |
+-----+----+------+-----+

答案 1 :(得分:2)

我只进行了一次更改并删除了(看似)不必要的列:

declare @emp table (id  int primary key, mgr int);  
insert into @emp values 
       (1, null)
     , (2, 1)
     , (3, 2)
     , (4, null)
     , (5, 4);
select * from @emp;

; with cte as 
( select e.id, e.mgr
  from @emp e  
  union all 
  select cte.id, e.mgr
  from @emp e 
  join cte 
    on cte.mgr = e.id 
) 

 select id, mgr
 from cte 
 where id = 3;

结果是:

id | mgr
3  | 2
3  | 1
3  | NULL

答案 2 :(得分:1)

您必须决定是否

  • 从根节点开始(向您的锚点添加WHERE mgr IS NULL或显式ID)并向下移动链接,或者
  • 从任何子节点开始(将where not exists(SELECT 1 FROM @emp AS x WHERE e.id=x.mgr)添加到锚点)并向上或向
  • 移动
  • 从明确的孩子开始(向{}添加where e.id=3)并向上移动。

一般建议是:从最窄的锚点开始!

您在问题中陈述的第一个查询(以及此处的其他答案)将执行巨大过量处开始创建每个任意链重叠结果

由于递归CTE是一个隐藏的RBAR ,引擎无法预测其结果并将创建满载 - 只是为了抛弃大部分内容。

如果这是一个1:n关系(总是1 mgr on-top),向上移动会快得多。从给定的子节点开始每个级别只有一步 - 就是这样。

之后将过滤器应用于递归CTE,意味着创建任何可能的链只是为了将其中大部分放弃...

你的第二种方法是我能想到的最好方法。

所以问题是:你为什么要在最后应用这个过滤器?