Ich habe eine Person mit mehreren Ausweisen. Einige davon in Spalte Id1 und einige in Id2. Ich möchte die gleichen Personen-IDs für eine Gruppe sammeln.
Wenn id1 = 10, befindet sich in derselben Zeile mit id2 = 20. Es bedeutet also, dass die Person mit id1 = 10 dieselbe Person wie id2 = 20 ist.
Es ist besser, solche Berechnungen iterativ durchzuführen.
Das " Problem " einer solchen Lösung besteht darin, dass bei einer großen Quelltabelle die Ausgabetabelle alle Zeilen enthält. Tatsächlich ist dies aber auch der Vorteil: Im Falle einer großen Quelltabelle arbeitet ein solcher Code viel schneller als Rekursionen.
if object_id('tempdb..#src')isnotnulldroptable#src;createtable#src (id1 int notnull, id2 int)if object_id('tempdb..#rez')isnotnulldroptable#rez;createtable#rez (id int notnull, chainid int notnull);declare@chainId int;insert#src
values(10,20),(10,30),(30,30),(10,40),(50,70),(60,50),(70,70)--values (4457745,255714),(4457745,2540222),(2540222,4457745),(255714,4457745)set@chainId =1while1=1begininsert#rez(id, chainid)selecttop1 id1,@chainId
from#src
where
Id1 NOTIN(select Id from#rez)and id2 NOTIN(select Id from#rez)if@@rowcount=0breakwhile1=1begininsert#rez(id, chainid)select id1,@chainid
from#src
where
id2 in(select id from#rez where chainid =@chainId)and id1 notin(select id from#rez where chainid =@chainId)unionselect id2,@chainId
from#src
where
id1 in(select id from#rez where chainid =@chainId)and id2 notin(select id from#rez where chainid =@chainId)if@@rowcount=0breakendselect@chainId =@chainId +1endselect[NewId]= chainid, OldID = id
from#rez
orderby1,2;
In einigen Details lautet die "Logik" des Skripts wie folgt:
neuen ID (Ketten-ID) Anfangswert setzen (Bsp. 1)
Holen Sie sich den ersten Eintrag, der noch nicht gefunden wurde (externer Zyklus)
Finden und Speichern aller damit verbundenen Elemente (in beiden Spalten) (interner Zyklus)
Erhöhen Sie die neue ID
Suchen Sie den nächsten Eintrag, der nicht in der Ergebnistabelle enthalten ist
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 NOTNULL, ID2 int NOTNULL);INSERTINTO@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
UNIONSELECT ID2 AS ID
FROM@T
),CTE_Pairs
AS(SELECT ID1, ID2
FROM@T
WHERE ID1 <> ID2
UNIONSELECT 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
,1AS Lvl
FROM
CTE_Pairs
INNERJOIN CTE_Ids ON CTE_Ids.ID = CTE_Pairs.ID1
UNIONALLSELECT
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 +1AS Lvl
FROM
CTE_Pairs
INNERJOIN CTE_Recursive ON CTE_Recursive.ID2 = CTE_Pairs.ID1
WHERE
CTE_Recursive.IdPath NOTLIKE
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
UNIONSELECT AnchorID, ID2 AS ID
FROM CTE_RecursionResult
)SELECT
DENSE_RANK()OVER(ORDERBY CA_Data.XML_Value)AS[NewID],CTE_Ids.ID AS[OldID],CA_Data.XML_Value AS GroupMembers
FROM
CTE_Ids
CROSSAPPLY(SELECT CAST(CTE_CleanResult.ID AS nvarchar(max))+','FROM CTE_CleanResult
WHERE CTE_CleanResult.AnchorID = CTE_Ids.ID
ORDERBY CTE_CleanResult.ID FORXML PATH(''), TYPE
)AS CA_XML(XML_Value)CROSSAPPLY(SELECT CA_XML.XML_Value.value('.','NVARCHAR(MAX)'))AS CA_Data(XML_Value)ORDERBY[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.
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 NOTLIKE
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.
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.
Ich bin mir sicher, dass es effizientere Wege gibt, aber vielleicht ist dies ein Anfang.
Ich habe CTEs verwendet, um zuerst die IDs umzuschalten, sodass die linke Spalte kleiner als die rechte Spalte war (gefiltert).
Dann habe ich eine Tabelle erstellt, die alle niedrigeren Werte (aus der linken Spalte) zusammen mit einem höheren Wert (aus der rechten Spalte) (FilteredUp) enthielt.
FilteredDown entfernt alle Fälle, in denen ein niedrigeres Beispiel vorhanden ist (z. B. 30,30 als eine Zeile, dies wird jedoch entfernt, da eine Zeile 10,20 vorhanden ist) und stellt außerdem sicher, dass alle linken Werte in der rechten Spalte vorhanden sind (z 10,10 und 50,50).
Schließlich habe ich die neuen IDs mit ROW_NUMBER () erstellt:
IF OBJECT_ID('tempdb..#Temp')ISNULLBEGINCREATETABLE#Temp (Id1 INT, Id2 INT)INSERTINTO#Temp
VALUES(10,20),(10,30),(30,30),(10,40),(50,70),(60,50),(70,70)ENDSELECT*FROM#Temp
;With Filtered AS(SELECTCASEWHEN Id1 <= Id2 THEN Id1 ELSE Id2 ENDAS Id1,CASEWHEN Id1 >= Id2 THEN Id1 ELSE Id2 ENDAS Id2 FROM#Temp
),
FilteredUp AS(SELECT id1, Id2 AS Up FROM Filtered
UNIONALLSELECT f.id1, fu.Id2 FROM FilteredUp f
JOIN Filtered fu ON f.Up = fu.Id1 AND f.Up < fu.Id2
),
FilteredDown AS(SELECT f.Id1, f.Id2 FROM Filtered f
LEFTJOIN FilteredUp fu ON f.Id1 = fu.Up
WHERE fu.Id1 ISNULLUNIONSELECT f.Id1, f.Id1 AS Id2 FROM Filtered f
LEFTJOIN FilteredUp fu ON f.Id1 = fu.Up
WHERE fu.Id1 ISNULL),
RowNumber AS(SELECT ROW_NUMBER()OVER(ORDERBY Id1)AS NewId, Id1 FROM FilteredDown
GROUPBY Id1
)SELECT rn.NewId, fd.Id2 AS OldId FROM RowNumber rn
JOIN FilteredDown fd ON rn.Id1 = fd.Id1
Es gibt ein Problem für die Eingabe: (4457745,255714), (4457745,2540222), (2540222,4457745), (255714,4457745). Es sollte für alle IDs die gleiche NewId
Dima Ha
Aber es ist sehr seltsam, wenn ich 4457745 auf 10 (oder eine kleine Zahl) ändere, wird es funktionieren. wissen Sie, warum?
Dima Ha
Sam? Können Sie mir sagen, warum dieser Einsatz nicht funktioniert? :( 4457745,255714), (4457745,2540222), (2540222,4457745), (255714, 4457745)
Dima Ha
Verwenden Sie eine Bigint
McNets
Sandrs Antwort ist vollständiger (und viel besser), ich habe die Möglichkeit einer Kette nicht in Betracht gezogen, wie in Ihrem Beispiel. Da 255714 und 2540222 beide kleiner als 4457745 sind und nichts sie direkt miteinander verbindet, werden sie von mir vermisst. Während Sandrs Lösung gegen diese Kette prüft.
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
Abfrage
Ergebnis
Wie es funktioniert
CTE_Ids
CTE_Ids
Gibt die Liste aller Bezeichner an, die sowohl inID1
als auch in denID2
Spalten angezeigt werden. Da sie in beliebiger Reihenfolge erscheinen können, haben wirUNION
beide Spalten zusammen.UNION
Entfernt auch alle Duplikate.CTE_Pairs
CTE_Pairs
gibt die Liste aller Kanten des Diagramms in beide Richtungen an. Wird wiederUNION
verwendet, um alle Duplikate zu entfernen.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 erzeugtUNION ALL
. Der zweite Teil vonUNION ALL
rekursiv verbindet sich mit sich selbst und verknüpft sichID2
mitID1
. Da wirCTE_Pairs
alle Kanten in beide Richtungen vorgefertigt haben , können wir immer nurID2
auf verlinkenID1
und erhalten alle Pfade im Diagramm. Gleichzeitig wird die Abfrage erstelltIdPath
- eine Zeichenfolge von durch Kommas getrennten Bezeichnern, die bisher durchlaufen wurden. Es wird imWHERE
Filter verwendet: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.CTE_CleanResult
CTE_CleanResult
Lässt nur relevante Teile vonCTE_Recursive
und führt beide wieder zusammenID1
undID2
verwendetUNION
.Final SELECT
Jetzt müssen wir
ID
für jeden eine Folge von durch Kommas getrennten Werten erstellenAnchorID
.CROSS APPLY
mitFOR XML
macht es.DENSE_RANK()
berechnet dieNewID
Zahlen für jedenAnchorID
.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
zuCTE_Ids
und hinzufügenCTE_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.quelle
Ich bin mir sicher, dass es effizientere Wege gibt, aber vielleicht ist dies ein Anfang.
Ich habe CTEs verwendet, um zuerst die IDs umzuschalten, sodass die linke Spalte kleiner als die rechte Spalte war (gefiltert).
Dann habe ich eine Tabelle erstellt, die alle niedrigeren Werte (aus der linken Spalte) zusammen mit einem höheren Wert (aus der rechten Spalte) (FilteredUp) enthielt.
FilteredDown entfernt alle Fälle, in denen ein niedrigeres Beispiel vorhanden ist (z. B. 30,30 als eine Zeile, dies wird jedoch entfernt, da eine Zeile 10,20 vorhanden ist) und stellt außerdem sicher, dass alle linken Werte in der rechten Spalte vorhanden sind (z 10,10 und 50,50).
Schließlich habe ich die neuen IDs mit ROW_NUMBER () erstellt:
quelle