使用递归CTE对父母列表中的所有子项进行分组

时间:2014-10-23 18:58:26

标签: sql-server hierarchy common-table-expression

我有一个包含两列的表 - ChildPersonId和ParentPersonId。两个列中不能包含相同的ID。

CREATE TABLE Relationship
 (PkId int IDENTITY(1,1) PRIMARY KEY, ChildPersonId int, GuardianPersonId int)

INSERT INTO Relationship (ChildPersonId, GuardianPersonId) VALUES
(42,24),(42,25),(42,56),(42,56),(43,24),(43,25),(43,26),(43,27),
(43,56),(44,29),(44,30),(45,31),(45,33),(46,34),(47,35),(48,36),
(48,37),(49,36),(49,37),(50,38),(50,39),(51,38),(51,39),(52,40),
(52,41),(53,40),(53,41),(57,24),(57,25),(57,26),(57,27),(57,56),
(63,24),(63,25),(63,26),(63,27),(63,56),(63,59),(64,59),(64,61),
(65,61),(65,62)

我想要一个查询,我可以根据数据中定义的关系传递ChildPersonId并返回所有相关的子项。因此,如果一个孩子有父母,我需要找到该父母的所有其他孩子,然后与那些孩子找到他们的父母,然后与那些父母一起找到孩子......你得到了递归图片。

ALMOST下面链接中的查询有效,但在性能方面有所下降:

Using recursive CTE to resolve a group, not hierarchy

以下示例实际确实返回了我想要的结果:

SELECT ChildPersonId FROM Relationship M5 INNER JOIN
(SELECT GuardianPersonId FROM Relationship M4 INNER JOIN
 (SELECT ChildPersonId FROM Relationship M3 INNER JOIN
  (SELECT GuardianPersonId FROM Relationship M2 INNER JOIN
   (SELECT ChildPersonId FROM Relationship m1 INNER JOIN
    (SELECT GuardianPersonId FROM Relationship
      WHERE ChildPersonId = 42) g1
       ON m1.GuardianPersonId = g1.GuardianPersonId) c1
    ON m2.ChildPersonId = c1.ChildPersonId) g2
   ON m3.GuardianPersonId = g2.GuardianPersonId) c2
  ON m4.ChildPersonId = c2.ChildPersonId) g3
 ON m5.GuardianPersonId = g3.GuardianPersonId
 GROUP BY ChildPersonId

然而,这是一个丑陋的代码,我只会递归次数,因为我愿意削减&糊。

任何人都可以向我提示我需要关闭的递归CTE逻辑 - 没有上面示例中的WHERE子句破坏执行计划吗?

链接Getting all the children of a parent using MSSQL query显示另一种方法,但不返回ChildPersonIds 64和65 - 它似乎没有足够的递归。

非常感谢任何帮助 - 提前感谢。

1 个答案:

答案 0 :(得分:0)

正如您所指出的,传统的CTE方法将匹配所有人ID 64和65.该方法很容易找到元素63.它找不到64和65,因为没有直接到达的递归路径。

例如,未达到64,因为唯一的链接是63到父59.但是,59没有链接到任何其他元素,因为它基本上是树中多个根节点之一。此外,未达到65,因为它的唯一路径是如上所述找到64,然后遍历其父61(也是根节点)到它的另一个孩子65.

传统的CTE递归不会找到那些备用路径,因此我已经包含了传统方法以及扩展,以便将父母兄弟姐妹作为关系路径进行迭代。由于数据结构无法退出递归条件,因此此扩展需要的不仅仅是CTE。

传统的CTE方法

DECLARE @ChildPersonId INT = 42
; WITH cte AS (
    SELECT
        ChildPersonId,
        GuardianPersonId
    FROM Relationship R
    WHERE ChildPersonId = @ChildPersonId
    UNION ALL
    SELECT
        R.ChildPersonId,
        R.GuardianPersonId
    FROM cte
        INNER JOIN Relationship R
            ON cte.GuardianPersonId = R.ChildPersonId
)
SELECT DISTINCT
    R.ChildPersonId
FROM cte
    INNER JOIN Relationship R
        ON cte.GuardianPersonId = R.GuardianPersonId

传统方法将产生

ChildPersonId
-------------
42
43
57
63

对传统方法的扩展

CREATE FUNCTION dbo.f_GetRelations(
    @ChildPersonId INT
) RETURNS @Results TABLE (
    ChildPersonId INT
)
AS 
BEGIN
    ;WITH cte AS (
        SELECT
            ChildPersonId,
            GuardianPersonId
        FROM Relationship R
        WHERE ChildPersonId = @ChildPersonId
        UNION ALL
        SELECT
            R.ChildPersonId,
            R.GuardianPersonId
        FROM cte
            INNER JOIN Relationship R
                ON cte.GuardianPersonId = R.ChildPersonId
    )
        INSERT @Results
            SELECT DISTINCT
                R.ChildPersonId
            FROM cte
                INNER JOIN Relationship R
                    ON cte.GuardianPersonId = R.GuardianPersonId
    DECLARE @Rows INT = -1
    WHILE @Rows <> 0 BEGIN
        INSERT @Results
            SELECT DISTINCT
                C.ChildPersonId
            FROM @Results A
                INNER JOIN Relationship B
                    ON A.ChildPersonId = B.ChildPersonId
                INNER JOIN Relationship C
                    ON B.GuardianPersonId = C.GuardianPersonId
            WHERE NOT EXISTS (SELECT 1 FROM @Results WHERE ChildPersonId = C.ChildPersonId)
        SET @Rows = @@ROWCOUNT
    END
    RETURN
END
GO

SELECT A.ChildPersonId FROM f_GetRelations(42) A

此方法的输出产生与预期输出的匹配

ChildPersonId
-------------
42
43
57
63
64
65