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_Ids
Gibt die Liste aller Bezeichner an, die sowohl in ID1
als auch in den ID2
Spalten angezeigt werden. Da sie in beliebiger Reihenfolge erscheinen können, haben wir UNION
beide Spalten zusammen. UNION
Entfernt auch alle Duplikate.
+---------+
| ID |
+---------+
| 10 |
| 20 |
| 30 |
| 40 |
| 50 |
| 60 |
| 70 |
| 255714 |
| 2540222 |
| 4457745 |
+---------+
CTE_Pairs
CTE_Pairs
gibt die Liste aller Kanten des Diagramms in beide Richtungen an. Wird wieder UNION
verwendet, 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_Recursive
ist 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 ALL
rekursiv verbindet sich mit sich selbst und verknüpft sich ID2
mit ID1
. Da wir CTE_Pairs
alle Kanten in beide Richtungen vorgefertigt haben , können wir immer nur ID2
auf verlinken ID1
und 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 WHERE
Filter 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.
AnchorID
ist die Startkennung für die Rekursion und wird später zum Gruppieren von Ergebnissen verwendet.
Lvl
wird 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_CleanResult
Lässt nur relevante Teile von CTE_Recursive
und führt beide wieder zusammen ID1
und ID2
verwendet 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 ID
für jeden eine Folge von durch Kommas getrennten Werten erstellen AnchorID
.
CROSS APPLY
mit FOR XML
macht es.
DENSE_RANK()
berechnet die NewID
Zahlen 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 = 10
zu CTE_Ids
und 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.