PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Postgres:Fassen Sie Konten nach gemeinsamer E-Mail-Adresse zu einer einzigen Identität zusammen

demo1:db<>fiddle , demo2:db<>fiddle

WITH combined AS (
    SELECT
        a.email as a_email,
        b.email as b_email,
        array_remove(ARRAY[a.id, b.id], NULL) as ids
    FROM 
        a
    FULL OUTER JOIN b ON (a.email = b.email)
), clustered AS (
    SELECT DISTINCT
        ids
    FROM (
        SELECT DISTINCT ON (unnest_ids) 
            *, 
            unnest(ids) as unnest_ids 
        FROM combined
        ORDER BY unnest_ids, array_length(ids, 1) DESC
    ) s
)
SELECT DISTINCT
    new_id, 
    unnest(array_cat) as email
FROM (
    SELECT
        array_cat(
            array_agg(a_email) FILTER (WHERE a_email IS NOT NULL), 
            array_agg(b_email) FILTER (WHERE b_email IS NOT NULL)
        ), 
        row_number() OVER () as new_id
    FROM combined co
    JOIN clustered cl
    ON co.ids <@ cl.ids
    GROUP BY cl.ids
) s

Schritt-für-Schritt-Erklärung:

Zur Erklärung nehme ich diesen Datensatz. Das ist etwas komplexer als bei dir. Es kann meine Schritte besser veranschaulichen. Einige Probleme treten in Ihrem kleineren Set nicht auf. Stellen Sie sich die Zeichen als Variablen für E-Mail-Adressen vor.

Tabelle A:

| id | email |
|----|-------|
|  1 |     a |
|  1 |     b |
|  2 |     c |
|  5 |     e |

Tabelle B

| id | email |
|----|-------|
|  3 |     a |
|  3 |     d |
|  4 |     e |
|  4 |     f |
|  3 |     b |

CTE combined :

VERBINDEN Sie beide Tabellen mit denselben E-Mail-Adressen, um einen Berührungspunkt zu erhalten. IDs gleicher IDs werden in einem Array verkettet:

|   a_email |   b_email | ids |
|-----------|-----------|-----|
|    (null) | [email protected] |   3 |
| [email protected] | [email protected] | 1,3 |
| [email protected] |    (null) |   1 |
| [email protected] |    (null) |   2 |
|    (null) | [email protected] |   4 |

CTE clustered (Entschuldigung für die Namen...):

Ziel ist es, alle Elemente genau in nur einem Array zu bekommen. In combined Sie können sehen, dass es z. B. derzeit mehr Arrays mit dem Element 4 gibt :{5,4} und {4} .

Sortieren Sie zuerst die Zeilen nach der Länge ihrer ids Arrays, da der DISTINCT später das längste Array nehmen sollte (weil das Halten des Berührungspunkts {5,4} anstelle von {4} ).

Dann unnest die ids Arrays, um eine Grundlage für die Filterung zu erhalten. Diese endet auf:

| a_email | b_email | ids | unnest_ids |
|---------|---------|-----|------------|
|       b |       b | 1,3 |          1 |
|       a |       a | 1,3 |          1 |
|       c |  (null) |   2 |          2 |
|       b |       b | 1,3 |          3 |
|       a |       a | 1,3 |          3 |
|  (null) |       d |   3 |          3 |
|       e |       e | 5,4 |          4 |
|  (null) |       f |   4 |          4 |
|       e |       e | 5,4 |          5 |

Nach dem Filtern mit DISTINCT ON

| a_email | b_email | ids | unnest_ids |
|---------|---------|-----|------------|
|       b |       b | 1,3 |          1 |
|       c |  (null) |   2 |          2 |
|       b |       b | 1,3 |          3 |
|       e |       e | 5,4 |          4 |
|       e |       e | 5,4 |          5 |

Uns interessieren nur die ids Spalte mit den generierten eindeutigen ID-Clustern. Wir brauchen sie also alle nur einmal. Dies ist die Aufgabe des letzten DISTINCT . Also CTE clustered Ergebnisse in

| ids |
|-----|
|   2 |
| 1,3 |
| 5,4 |

Jetzt wissen wir, welche IDs kombiniert werden und ihre Daten teilen sollten. Jetzt verbinden wir die geclusterten ids gegen die Ursprungstabellen. Da wir dies im CTE combined gemacht haben wir können diesen Teil wiederverwenden (deshalb wird er übrigens in einen einzigen CTE ausgelagert:Wir brauchen in diesem Schritt keinen weiteren Join der beiden Tabellen mehr). Der JOIN-Operator <@ sagt:JOIN wenn das "touch point" Array von combined ist eine Untergruppe des ID-Clusters von clustered . Dies ergibt:

| a_email | b_email | ids | ids |
|---------|---------|-----|-----|
|       c |  (null) |   2 |   2 |
|       a |       a | 1,3 | 1,3 |
|       b |       b | 1,3 | 1,3 |
|  (null) |       d |   3 | 1,3 |
|       e |       e | 5,4 | 5,4 |
|  (null) |       f |   4 | 5,4 |

Jetzt können wir die E-Mail-Adressen gruppieren, indem wir die geclusterten IDs (Spalte ganz rechts) verwenden.

array_agg aggregiert die Mails einer Spalte, array_cat verkettet die E-Mail-Arrays beider Spalten zu einem großen E-Mail-Array.

Da es Spalten gibt, in denen email NULL ist wir können diese Werte vor dem Clustering mit dem FILTER (WHERE...) herausfiltern Klausel.

Bisheriges Ergebnis:

| array_cat |
|-----------|
|         c |
| a,b,a,b,d |
|     e,e,f |

Jetzt gruppieren wir alle E-Mail-Adressen für eine einzige ID. Wir müssen neue eindeutige IDs generieren. Das ist die Fensterfunktion row_number ist für. Es fügt der Tabelle einfach eine Zeilenanzahl hinzu:

| array_cat | new_id |
|-----------|--------|
|         c |      1 |
| a,b,a,b,d |      2 |
|     e,e,f |      3 |

Der letzte Schritt ist das unnest das Array, um eine Zeile pro E-Mail-Adresse zu erhalten. Da sich im Array noch einige Duplikate befinden, können wir diese in diesem Schritt mit einem DISTINCT eliminieren auch:

| new_id | email |
|--------|-------|
|      1 |     c |
|      2 |     a |
|      2 |     b |
|      2 |     d |
|      3 |     e |
|      3 |     f |