代码之家  ›  专栏  ›  技术社区  ›  Ronald Wildenberg

在递归SQL表中查找最低公共父级

  •  3
  • Ronald Wildenberg  · 技术社区  · 15 年前

    0..n

    Id | ParentId
    ---|---------
     1 |     NULL
     2 |        1
     3 |        1
     4 |        2
     5 |        2
     6 |        3
     7 |        3
     8 |        7
    

    然后,以下几组ID将导致以下结果(第一组是角点情况):

    []      => 1 (or NULL, doesn't really matter)
    [1]     => 1
    [2]     => 2
    [1,8]   => 1
    [4,5]   => 2
    [4,6]   => 1
    [6,7,8] => 3
    

    编辑:请注意,并非在所有情况下,parent都是正确的术语。它是树上所有路径中最低的公共节点。最低公共节点也可以是节点本身(例如在 [1,8] => 1 ,节点 1 不是节点的父级 1. 但是节点 1. 本身)。

    亲切问候,,

    2 回复  |  直到 15 年前
        1
  •  5
  •   Marc Gravell    15 年前

    这里有一种方法;它使用递归CTE来查找节点的祖先,并对输入值使用“交叉应用”来获得共同祖先;您只需更改中的值 @ids (表变量):

    ----------------------------------------- SETUP
    CREATE TABLE MyData (
       Id int NOT NULL,
       ParentId int NULL)
    
    INSERT MyData VALUES (1,NULL)
    INSERT MyData VALUES (2,1)
    INSERT MyData VALUES (3,1)
    INSERT MyData VALUES (4,2)
    INSERT MyData VALUES (5,2)
    INSERT MyData VALUES (6,3)
    INSERT MyData VALUES (7,3)
    INSERT MyData VALUES (8,7)
    
    GO
    CREATE FUNCTION AncestorsUdf (@Id int)
    RETURNS TABLE
    AS
    RETURN (
        WITH Ancestors (Id, ParentId)
        AS (
            SELECT Id, ParentId
            FROM MyData
            WHERE Id = @Id
            UNION ALL
            SELECT md.Id, md.ParentId
            FROM MyData md
            INNER JOIN Ancestors a
              ON md.Id = a.ParentId
        )
        SELECT Id FROM Ancestors
    );
    GO
    ----------------------------------------- ACTUAL QUERY
    DECLARE @ids TABLE (Id int NOT NULL)
    DECLARE @Count int
    -- your data (perhaps via a "split" udf)
    INSERT @ids VALUES (6)
    INSERT @ids VALUES (7)
    INSERT @ids VALUES (8)
    
    SELECT @Count = COUNT(1) FROM @ids
    ;
    SELECT TOP 1 a.Id
    FROM @ids
    CROSS APPLY AncestorsUdf(Id) AS a
    GROUP BY a.Id
    HAVING COUNT(1) = @Count
    ORDER BY a.ID DESC
    

    CREATE FUNCTION AncestorsUdf (@Id int)
    RETURNS @result TABLE (Id int, [Level] int)
    AS
    BEGIN
        WITH Ancestors (Id, ParentId, RelLevel)
        AS (
            SELECT Id, ParentId, 0
            FROM MyData
            WHERE Id = @Id
            UNION ALL
            SELECT md.Id, md.ParentId, a.RelLevel - 1
            FROM MyData md
            INNER JOIN Ancestors a
              ON md.Id = a.ParentId
        )
    
        INSERT @result
        SELECT Id, RelLevel FROM Ancestors
    
        DECLARE @Min int
        SELECT @Min = MIN([Level]) FROM @result
    
        UPDATE @result SET [Level] = [Level] - @Min
    
        RETURN
    END
    GO
    

    SELECT TOP 1 a.Id
    FROM @ids
    CROSS APPLY AncestorsUdf(Id) AS a
    GROUP BY a.Id, a.[Level]
    HAVING COUNT(1) = @Count
    ORDER BY a.[Level] DESC
    
        2
  •  4
  •   Ronald Wildenberg    15 年前

    在从Marc的回答(谢谢)中做了一些思考和提示之后,我自己想出了另一个解决方案:

    DECLARE @parentChild TABLE (Id INT NOT NULL, ParentId INT NULL);
    INSERT INTO @parentChild VALUES (1, NULL);
    INSERT INTO @parentChild VALUES (2, 1);
    INSERT INTO @parentChild VALUES (3, 1);
    INSERT INTO @parentChild VALUES (4, 2);
    INSERT INTO @parentChild VALUES (5, 2);
    INSERT INTO @parentChild VALUES (6, 3);
    INSERT INTO @parentChild VALUES (7, 3);
    INSERT INTO @parentChild VALUES (8, 7);
    
    DECLARE @ids TABLE (Id INT NOT NULL);
    INSERT INTO @ids VALUES (6);
    INSERT INTO @ids VALUES (7);
    INSERT INTO @ids VALUES (8);
    
    DECLARE @count INT;
    SELECT @count = COUNT(1) FROM @ids;
    
    WITH Nodes(Id, ParentId, Depth) AS
    (
        -- Start from every node in the @ids collection.
        SELECT pc.Id , pc.ParentId , 0 AS DEPTH
        FROM @parentChild pc
        JOIN @ids i ON pc.Id = i.Id
    
        UNION ALL
    
        -- Recursively find parent nodes for each starting node.
        SELECT pc.Id , pc.ParentId , n.Depth - 1
        FROM @parentChild pc
        JOIN Nodes n ON pc.Id = n.ParentId
    )
    SELECT n.Id
    FROM Nodes n
    GROUP BY n.Id
    HAVING COUNT(n.Id) = @count
    ORDER BY MIN(n.Depth) DESC
    

    TOP 1 选择。