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 |