Reputation: 13
I have a table like the below:
Id ParentID
1 99
2 9
3 1
4 2
5 4
6 3
and I would like to have a query that gives me for each child the last ancestor.
I mean that the desired result is
id Lastancestor
1 99
2 9
3 99
4 9
5 9
6 99
I have a lot of data so I need something quick.
Thanks.
Upvotes: 0
Views: 152
Reputation: 72205
You can use a Recursive CTE to accomplish this:
;WITH CTE AS (
SELECT Id AS origId, ParentID, 0 AS lvl
FROM mytable
UNION ALL
SELECT c.origId AS origId,
m.ParentID, lvl = lvl + 1
FROM CTE AS c
INNER JOIN mytable AS m ON c.ParentID = m.Id
)
SELECT origId AS id, ParentID AS Lastancestor
FROM (
SELECT origId, ParentID,
ROW_NUMBER() OVER (PARTITION BY origId
ORDER BY lvl DESC) AS rn
FROM CTE) AS t
WHERE t.rn = 1
Here, the anchor member of the CTE is just the whole table. Recursion goes up the tree hierarchy while propagating the original Id
(as origId
) down the recursion chain. The recursion terminates as soon as an empty set is returned, i.e. as soon as no more c.ParentID = m.Id
matches are found.
To get the required result, i.e. the Lastancestor
per id
, all we need to do is fetch the record having the greatest lvl
(i.e. depth) per id
. This is achieved using ROW_NUMBER
window function.
Upvotes: 2
Reputation: 67321
If there is a maximum depth you could use this approach. You can add further depth levels with simple copy and past and adapt. I added one data element "19,6" to generate one with three ancestors and one with four.
Just paste this into an empty query window and execute. Adapt to your needs...
declare @Test table (Id int, ParentID int)
insert into @Test values
(1,99)
,(2,9)
,(3,1)
,(4,2)
,(5,4)
,(6,3)
,(19,6);
WITH Ancestors1 AS
(
SELECT Test.*
,Ancestor.ParentID AS Anc1ID
FROM @Test AS Test
LEFT JOIN @Test AS Ancestor ON Test.ParentID=Ancestor.Id
)
,Ancestors2 AS
(
SELECT Ancestors1.*
, Ancestor.ParentID AS Anc2ID
FROM Ancestors1
LEFT JOIN @Test AS Ancestor ON Ancestors1.Anc1ID=Ancestor.Id
)
,Ancestors3 AS
(
SELECT Ancestors2.*
, Ancestor.ParentID AS Anc3ID
FROM Ancestors2
LEFT JOIN @Test AS Ancestor ON Ancestors2.Anc2ID=Ancestor.Id
)
SELECT Id,*
,COALESCE(Anc3ID,Anc2ID,Anc1ID,ParentID) AS LastAncId
FROM Ancestors3
Upvotes: 0