sql >> データベース >  >> RDS >> Sqlserver

無向グラフのすべての接続されたサブグラフを見つける方法

    これは、カーソルを使用しないが、単一の再帰クエリを使用するバリアントです。

    基本的に、データをグラフのエッジとして扱い、グラフのすべてのエッジを再帰的にトラバースし、ループが検出されると停止します。次に、見つかったすべてのループをグループに入れ、各グループに番号を付けます。

    以下の仕組みの詳細な説明を参照してください。 CTEごとにクエリを実行し、各中間結果を調べて、それが何をするのかを理解することをお勧めします。

    サンプル1

    DECLARE @T TABLE (ID int, Ident1 char(1), Ident2 char(1));
    INSERT INTO @T (ID, Ident1, Ident2) VALUES
    (1, 'a', 'a'),
    (2, 'b', 'b'),
    (3, 'c', 'a'),
    (4, 'c', 'b'),
    (5, 'c', 'c');
    

    サンプル2

    zでもう1行追加しました 値が対になっていない値を持つ複数の行を持つようにします。

    DECLARE @T TABLE (ID int, Ident1 char(1), Ident2 char(1));
    INSERT INTO @T (ID, Ident1, Ident2) VALUES
    (1, 'a', 'a'),
    (1, 'a', 'c'),
    (2, 'b', 'f'),
    (3, 'a', 'g'),
    (4, 'c', 'h'),
    (5, 'b', 'j'),
    (6, 'd', 'f'),
    (7, 'e', 'k'),
    (8, 'i', NULL),
    (88, 'z', 'z'),
    (9, 'l', 'h');
    

    サンプル3

    DECLARE @T TABLE (ID int, Ident1 char(1), Ident2 char(1));
    INSERT INTO @T (ID, Ident1, Ident2) VALUES
    (1, 'a', 'f'),
    (2, 'a', 'g'),
    (3, 'a', NULL),
    (4, 'b', 'c'),
    (5, 'b', 'a'),
    (6, 'b', 'h'),
    (7, 'b', 'j'),
    (8, 'b', NULL),
    (9, 'b', NULL),
    (10, 'b', 'g'),
    (11, 'c', 'k'),
    (12, 'c', 'b'),
    (13, 'd', 'l'),
    (14, 'd', 'f'),
    (15, 'd', 'g'),
    (16, 'd', 'm'),
    (17, 'd', 'a'),
    (18, 'd', NULL),
    (19, 'd', 'a'),
    (20, 'e', 'c'),
    (21, 'e', 'b'),
    (22, 'e', NULL);
    

    クエリ

    WITH
    CTE_Idents
    AS
    (
        SELECT Ident1 AS Ident
        FROM @T
    
        UNION
    
        SELECT Ident2 AS Ident
        FROM @T
    )
    ,CTE_Pairs
    AS
    (
        SELECT Ident1, Ident2
        FROM @T
        WHERE Ident1 <> Ident2
    
        UNION
    
        SELECT Ident2 AS Ident1, Ident1 AS Ident2
        FROM @T
        WHERE Ident1 <> Ident2
    )
    ,CTE_Recursive
    AS
    (
        SELECT
            CAST(CTE_Idents.Ident AS varchar(8000)) AS AnchorIdent 
            , Ident1
            , Ident2
            , CAST(',' + Ident1 + ',' + Ident2 + ',' AS varchar(8000)) AS IdentPath
            , 1 AS Lvl
        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 + CTE_Pairs.Ident2 + ',' AS varchar(8000)) AS IdentPath
            , CTE_Recursive.Lvl + 1 AS Lvl
        FROM
            CTE_Pairs
            INNER JOIN CTE_Recursive ON CTE_Recursive.Ident2 = CTE_Pairs.Ident1
        WHERE
            CTE_Recursive.IdentPath NOT LIKE CAST('%,' + CTE_Pairs.Ident2 + ',%' 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
        CTE_Idents.Ident
        ,CASE WHEN CA_Data.XML_Value IS NULL 
        THEN CTE_Idents.Ident ELSE CA_Data.XML_Value END AS GroupMembers
        ,DENSE_RANK() OVER(ORDER BY 
            CASE WHEN CA_Data.XML_Value IS NULL 
            THEN CTE_Idents.Ident ELSE CA_Data.XML_Value END
        ) AS GroupID
    FROM
        CTE_Idents
        CROSS APPLY
        (
            SELECT CTE_CleanResult.Ident+','
            FROM CTE_CleanResult
            WHERE CTE_CleanResult.AnchorIdent = CTE_Idents.Ident
            ORDER BY CTE_CleanResult.Ident FOR XML PATH(''), TYPE
        ) AS CA_XML(XML_Value)
        CROSS APPLY
        (
            SELECT CA_XML.XML_Value.value('.', 'NVARCHAR(MAX)')
        ) AS CA_Data(XML_Value)
    WHERE
        CTE_Idents.Ident IS NOT NULL
    ORDER BY Ident;
    

    結果1

    +-------+--------------+---------+
    | Ident | GroupMembers | GroupID |
    +-------+--------------+---------+
    | a     | a,b,c,       |       1 |
    | b     | a,b,c,       |       1 |
    | c     | a,b,c,       |       1 |
    +-------+--------------+---------+
    

    結果2

    +-------+--------------+---------+
    | Ident | GroupMembers | GroupID |
    +-------+--------------+---------+
    | a     | a,c,g,h,l,   |       1 |
    | b     | b,d,f,j,     |       2 |
    | c     | a,c,g,h,l,   |       1 |
    | d     | b,d,f,j,     |       2 |
    | e     | e,k,         |       3 |
    | f     | b,d,f,j,     |       2 |
    | g     | a,c,g,h,l,   |       1 |
    | h     | a,c,g,h,l,   |       1 |
    | i     | i            |       4 |
    | j     | b,d,f,j,     |       2 |
    | k     | e,k,         |       3 |
    | l     | a,c,g,h,l,   |       1 |
    | z     | z            |       5 |
    +-------+--------------+---------+
    

    結果3

    +-------+--------------------------+---------+
    | Ident |       GroupMembers       | GroupID |
    +-------+--------------------------+---------+
    | a     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | b     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | c     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | d     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | e     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | f     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | g     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | h     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | j     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | k     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | l     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    | m     | a,b,c,d,e,f,g,h,j,k,l,m, |       1 |
    +-------+--------------------------+---------+
    

    仕組み

    この説明には、2番目のサンプルデータセットを使用します。

    CTE_Idents

    CTE_Idents 両方のIdent1に表示されるすべての識別子のリストを示します およびIdent2 列。任意の順序で表示できるため、UNION 両方の列を一緒に。 UNION 重複も削除します。

    +-------+
    | Ident |
    +-------+
    | NULL  |
    | a     |
    | b     |
    | c     |
    | d     |
    | e     |
    | f     |
    | g     |
    | h     |
    | i     |
    | j     |
    | k     |
    | l     |
    | z     |
    +-------+
    

    CTE_Pairs

    CTE_Pairs グラフの両方向のすべてのエッジのリストを示します。繰り返しますが、UNION 重複を削除するために使用されます。

    +--------+--------+
    | Ident1 | Ident2 |
    +--------+--------+
    | a      | c      |
    | a      | g      |
    | b      | f      |
    | b      | j      |
    | c      | a      |
    | c      | h      |
    | d      | f      |
    | e      | k      |
    | f      | b      |
    | f      | d      |
    | g      | a      |
    | h      | c      |
    | h      | l      |
    | j      | b      |
    | k      | e      |
    | l      | h      |
    +--------+--------+
    

    CTE_Recursive

    CTE_Recursive は、一意の各識別子から開始してグラフを再帰的にトラバースするクエリの主要部分です。これらの開始行は、UNION ALLの最初の部分によって生成されます。 。UNION ALLの2番目の部分 Ident2をリンクして再帰的に結合します Ident1へ 。CTE_Pairsを事前に作成したので すべてのエッジが両方向に書き込まれているため、常にリンクできるのはIdent2のみです。 Ident1へ グラフ内のすべてのパスを取得します。同時に、クエリはIdentPathを構築します。 -これまでにトラバースされたカンマ区切りの識別子の文字列。WHEREで使用されます。 フィルタ:

    CTE_Recursive.IdentPath NOT LIKE CAST('%,' + CTE_Pairs.Ident2 + ',%' AS varchar(8000))
    

    以前にパスに含まれていた識別子に遭遇するとすぐに、接続されたノードのリストが使い果たされるため、再帰が停止します。AnchorIdent は再帰の開始識別子であり、後で結果をグループ化するために使用されます。Lvl 実際には使用されていないので、何が起こっているのかをよりよく理解するために含めました。

    +-------------+--------+--------+-------------+-----+
    | AnchorIdent | Ident1 | Ident2 |  IdentPath  | Lvl |
    +-------------+--------+--------+-------------+-----+
    | a           | a      | c      | ,a,c,       |   1 |
    | a           | a      | g      | ,a,g,       |   1 |
    | b           | b      | f      | ,b,f,       |   1 |
    | b           | b      | j      | ,b,j,       |   1 |
    | c           | c      | a      | ,c,a,       |   1 |
    | c           | c      | h      | ,c,h,       |   1 |
    | d           | d      | f      | ,d,f,       |   1 |
    | e           | e      | k      | ,e,k,       |   1 |
    | f           | f      | b      | ,f,b,       |   1 |
    | f           | f      | d      | ,f,d,       |   1 |
    | g           | g      | a      | ,g,a,       |   1 |
    | h           | h      | c      | ,h,c,       |   1 |
    | h           | h      | l      | ,h,l,       |   1 |
    | j           | j      | b      | ,j,b,       |   1 |
    | k           | k      | e      | ,k,e,       |   1 |
    | l           | l      | h      | ,l,h,       |   1 |
    | l           | h      | c      | ,l,h,c,     |   2 |
    | l           | c      | a      | ,l,h,c,a,   |   3 |
    | l           | a      | g      | ,l,h,c,a,g, |   4 |
    | j           | b      | f      | ,j,b,f,     |   2 |
    | j           | f      | d      | ,j,b,f,d,   |   3 |
    | h           | c      | a      | ,h,c,a,     |   2 |
    | h           | a      | g      | ,h,c,a,g,   |   3 |
    | g           | a      | c      | ,g,a,c,     |   2 |
    | g           | c      | h      | ,g,a,c,h,   |   3 |
    | g           | h      | l      | ,g,a,c,h,l, |   4 |
    | f           | b      | j      | ,f,b,j,     |   2 |
    | d           | f      | b      | ,d,f,b,     |   2 |
    | d           | b      | j      | ,d,f,b,j,   |   3 |
    | c           | h      | l      | ,c,h,l,     |   2 |
    | c           | a      | g      | ,c,a,g,     |   2 |
    | b           | f      | d      | ,b,f,d,     |   2 |
    | a           | c      | h      | ,a,c,h,     |   2 |
    | a           | h      | l      | ,a,c,h,l,   |   3 |
    +-------------+--------+--------+-------------+-----+
    

    CTE_CleanResult

    CTE_CleanResult CTE_Recursiveから関連する部分のみを残します そして再び両方のIdent1をマージします およびIdent2 UNIONを使用する 。

    +-------------+-------+
    | AnchorIdent | Ident |
    +-------------+-------+
    | a           | a     |
    | a           | c     |
    | a           | g     |
    | a           | h     |
    | a           | l     |
    | b           | b     |
    | b           | d     |
    | b           | f     |
    | b           | j     |
    | c           | a     |
    | c           | c     |
    | c           | g     |
    | c           | h     |
    | c           | l     |
    | d           | b     |
    | d           | d     |
    | d           | f     |
    | d           | j     |
    | e           | e     |
    | e           | k     |
    | f           | b     |
    | f           | d     |
    | f           | f     |
    | f           | j     |
    | g           | a     |
    | g           | c     |
    | g           | g     |
    | g           | h     |
    | g           | l     |
    | h           | a     |
    | h           | c     |
    | h           | g     |
    | h           | h     |
    | h           | l     |
    | j           | b     |
    | j           | d     |
    | j           | f     |
    | j           | j     |
    | k           | e     |
    | k           | k     |
    | l           | a     |
    | l           | c     |
    | l           | g     |
    | l           | h     |
    | l           | l     |
    +-------------+-------+
    

    最終選択

    次に、コンマで区切られたIdentの文字列を作成する必要があります 各AnchorIdentの値 。CROSS APPLY FOR XMLを使用 DENSE_RANK() GroupIDを計算します 各AnchorIdentの番号 。



    1. リカバリが遅延したログ配布を使用したデータ損失の修正

    2. MySQL高可用性フレームワークの説明–パートIII:障害シナリオ

    3. Coalesce()がSQLiteでどのように機能するか

    4. MySQLテーブルの最大行数を設定するにはどうすればよいですか?