【问题标题】:Recursive CTE not getting desired result. How to assign anchor only?递归 CTE 没有得到想要的结果。如何仅分配锚点?
【发布时间】:2023-03-10 20:45:01
【问题描述】:

该应用程序是为一个员工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 中的硬编码)?

【问题讨论】:

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


    【解决方案1】:

    你需要在你的 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 |
    +-----+----+------+-----+
    

    【讨论】:

    • 这行得通。我还不能接受。我以为我尝试了您的答案,但一定有问题。
    • @Paparazzi 对于较大的表,这将变得非常慢。它一遍又一遍地调用每个级别的递归部分...您可以添加where not exists(SELECT 1 FROM @emp AS x WHERE e.id=x.mgr) 以仅从叶节点开始...但最好的是 - 如果您提前知道这一点 - 指定最窄的锚.递归 CTE 是隐藏的 RBAR...
    • HoneyBadger,您应该将锚点减少到最窄的集合(请参阅上面的评论或阅读我的答案)。这行得通,但价格非常高......
    • @Shnugo,我完全同意,我认为您的回答(和 cmets)涵盖了这一点。价格是否过高由 OP 决定。
    • @HoneyBadger 当然,这没问题......但无论如何:为锚点添加叶节点过滤器至少可以避免结果中的重叠链,这会使这个解决方案更加膨胀 -不改变结果...
    【解决方案2】:

    你必须决定是否

    • 从根节点开始(将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),向上移动会快得多。从给定的子节点开始每个级别只有一个步骤 - 就是这样。

    WHERE 中应用过滤器递归 CTE 被制定出来之后,意味着创建任何可能的链只是为了将它们中的大部分扔掉......

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

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

    【讨论】:

    • @Paparazzi 是的,当然,但价格非常高。如果您的列表很小 - 没问题,但方法是 - 老实说 - 你可以采取的最糟糕的......
    • @Paparazzi 我看到你已经接受了这个答案。这当然是你的选择。但是您至少应该将条件添加到您的锚点以仅从叶节点开始......(请参阅 HoneyBadger 答案下方的评论)。答案 - 按原样 - 非常多余......
    • @Paparazzi 你有理由,为什么要在最后应用过滤器吗?
    • 我了解价格高。另一个查询是 select ori, id, mgr, cnt from cte order by ori, cnt。
    • @Paparazzi 我已经更新了我的答案,您可能会阅读它以了解一些详细信息。如果您的清单很小 - 请继续。如果你有更大的集合,你应该重新考虑这个......
    【解决方案3】:

    我只做了一项更改并删除了(看似)不必要的列:

    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
    

    【讨论】:

    • 那么,期望的输出是什么? 3 | 2, 1 ?
    • 是的,走管理链。接受的答案显示所需的输出。
    猜你喜欢
    • 2017-07-03
    • 1970-01-01
    • 1970-01-01
    • 2016-03-14
    • 2022-09-29
    • 2020-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多