demo1:db<>violon , demo2:db<>violon
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
Explication étape par étape :
Pour l'explication, je vais prendre cet ensemble de données. C'est un peu plus complexe que le vôtre. Cela peut mieux illustrer mes démarches. Certains problèmes ne se produisent pas dans votre ensemble plus petit. Considérez les caractères comme des variables pour les adresses e-mail.
Tableau A :
| id | email |
|----|-------|
| 1 | a |
| 1 | b |
| 2 | c |
| 5 | e |
Tableau B
| id | email |
|----|-------|
| 3 | a |
| 3 | d |
| 4 | e |
| 4 | f |
| 3 | b |
CTE combined
:
JOIN des deux tables sur les mêmes adresses e-mail pour obtenir un point de contact. Les identifiants des mêmes identifiants seront concaténés dans un tableau :
| 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
(désolé pour les noms...) :
Le but est d'obtenir tous les éléments exactement dans un seul tableau. En combined
vous pouvez voir, par exemple actuellement il y a plus de tableaux avec l'élément 4
:{5,4}
et {4}
.
Trier d'abord les lignes par la longueur de leurs ids
tableaux parce que le DISTINCT
plus tard devrait prendre le tableau le plus long (parce que maintenir le point de contact {5,4}
au lieu de {4}
).
Puis unnest
les ids
tableaux pour obtenir une base de filtrage. Cela se termine par :
| 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 |
Après filtrage avec 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 |
Seuls les ids
nous intéressent colonne avec les clusters d'ID uniques générés. Nous n'avons donc besoin de tous qu'une seule fois. C'est le travail du dernier DISTINCT
. Donc CTE clustered
résultats en
| ids |
|-----|
| 2 |
| 1,3 |
| 5,4 |
Nous savons maintenant quels identifiants sont combinés et nous devons partager leurs données. Maintenant, nous joignons les ids
en cluster contre les tables d'origine. Puisque nous l'avons fait dans le CTE combined
nous pouvons réutiliser cette partie (c'est d'ailleurs la raison pour laquelle elle est externalisée dans un seul CTE :nous n'avons plus besoin d'une autre jointure des deux tables à cette étape). L'opérateur JOIN <@
dit :JOIN si le tableau "point de contact" de combined
est un sous-groupe du cluster d'id de clustered
. Cela donne :
| 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 |
Nous pouvons maintenant regrouper les adresses e-mail en utilisant les identifiants groupés (colonne la plus à droite).
array_agg
agrège les mails d'une colonne, array_cat
concatène les tableaux d'e-mails des deux colonnes en un seul grand tableau d'e-mails.
Puisqu'il y a des colonnes où l'email est NULL
nous pouvons filtrer ces valeurs avant de les regrouper avec le FILTER (WHERE...)
clause.
Résultat jusqu'à présent :
| array_cat |
|-----------|
| c |
| a,b,a,b,d |
| e,e,f |
Maintenant, nous regroupons toutes les adresses e-mail pour un seul identifiant. Nous devons générer de nouveaux identifiants uniques. C'est ce que la fonction window
row_number
est pour. Il ajoute simplement un nombre de lignes au tableau :
| array_cat | new_id |
|-----------|--------|
| c | 1 |
| a,b,a,b,d | 2 |
| e,e,f | 3 |
La dernière étape consiste à unnest
le tableau pour obtenir une ligne par adresse e-mail. Puisque dans le tableau il y a encore des doublons, nous pouvons les éliminer dans cette étape avec un DISTINCT
aussi :
| new_id | email |
|--------|-------|
| 1 | c |
| 2 | a |
| 2 | b |
| 2 | d |
| 3 | e |
| 3 | f |