Reputation: 45096
The application is for an employee id get all managers
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;
The above only returns the single row for id = 3. I get that is expected but not what I desire. I want to start (anchor) at 3 and get the manager chain.
If I hard code the anchor I get the desired result.
See below:
; 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;
My question is how to assign the anchor (top part) only in a where
on the cte?
If not in the where
is there another way to assign the anchor only (not hard coding in the cte)?
Upvotes: 2
Views: 191
Reputation: 67311
You must decide whether to
WHERE mgr IS NULL
or an explicit id to your anchor) and move down the chain, or to where not exists(SELECT 1 FROM @emp AS x WHERE e.id=x.mgr)
to the anchor) and move up or towhere e.id=3
to the anchor) and move up.The general advise is: Start with the narrowest anchor-set possible!
The first query you state in your question (as well as other answers here) will do a huge overdose creating each an any chain starting from everywhere with overlapping results.
As the recursive CTE is a hidden RBAR the engine has no chance to predict its result and will create the full load - just to throw most of this away.
If this is a 1:n
relation (always 1 mgr on-top) moving upwards will be much faster. Starting from a given child node You have exactly one step per level - that's it.
Applying the filter in a WHERE
after the recursice CTE was worked out, means to create any possible chain just to throw most of them away...
Your second approach is the best I can think of actually.
So the question is: Why do you want to apply this filter at the end?
Upvotes: 1
Reputation: 15140
You need to keep the starting position in your 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;
Result:
+-----+----+------+-----+
| ori | id | mgr | cnt |
+-----+----+------+-----+
| 3 | 3 | 2 | 1 |
| 3 | 2 | 1 | 2 |
| 3 | 1 | NULL | 3 |
+-----+----+------+-----+
Upvotes: 3
Reputation: 37377
I made just one change and removed (seemingly) unnecessary column:
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;
Result is:
id | mgr
3 | 2
3 | 1
3 | NULL
Upvotes: 2