SQL递归CTE图遍历

时间:2019-09-24 13:23:54

标签: sql-server tsql

我有一个包含少量相关节点的表。我希望能够识别所有相关的节点。

-- Example of some small related nodes.
--         (14)   (5) (8)    (10) (3)
--         /        \   \      \  /
--  (11)-(2)       (12)-(9)    (7)
--         \        /          /  \
--         (4)    (6)        (1)  (13) 

DECLARE @Graph TABLE  (
    A SmallInt NOT NULL,
    B SmallInt NOT NULL
)

INSERT INTO @Graph (A, B)
VALUES 
    (11,  2), ( 2, 14), ( 2,  4), ( 5, 12),
    ( 6, 12), ( 12, 9), ( 8,  9), (10,  7),
    ( 1,  7), ( 7, 13), ( 7,  3);

所需结果

  • 1,13
  • 2、14
  • 3、13
  • 4、14
  • 5、12
  • 6、12
  • 7、13
  • 8、12
  • 9、12
  • 10、13
  • 11、14
  • 12、12
  • 13、13
  • 14、14

CTE接近正确答案,但不完全相同。

WITH Src AS (
    SELECT A, B FROM @Graph
)
, Recurse (A, B) AS (
    SELECT A, B FROM Src
    UNION ALL
    SELECT S.A, R.B FROM Src S INNER JOIN Recurse R ON S.B = R.A 
)
, List AS (
    SELECT A, B FROM Recurse 
    UNION SELECT A, A FROM Src
    UNION SELECT B, B FROM Src
)
SELECT A, MAX(B) B FROM List GROUP BY A ORDER BY 1, 2;

查询结果

  • 1,13
  • 2、14
  • 3、3 <-错误的结果
  • 4,4 <-错误的结果
  • 5、12
  • 6、12
  • 7、13
  • 8、9 <-错误的结果
  • 9,9 <-错误的结果
  • 10、13
  • 11、14
  • 12、12
  • 13、13
  • 14、14

我决定使用MAX节点号将节点关联在一起,但是可以接受其他方法。

1 个答案:

答案 0 :(得分:0)

EzLo必须引诱我到另一篇帖子(How to find all connected subgraphs of an undirected graph),使我能够撰写正确的答案。

DECLARE @Graph TABLE  (
    A SmallInt NOT NULL,
    B SmallInt NOT NULL
)

INSERT INTO @Graph (A, B)
VALUES 
    (11,  2), ( 2, 14), ( 2,  4), ( 5, 12),
    ( 6, 12), ( 12, 9), ( 8,  9), (10,  7),
    ( 1,  7), ( 7, 13), ( 7,  3);


WITH CTE_Idents AS (
    SELECT A AS Ident FROM @Graph
    UNION SELECT B AS Ident FROM @Graph
)
, CTE_Pairs AS (
    SELECT A AS Ident1, B AS Ident2 FROM @Graph WHERE A <> B
    UNION SELECT B AS Ident1, A AS Ident2 FROM @Graph WHERE A <> B
)
, CTE_Recursive AS (
    SELECT
        CTE_Idents.Ident AS AnchorIdent,
        Ident1,
        Ident2,
        CAST(',' + CAST(Ident1 AS VARCHAR(2)) + ',' + CAST(Ident2 AS VARCHAR(2)) + ',' AS varchar(8000)) AS IdentPath
    FROM 
            CTE_Pairs
        INNER JOIN 
            CTE_Idents 
                ON CTE_Idents.Ident = CTE_Pairs.Ident1

    UNION ALL

    SELECT 
        CTE_Recursive.AnchorIdent, 
        CTE_Pairs.Ident1, 
        CTE_Pairs.Ident2, 
        CAST(CTE_Recursive.IdentPath + CAST(CTE_Pairs.Ident2 AS VARCHAR(2)) + ',' AS varchar(8000)) AS IdentPath
    FROM
            CTE_Pairs
        INNER JOIN 
            CTE_Recursive 
                ON CTE_Recursive.Ident2 = CTE_Pairs.Ident1
    WHERE
        CTE_Recursive.IdentPath NOT LIKE CAST('%,' + CAST(CTE_Pairs.Ident2 AS VARCHAR(2)) + ',%' AS varchar(8000))
)
, CTE_RecursionResult AS (
    SELECT AnchorIdent, Ident1, Ident2 FROM CTE_Recursive
)
, CTE_CleanResult AS (
    SELECT AnchorIdent, Ident1 AS Ident FROM CTE_RecursionResult
    UNION SELECT AnchorIdent, Ident2 AS Ident FROM CTE_RecursionResult
)
SELECT AnchorIdent, MAX(Ident) AS Ident FROM CTE_CleanResult GROUP BY AnchorIdent ORDER BY AnchorIdent