Sammle alle ähnlichen Personen zu einer Gruppe

7

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.

Das Eingabe- und Ausgabebeispiel:

Eingang

Id1     Id2
---     ---
10      20
10      30
30      30
10      40

50      70
60      50
70      70 

Ausgabe

NewId   OldId
-----   -----
1       10
1       20
1       30
1       40

2       50
2       60
2       70
Dima Ha
quelle

Antworten:

3

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') is not null drop table #src;
create table #src (id1 int not null, id2 int)

if object_id('tempdb..#rez') is not null drop table #rez;
create table #rez (id int not null, chainid int not null);

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 = 1

while 1 = 1
begin
    insert #rez(id, chainid)
    select top 1 id1, @chainId
    from #src
    where
        Id1 NOT IN (select Id from #rez)
        and id2 NOT IN (select Id from #rez)

    if @@rowcount = 0 break

    while 1 = 1
    begin
        insert #rez(id, chainid)

        select id1, @chainid
        from #src
        where
            id2 in (select id from #rez where chainid = @chainId)
            and id1 not in (select id from #rez where chainid = @chainId)

        union

        select id2, @chainId
        from #src
        where
            id1 in (select id from #rez where chainid = @chainId)
            and id2 not in (select id from #rez where chainid = @chainId)

        if @@rowcount = 0 break
    end

    select @chainId = @chainId + 1
end

select [NewId] = chainid, OldID = id
from #rez 
order by 1, 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

    • Fahren Sie fort, bis Daten funktionieren

  • sortiertes Ergebnis zurückgeben

Sandr
quelle
Übrigens, mit beiden Datenbeispielen überprüft: Funktioniert wie erwartet.
Sandr
3

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.

Vladimir Baranov
quelle
1

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') IS NULL
BEGIN
CREATE TABLE #Temp (Id1 INT, Id2 INT)

INSERT INTO #Temp 
    VALUES(10,20),(10,30),(30,30),(10,40),(50,70),(60,50),(70,70)
END

 SELECT * FROM #Temp

;With Filtered AS (
    SELECT CASE WHEN Id1 <= Id2 THEN Id1 ELSE Id2 END AS Id1, CASE WHEN Id1 >= Id2 THEN Id1 ELSE Id2 END AS Id2 FROM #Temp
), 
FilteredUp AS(
    SELECT id1, Id2 AS Up FROM Filtered 
    UNION ALL
    SELECT 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
        LEFT JOIN FilteredUp fu ON f.Id1 = fu.Up
        WHERE fu.Id1 IS NULL
    UNION
    SELECT f.Id1, f.Id1 AS Id2 FROM Filtered f
        LEFT JOIN FilteredUp fu ON f.Id1 = fu.Up
        WHERE fu.Id1 IS NULL 
),
RowNumber AS (
SELECT ROW_NUMBER() OVER(ORDER BY Id1) AS NewId, Id1 FROM FilteredDown
    GROUP BY Id1
)
SELECT rn.NewId, fd.Id2 AS OldId FROM RowNumber rn
    JOIN FilteredDown fd ON rn.Id1 = fd.Id1 
Sam
quelle
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.
Sam