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
Trin for trin forklaring:
Til forklaring tager jeg dette datasæt. Det her er lidt mere komplekst end dit. Det kan illustrere mine trin bedre. Nogle problemer opstår ikke i dit mindre sæt. Tænk på tegnene som variabler for e-mail-adresser.
Tabel A:
| id | email |
|----|-------|
| 1 | a |
| 1 | b |
| 2 | c |
| 5 | e |
Tabel B
| id | email |
|----|-------|
| 3 | a |
| 3 | d |
| 4 | e |
| 4 | f |
| 3 | b |
CTE combined
:
JOIN af begge borde på samme e-mailadresser for at få et kontaktpunkt. ID'er af samme Id'er vil blive sammenkædet i ét array:
| 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
(undskyld navnene...):
Målet er at få alle elementer nøjagtigt i kun ét array. I combined
du kan se, for eksempel i øjeblikket er der flere arrays med elementet 4
:{5,4}
og {4}
.
Først sorteres rækkerne efter længden af deres ids
arrays, fordi DISTINCT
senere skulle tage det længste array (fordi man holder berøringspunktet {5,4}
i stedet for {4}
).
Derefter unnest
ids
arrays for at få et grundlag for filtrering. Dette ender med:
| 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 |
Efter filtrering med 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 |
Vi er kun interesserede i ids
kolonne med de genererede unikke id-klynger. Så vi har kun brug for dem alle én gang. Dette er opgaven for den sidste DISTINCT
. Så CTE clustered
resulterer i
| ids |
|-----|
| 2 |
| 1,3 |
| 5,4 |
Nu ved vi, hvilke id'er der er kombineret og bør dele deres data. Nu slutter vi os til de klyngede ids
mod oprindelsestabellerne. Da vi har gjort dette i CTE combined
vi kan genbruge denne del (det er i øvrigt grunden til, at den er outsourcet til en enkelt CTE:Vi har ikke længere brug for endnu en sammenføjning af begge tabeller i dette trin). JOIN-operatøren <@
siger:JOIN hvis "touch point"-arrayet af combined
er en undergruppe af id-klyngen af clustered
. Dette giver:
| 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 |
Nu er vi i stand til at gruppere e-mail-adresserne ved at bruge de klyngede id'er (kolonne længst til højre).
array_agg
samler mails fra én kolonne, array_cat
sammenkæder e-mail-arrays af begge kolonner til en stor e-mail-array.
Da der er kolonner, hvor e-mail er NULL
vi kan filtrere disse værdier fra før klyngedannelse med FILTER (WHERE...)
klausul.
Resultat indtil videre:
| array_cat |
|-----------|
| c |
| a,b,a,b,d |
| e,e,f |
Nu grupperer vi alle e-mailadresser til ét enkelt id. Vi skal generere nye unikke id'er. Det er hvad vinduefunktionen
row_number
er for. Det tilføjer simpelthen et rækkeantal til tabellen:
| array_cat | new_id |
|-----------|--------|
| c | 1 |
| a,b,a,b,d | 2 |
| e,e,f | 3 |
Sidste trin er at unnest
arrayet for at få en række pr. e-mailadresse. Da der stadig er nogle dubletter i arrayet, kan vi fjerne dem i dette trin med en DISTINCT
også:
| new_id | email |
|--------|-------|
| 1 | c |
| 2 | a |
| 2 | b |
| 2 | d |
| 3 | e |
| 3 | f |