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
Stap voor stap uitleg:
Voor uitleg neem ik deze dataset. Dit is een beetje ingewikkelder dan de jouwe. Het kan mijn stappen beter illustreren. Sommige problemen doen zich niet voor in uw kleinere set. Beschouw de tekens als variabelen voor e-mailadressen.
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 van beide tabellen op dezelfde e-mailadressen om een contactpunt te krijgen. ID's van dezelfde ID's worden samengevoegd in één 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
(sorry voor de namen...):
Het doel is om alle elementen precies in één array te krijgen. In combined
je kunt zien dat er momenteel bijvoorbeeld meer arrays zijn met het element 4
:{5,4}
en {4}
.
Sorteer eerst de rijen op de lengte van hun ids
arrays omdat de DISTINCT
later zou de langste array moeten duren (omdat het aanraken van het aanraakpunt {5,4}
in plaats van {4}
).
Dan unnest
de ids
arrays om een basis voor het filteren te krijgen. Dit eindigt op:
| 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 |
Na filteren met 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 |
We zijn alleen geïnteresseerd in de ids
kolom met de gegenereerde unieke id-clusters. We hebben ze dus allemaal maar één keer nodig. Dit is de taak van de laatste DISTINCT
. Dus CTE clustered
resulteert in
| ids |
|-----|
| 2 |
| 1,3 |
| 5,4 |
Nu weten we welke ID's worden gecombineerd en hun gegevens moeten delen. Nu voegen we ons bij de geclusterde ids
tegen de oorsprongstabellen. Aangezien we dit hebben gedaan in de CTE combined
we kunnen dit onderdeel hergebruiken (dat is trouwens de reden waarom het is uitbesteed aan een enkele CTE:we hebben in deze stap geen nieuwe samenvoeging van beide tabellen meer nodig). De JOIN-operator <@
zegt:JOIN als de "touch point" array van combined
is een subgroep van het id-cluster van clustered
. Dit levert op in:
| 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 kunnen we de e-mailadressen groeperen met behulp van de geclusterde ID's (meest rechtse kolom).
array_agg
verzamelt de e-mails van één kolom, array_cat
voegt de e-mailarrays van beide kolommen samen tot één grote e-mailarray.
Aangezien er kolommen zijn waar e-mail NULL
is we kunnen deze waarden eruit filteren voordat ze worden geclusterd met de FILTER (WHERE...)
clausule.
Resultaat tot nu toe:
| array_cat |
|-----------|
| c |
| a,b,a,b,d |
| e,e,f |
Nu groeperen we alle e-mailadressen voor één enkele id. We moeten nieuwe unieke id's genereren. Dat is wat de vensterfunctie
row_number
is voor. Het voegt gewoon een rijtelling toe aan de tabel:
| array_cat | new_id |
|-----------|--------|
| c | 1 |
| a,b,a,b,d | 2 |
| e,e,f | 3 |
De laatste stap is het unnest
de array om een rij per e-mailadres te krijgen. Aangezien er in de array nog enkele duplicaten zijn, kunnen we deze in deze stap elimineren met een DISTINCT
ook:
| new_id | email |
|--------|-------|
| 1 | c |
| 2 | a |
| 2 | b |
| 2 | d |
| 3 | e |
| 3 | f |