PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Postgres:aggrega gli account in un'unica identità tramite un indirizzo email comune

demo1:db<>violino , demo2:db<>violino

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

Spiegazione passo passo:

Per la spiegazione prenderò questo set di dati. Questo è un po' più complesso del tuo. Può illustrare meglio i miei passi. Alcuni problemi non si verificano nel tuo set più piccolo. Pensa ai caratteri come variabili per gli indirizzi email.

Tabella A:

| id | email |
|----|-------|
|  1 |     a |
|  1 |     b |
|  2 |     c |
|  5 |     e |

Tabella B

| id | email |
|----|-------|
|  3 |     a |
|  3 |     d |
|  4 |     e |
|  4 |     f |
|  3 |     b |

CTE combined :

UNISCITI di entrambe le tabelle sugli stessi indirizzi email per ottenere un punto di contatto. Gli ID degli stessi ID verranno concatenati in un 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 (scusate i nomi...):

L'obiettivo è ottenere tutti gli elementi esattamente in un solo array. In combined puoi vedere, ad esempio, attualmente ci sono più array con l'elemento 4 :{5,4} e {4} .

Prima ordinando le righe in base alla lunghezza dei loro ids array perché DISTINCT in seguito dovrebbe richiedere l'array più lungo (perché tenendo premuto il punto di contatto {5,4} invece di {4} ).

Quindi unnest gli ids array per ottenere una base per il filtraggio. Questo finisce in:

| 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 |

Dopo aver filtrato con 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 |

Siamo interessati solo agli ids colonna con i cluster di ID univoci generati. Quindi abbiamo bisogno di tutti loro solo una volta. Questo è il lavoro dell'ultimo DISTINCT . Quindi CTE clustered risultati in

| ids |
|-----|
|   2 |
| 1,3 |
| 5,4 |

Ora sappiamo quali ID sono combinati e dovrebbero condividere i loro dati. Ora ci uniamo agli ids raggruppati contro le tabelle di origine. Dal momento che lo abbiamo fatto nel CTE combined possiamo riutilizzare questa parte (questo è il motivo per cui è esternalizzata in un unico CTE:non abbiamo più bisogno di un altro join di entrambe le tabelle in questo passaggio). L'operatore JOIN <@ dice:JOIN se l'array "touch point" di combined è un sottogruppo del cluster id di clustered . Ciò si traduce 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 |

Ora siamo in grado di raggruppare gli indirizzi email utilizzando gli ID raggruppati (colonna più a destra).

array_agg aggrega le mail di una colonna, array_cat concatena gli array e-mail di entrambe le colonne in un unico grande array e-mail.

Poiché ci sono colonne in cui l'email è NULL possiamo filtrare questi valori prima di raggrupparli con FILTER (WHERE...) clausola.

Risultato finora:

| array_cat |
|-----------|
|         c |
| a,b,a,b,d |
|     e,e,f |

Ora raggruppiamo tutti gli indirizzi e-mail per un unico ID. Dobbiamo generare nuovi ID univoci. Questo è ciò che la funzione finestra row_number è per. Aggiunge semplicemente un conteggio delle righe alla tabella:

| array_cat | new_id |
|-----------|--------|
|         c |      1 |
| a,b,a,b,d |      2 |
|     e,e,f |      3 |

L'ultimo passaggio è unnest l'array per ottenere una riga per indirizzo email. Poiché nell'array ci sono ancora dei duplicati, possiamo eliminarli in questo passaggio con un DISTINCT anche:

| new_id | email |
|--------|-------|
|      1 |     c |
|      2 |     a |
|      2 |     b |
|      2 |     d |
|      3 |     e |
|      3 |     f |