【问题标题】:Finding a Top Level Parent in SQL在 SQL 中查找顶级父级
【发布时间】:2013-07-16 12:48:44
【问题描述】:

我有两张表如下

Person

  Id   Name
   1    A
   2    B
   3    C
   4    D
   5    E

RelationHierarchy

ParentId   ChildId
   2         1
   3         2
   4         3

这将形成一个树状结构

          D
          |
          C
          |
          B
          |
          A

ParentId 和 ChildId 是 Person 表的 Id 列的外键

我需要编写可以获取顶级父级即根目录的 SQL。任何人都可以建议任何可以帮助我完成此任务的 SQL

【问题讨论】:

标签: sql sql-server


【解决方案1】:

您可以使用recursive CTE 来实现:

DECLARE @childID INT 
SET @childID  = 1 --chield to search

;WITH RCTE AS
(
    SELECT *, 1 AS Lvl FROM RelationHierarchy 
    WHERE ChildID = @childID

    UNION ALL

    SELECT rh.*, Lvl+1 AS Lvl FROM dbo.RelationHierarchy rh
    INNER JOIN RCTE rc ON rh.CHildId = rc.ParentId
)
SELECT TOP 1 id, Name
FROM RCTE r
inner JOIN dbo.Person p ON p.id = r.ParentId
ORDER BY lvl DESC

SQLFiddle DEMO

编辑 - 更新所有孩子的顶级父母的请求:

;WITH RCTE AS
(
    SELECT  ParentId, ChildId, 1 AS Lvl FROM RelationHierarchy 

    UNION ALL

    SELECT rh.ParentId, rc.ChildId, Lvl+1 AS Lvl 
    FROM dbo.RelationHierarchy rh
    INNER JOIN RCTE rc ON rh.ChildId = rc.ParentId
)
,CTE_RN AS 
(
    SELECT *, ROW_NUMBER() OVER (PARTITION BY r.ChildID ORDER BY r.Lvl DESC) RN
    FROM RCTE r

)
SELECT r.ChildId, pc.Name AS ChildName, r.ParentId, pp.Name AS ParentName
FROM CTE_RN r
INNER JOIN dbo.Person pp ON pp.id = r.ParentId
INNER JOIN dbo.Person pc ON pc.id = r.ChildId
WHERE RN =1

SQLFiddle DEMO

EDIT2 - 让所有人在最后更改 JOINS:

SELECT pc.Id AS ChildID, pc.Name AS ChildName, r.ParentId, pp.Name AS ParentName
FROM dbo.Person pc 
LEFT JOIN CTE_RN r ON pc.id = r.CHildId AND  RN =1
LEFT JOIN dbo.Person pp ON pp.id = r.ParentId

SQLFiddle DEMo

【讨论】:

  • 我认为大多数 CTE 答案或多或少是正确的,但我发现这个答案非常适合我的问题。非常感谢
  • 我们可以修改这个来获取所有人的父母,即 PersonName 并且它是 Person 表中每一行的顶级父母
  • @user1711287 当然,检查编辑的答案。请注意 CTE 的递归部分的细微差别,ChildID 现在每次都从锚部分重写。还添加了ROW_NUMBER() 函数,用于在最后为每个孩子获取顶层。
  • 太好了,它工作正常。我可以让所有人都知道父母是否存在。如果父级存在,则其名称和 ID 将显示为 NULL,否则将显示为 NULL 作为 PArentID。我需要改变哪一部分?
  • 我想一旦这个 CTE 返回一个结果集,我需要在 Person 和 CTE 的 ResultSet 之间做一个左连接
【解决方案2】:

我已使用此模式将层次结构中的项目与项目的根节点相关联。

本质上递归的层次结构维护根节点的值作为附加到每一行的附加列。希望这会有所帮助。

with allRows as (
    select ItemId, ItemName, ItemId [RootId],ItemName [RootName] 
    from parentChildTable
    where ParentItemId is null
    union all
    select a1.ItemId,a1.ItemName,a2.[RootId],a2.[RootName]
    from parentChildTable a1
    join allRows a2 on a2.ItemId = a1.ParentItemId
)   

select * from allRows

【讨论】:

  • 我一生都无法弄清楚如何格式化这篇文章中的代码行。
  • 我编辑了您的代码,在您的代码中添加了 4 个初始空格字符。
【解决方案3】:

要查找所有顶级父级,请使用如下查询:

select p.Name
from Person p
where not exists
(select null
 from RelationHierarchy r
 where r.ChildId = p.Id)

SQLFiddle here.

要查找特定子级的顶级父级,请使用:

with cte as
(select t.ParentId TopParent, t.ChildId 
 from RelationHierarchy t
 left join RelationHierarchy p on p.ChildId = t.ParentId
 where p.ChildId is null
 union all
 select t.TopParent TopParent, c.ChildId 
 from cte t
 join RelationHierarchy c on t.ChildId = c.ParentId)
select p.name
from cte h
join Person p on h.TopParent = p.Id
where h.ChildId=3 /*or whichever child is required*/

SQLFiddle here.

【讨论】:

    【解决方案4】:

    试试这个。

    递归 CTE 将找到该人并向上遍历层次结构,直到找不到父级。

    -- This CTE will find the ancestors along with a measure of how far up
    -- the hierarchy each ancestor is from the selected person.
    with ancestor as (
      select ParentId as AncestorId, 0 as distance
      from RelationHierarchy
      where CHildId = ?
    
      union all
    
      select h.ParentId, a.distance + 1
      from ancestor a inner join RelationHierarchy rh on a.AncestorId = rh.ChildId
    )
    select AncestorId
    from ancestor
    where distance = (select max(distance) from ancestor)
    

    【讨论】:

    • 回顾我 5.5 年后写的这个答案,在查询中使用 order byselect top 1 可能比标量子查询更简单。 select top 1 AncestorId from ancestor order by distance desc.
    【解决方案5】:

    这样的东西适用于上面的例子:

    SELECT ParentId FROM RelationHierarchy 
    WHERE ParentId NOT IN (SELECT CHildId FROM RelationHierarchy)
    

    【讨论】:

    • 我需要为给定的 Id 找到顶级父级,例如4
    • 这仅在根节点没有子节点时有效。您可能有一个独立节点,但此查询将找不到它。
    【解决方案6】:

    您可以在“标准”SQL 中执行此操作的唯一方法是假设树的最大深度,然后对每个级别进行连接。以下获取顶级id:

    select rh1.ChildId,
           coalesce(rh4.parentid, rh3.parentid, rh2.parentid, rh1.parentid) as topLevel
    from RelationshipHierarchy rh1 left outer join
         RelationshipHierarchy rh2
         on rh1.parentId = rh2.childId left outer join
         RelationshipHierarchy rh3
         on rh2.parentId = rh3.childId left outer join
         RelationshipHierarchy rh4
         on rh3.parentId = rh4.childId;
    

    如果你想要名字,你可以加入它:

    select rh1.ChildId,
           coalesce(rh4.parentid, rh3.parentid, rh2.parentid, rh1.parentid) as topLevel,
           p.name
    from RelationshipHierarchy rh1 left outer join
         RelationshipHierarchy rh2
         on rh1.parentId = rh2.childId left outer join
         RelationshipHierarchy rh3
         on rh2.parentId = rh3.childId left outer join
         RelationshipHierarchy rh4
         on rh3.parentId = rh4.childId left outer join
         Person p
         on p.id = coalesce(rh4.parentid, rh3.parentid, rh2.parentid, rh1.parentid);
    

    【讨论】:

      【解决方案7】:

      使用路径获取所有顶级父母

      路径格式:rootId/.../parentId/nodeId/

      select t1.path from nodes t1 inner join nodes t2
      on t1.path like t2.path+'%' 
      group by t1.path 
      having len(t1.path)-len(replace(t1.path, '/', ''))
      =min(len(t2.path)-len(replace(t2.path, '/', '')))
      

      【讨论】:

        【解决方案8】:

        试一试:

        选择身份证,姓名 来自人 p 不存在的地方 ( 选择 1 从关系层次 r 其中 r.childid= p.id ) 并且存在 ( 选择 1 从关系层次 r 其中 r.parentid= p.id )

        仅查看子 id 是否存在是不够的,如您的示例中 E 存在于 person 表中但不存在于 relationshiphierarchy 表中。

        【讨论】:

        • 每个人都需要一个顶级父级。我们也需要 JOIN 吗?
        • “每个人都需要一个顶级父级。”您在最初的问题中没有提到这一点;)
        【解决方案9】:
        WITH CTE_MyTable AS (
            SELECT        Id, ParentId, NULL As RootParent, 1 As Lvl
            FROM            dbo.Ministry
            UNION ALL
            SELECT        a.id, b.ParentId, a.ParentId As RootParent, Lvl + 1
            FROM            CTE_MyTableAS a INNER JOIN
                                                       dbo.MyTableAS b ON a.ParentId = b.Id
        )
        , CTE_Ministry_RN AS  (
            SELECT Id, RootParent, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Lvl DESC) RN
            FROM CTE_Ministry
        )
        
        SELECT Id, ISNULL(RootParent, Id) As RootParent
        FROM CTE_Ministry_RN
        WHERE RN = 1
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-31
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多