【问题标题】:Find lowest common parent in recursive SQL table在递归 SQL 表中查找最低公共父级
【发布时间】:2010-11-03 14:16:29
【问题描述】:

假设我有一个递归表(例如员工和经理)和一个大小为0..n 的 ID 列表。如何找到这些 id 的最低共同父项?

例如,如果我的表格如下所示:

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

如何做到这一点?

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

亲切的问候, 罗纳德

【问题讨论】:

  • 如果单项,这真的是最低的共同父母或自我。
  • 没错,如果self恰好是最低公共节点,也是self。我稍微修改了我的问题以考虑到这一点。

标签: sql sql-server tsql recursion common-table-expression


【解决方案1】:

这是一种方法;它使用递归 CTE 来查找节点的祖先,并在输入值上使用“CROSS APPLY”来获取共同祖先;您只需更改@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

【讨论】:

  • 谢谢。我在理解它的工作原理时遇到了一些麻烦,但我现在明白了。如果我说最后的 'ORDER BY a.ID DESC' 仅因为在当前配置中较低的节点具有较高的 id 而起作用,我是否正确?
  • 我自己想出了一个不需要 UDF 并且使用正确顺序的版本。我会尽快发布的..
  • 嗨,我将自己的答案标记为答案。它更紧凑,不需要 UDF。感谢您将我推向正确的方向。
【解决方案2】:

在从 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 的问题。

【讨论】:

  • 您可能必须注意深度是自上而下,而不是自下而上 - 这可能会有所不同。我没有费心去考虑每一个边缘情况;-p 不过看起来不错,+1
  • 深度有点容易被解释;-p 例如,你的树有什么方向。我总是把它倒过来(根节点在顶部)。因此,我为树中的每个“向上”级别减去 1。无论如何,我认为我的顺序是正确的(至少我希望如此)......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-31
  • 1970-01-01
相关资源
最近更新 更多