Hier angepasst ich meine alte Antwort aus einer ähnlichen Frage SO Wie alle angeschlossenen Untergraphen eines ungerichteten Graphen zu finden .
Diese Variante verwendet keinen Cursor oder eine explizite Schleife, sondern eine einzelne rekursive Abfrage.
Im Wesentlichen werden die Daten als Kanten in einem Diagramm behandelt und alle Kanten des Diagramms rekursiv durchlaufen, wobei angehalten wird, wenn die Schleife erkannt wird. Dann werden alle gefundenen Schleifen in Gruppen zusammengefasst und jeder Gruppe eine Nummer gegeben.
Unten finden Sie detaillierte Erklärungen zur Funktionsweise. Ich empfehle Ihnen, die Abfrage CTE für CTE auszuführen und jedes Zwischenergebnis zu untersuchen, um zu verstehen, was es tut.
Beispieldaten
DECLARE @T TABLE (ID1 int NOT NULL, ID2 int NOT NULL);
INSERT INTO @T (ID1, ID2) VALUES
(10, 20),
(10, 30),
(30, 30),
(10, 40),
(50, 70),
(60, 50),
(70, 70),
(4457745, 255714),
(4457745,2540222),
(2540222,4457745),
( 255714,4457745);
Abfrage
WITH
CTE_Ids
AS
(
SELECT ID1 AS ID
FROM @T
UNION
SELECT ID2 AS ID
FROM @T
)
,CTE_Pairs
AS
(
SELECT ID1, ID2
FROM @T
WHERE ID1 <> ID2
UNION
SELECT ID2 AS ID1, ID1 AS ID2
FROM @T
WHERE ID1 <> ID2
)
,CTE_Recursive
AS
(
SELECT
CAST(CTE_Ids.ID AS varchar(8000)) AS AnchorID
,ID1
,ID2
,CAST(
',' + CAST(ID1 AS varchar(8000)) +
',' + CAST(ID2 AS varchar(8000)) +
',' AS varchar(8000)) AS IdPath
,1 AS Lvl
FROM
CTE_Pairs
INNER JOIN CTE_Ids ON CTE_Ids.ID = CTE_Pairs.ID1
UNION ALL
SELECT
CTE_Recursive.AnchorID
,CTE_Pairs.ID1
,CTE_Pairs.ID2
,CAST(
CTE_Recursive.IdPath +
CAST(CTE_Pairs.ID2 AS varchar(8000)) +
',' AS varchar(8000)) AS IdPath
,CTE_Recursive.Lvl + 1 AS Lvl
FROM
CTE_Pairs
INNER JOIN CTE_Recursive ON CTE_Recursive.ID2 = CTE_Pairs.ID1
WHERE
CTE_Recursive.IdPath NOT LIKE
CAST('%,' + CAST(CTE_Pairs.ID2 AS varchar(8000)) + ',%' AS varchar(8000))
)
,CTE_RecursionResult
AS
(
SELECT AnchorID, ID1, ID2
FROM CTE_Recursive
)
,CTE_CleanResult
AS
(
SELECT AnchorID, ID1 AS ID
FROM CTE_RecursionResult
UNION
SELECT AnchorID, ID2 AS ID
FROM CTE_RecursionResult
)
SELECT
DENSE_RANK() OVER (ORDER BY CA_Data.XML_Value) AS [NewID]
,CTE_Ids.ID AS [OldID]
,CA_Data.XML_Value AS GroupMembers
FROM
CTE_Ids
CROSS APPLY
(
SELECT CAST(CTE_CleanResult.ID AS nvarchar(max)) + ','
FROM CTE_CleanResult
WHERE CTE_CleanResult.AnchorID = CTE_Ids.ID
ORDER BY CTE_CleanResult.ID FOR XML PATH(''), TYPE
) AS CA_XML(XML_Value)
CROSS APPLY
(
SELECT CA_XML.XML_Value.value('.', 'NVARCHAR(MAX)')
) AS CA_Data(XML_Value)
ORDER BY [NewID], [OldID];
Ergebnis
+-------+---------+-------------------------+
| NewID | OldID | GroupMembers |
+-------+---------+-------------------------+
| 1 | 10 | 10,20,30,40, |
| 1 | 20 | 10,20,30,40, |
| 1 | 30 | 10,20,30,40, |
| 1 | 40 | 10,20,30,40, |
| 2 | 255714 | 255714,2540222,4457745, |
| 2 | 2540222 | 255714,2540222,4457745, |
| 2 | 4457745 | 255714,2540222,4457745, |
| 3 | 50 | 50,60,70, |
| 3 | 60 | 50,60,70, |
| 3 | 70 | 50,60,70, |
+-------+---------+-------------------------+
Wie es funktioniert
CTE_Ids
CTE_IdsGibt die Liste aller Bezeichner an, die sowohl in ID1als auch in den ID2Spalten angezeigt werden. Da sie in beliebiger Reihenfolge erscheinen können, haben wir UNIONbeide Spalten zusammen. UNIONEntfernt auch alle Duplikate.
+---------+
| ID |
+---------+
| 10 |
| 20 |
| 30 |
| 40 |
| 50 |
| 60 |
| 70 |
| 255714 |
| 2540222 |
| 4457745 |
+---------+
CTE_Pairs
CTE_Pairsgibt die Liste aller Kanten des Diagramms in beide Richtungen an. Wird wieder UNIONverwendet, um alle Duplikate zu entfernen.
+---------+---------+
| ID1 | ID2 |
+---------+---------+
| 10 | 20 |
| 10 | 30 |
| 10 | 40 |
| 20 | 10 |
| 30 | 10 |
| 40 | 10 |
| 50 | 60 |
| 50 | 70 |
| 60 | 50 |
| 70 | 50 |
| 255714 | 4457745 |
| 2540222 | 4457745 |
| 4457745 | 255714 |
| 4457745 | 2540222 |
+---------+---------+
CTE_Recursive
CTE_Recursiveist der Hauptteil der Abfrage, der das Diagramm ausgehend von jedem eindeutigen Bezeichner rekursiv durchläuft. Diese Startreihen werden vom ersten Teil von erzeugt UNION ALL. Der zweite Teil von UNION ALLrekursiv verbindet sich mit sich selbst und verknüpft sich ID2mit ID1. Da wir CTE_Pairsalle Kanten in beide Richtungen vorgefertigt haben , können wir immer nur ID2auf verlinken ID1und erhalten alle Pfade im Diagramm. Gleichzeitig wird die Abfrage erstellt IdPath- eine Zeichenfolge von durch Kommas getrennten Bezeichnern, die bisher durchlaufen wurden. Es wird im WHEREFilter verwendet:
CTE_Recursive.IdPath NOT LIKE
CAST('%,' + CAST(CTE_Pairs.ID2 AS varchar(8000)) + ',%' AS varchar(8000))
Sobald wir auf den Bezeichner stoßen, der zuvor im Pfad enthalten war, stoppt die Rekursion, da die Liste der verbundenen Knoten erschöpft ist.
AnchorIDist die Startkennung für die Rekursion und wird später zum Gruppieren von Ergebnissen verwendet.
Lvlwird nicht wirklich verwendet, ich habe es aufgenommen, um besser zu verstehen, was los ist.
+----------+---------+---------+--------------------------+-----+
| AnchorID | ID1 | ID2 | IdPath | Lvl |
+----------+---------+---------+--------------------------+-----+
| 10 | 10 | 20 | ,10,20, | 1 |
| 10 | 10 | 30 | ,10,30, | 1 |
| 10 | 10 | 40 | ,10,40, | 1 |
| 20 | 20 | 10 | ,20,10, | 1 |
| 30 | 30 | 10 | ,30,10, | 1 |
| 40 | 40 | 10 | ,40,10, | 1 |
| 50 | 50 | 60 | ,50,60, | 1 |
| 50 | 50 | 70 | ,50,70, | 1 |
| 60 | 60 | 50 | ,60,50, | 1 |
| 70 | 70 | 50 | ,70,50, | 1 |
| 255714 | 255714 | 4457745 | ,255714,4457745, | 1 |
| 2540222 | 2540222 | 4457745 | ,2540222,4457745, | 1 |
| 4457745 | 4457745 | 255714 | ,4457745,255714, | 1 |
| 4457745 | 4457745 | 2540222 | ,4457745,2540222, | 1 |
| 2540222 | 4457745 | 255714 | ,2540222,4457745,255714, | 2 |
| 255714 | 4457745 | 2540222 | ,255714,4457745,2540222, | 2 |
| 70 | 50 | 60 | ,70,50,60, | 2 |
| 60 | 50 | 70 | ,60,50,70, | 2 |
| 40 | 10 | 20 | ,40,10,20, | 2 |
| 40 | 10 | 30 | ,40,10,30, | 2 |
| 30 | 10 | 20 | ,30,10,20, | 2 |
| 30 | 10 | 40 | ,30,10,40, | 2 |
| 20 | 10 | 30 | ,20,10,30, | 2 |
| 20 | 10 | 40 | ,20,10,40, | 2 |
+----------+---------+---------+--------------------------+-----+
CTE_CleanResult
CTE_CleanResultLässt nur relevante Teile von CTE_Recursiveund führt beide wieder zusammen ID1und ID2verwendet UNION.
+----------+---------+
| AnchorID | ID |
+----------+---------+
| 10 | 10 |
| 10 | 20 |
| 10 | 30 |
| 10 | 40 |
| 20 | 10 |
| 20 | 20 |
| 20 | 30 |
| 20 | 40 |
| 2540222 | 255714 |
| 2540222 | 2540222 |
| 2540222 | 4457745 |
| 255714 | 255714 |
| 255714 | 2540222 |
| 255714 | 4457745 |
| 30 | 10 |
| 30 | 20 |
| 30 | 30 |
| 30 | 40 |
| 40 | 10 |
| 40 | 20 |
| 40 | 30 |
| 40 | 40 |
| 4457745 | 255714 |
| 4457745 | 2540222 |
| 4457745 | 4457745 |
| 50 | 50 |
| 50 | 60 |
| 50 | 70 |
| 60 | 50 |
| 60 | 60 |
| 60 | 70 |
| 70 | 50 |
| 70 | 60 |
| 70 | 70 |
+----------+---------+
Final SELECT
Jetzt müssen wir IDfür jeden eine Folge von durch Kommas getrennten Werten erstellen AnchorID.
CROSS APPLYmit FOR XMLmacht es.
DENSE_RANK()berechnet die NewIDZahlen für jeden AnchorID.
Diese Abfrage erstellt einen vollständigen Pfad für jeden Bezeichner, was nicht wirklich erforderlich ist.
Es ist möglich, die Gesamtlösung effizienter zu gestalten, indem diese Abfrage einmal für jede Gruppe / jeden Untergraphen in einer Schleife ausgeführt wird.
Wählen Sie einen einzelnen Startbezeichner aus ( WHERE ID = 10zu CTE_Idsund hinzufügen CTE_Pairs) und löschen Sie dann alle Bezeichner, die an diese Gruppe angehängt wurden, aus der Quelltabelle. Wiederholen, bis der große Tisch leer ist.