2015-09-09 53 views
0

的grandgrandparent我有一个表像之下:递归SQL - 发现孩子

Id ParentID 1 99 2 9 3 1 4 2 5 4 6 3

,我想有一个查询,让我对每个孩子的最后一个祖先。

我的意思是,理想的结果是

id Lastancestor 1 99 2 9 3 99 4 9 5 9 6 99

我有很多数据,所以我需要的东西很快。

谢谢。

+3

你需要的是一个[递归公用表表达式(https://technet.microsoft.com/en-us/library/ms186243(V = SQL.105)的.aspx) – Magnus

+0

和例如来自SO的优秀帮助:http://stackoverflow.com/questions/22909692/sql-server-cte-parent-child-recursive – xQbert

+0

有没有最大深度? – Shnugo

回答

2

可以使用Recursive CTE来实现:

;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 

这里,锚固构件的CTE的只是整个表。在递归链中向下传播原始Id(如origId)时递归上升到树层次结构。一旦返回空集合,即一旦找到不再有c.ParentID = m.Id匹配,递归就终止。

为了得到所需的结果,即每Lastancestorid,我们需要做的是为获取每id最大lvl(即深度)的记录。这是使用ROW_NUMBER窗口函数实现的。

Demo here

+0

非常好的解决方案! +1从我身边 – Shnugo

+0

一个问题:你是否在**真实生活**中使用了这种方法?我非常感谢,但是 - 正如OP所说 - 有“大量数据”,它必须“快速”。如果CTE填充大表,这种递归将导致许多全表扫描。我怀疑,有索引的用法......你有什么经验呢?无论如何,我喜欢它,必须做一些测试...... – Shnugo

+0

@Shnugo这是SQL Server中查询分层数据结构的首选方法。唯一的另一种替代AFAIK是使用效率较低的游标。尽管如此,我从来没有在现实生活中使用过递归CTE。 OP可以告诉我们它如何执行他的实际数据。 –

0

如果存在最大深度,则可以使用此方法。您可以添加更多的深度级别与简单的复制和过去和适应。我添加了一个数据元素“19,6”,以生成一个有三个祖先和一个有四个祖先的数据元素。

只需将其粘贴到一个空的查询窗口并执行即可。适应你的需要......

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