PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Postgres :regrouper les comptes en une seule identité par adresse e-mail commune

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 |