PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Postgres:agregar contas em uma única identidade por endereço de e-mail comum


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

Explicação passo a passo:

Para explicação, pegarei este conjunto de dados. Isso é um pouco mais complexo que o seu. Pode ilustrar melhor meus passos. Alguns problemas não ocorrem em seu conjunto menor. Pense nos caracteres como variáveis ​​para endereços de e-mail.

Tabela A:
| id | email |
|----|-------|
|  1 |     a |
|  1 |     b |
|  2 |     c |
|  5 |     e |

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

CTE combined :

JOIN de ambas as tabelas nos mesmos endereços de e-mail para obter um ponto de contato. Os IDs dos mesmos IDs serão concatenados em uma matriz:
|   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 (desculpem os nomes...):

O objetivo é obter todos os elementos exatamente em apenas um array. Em combined você pode ver, por exemplo, atualmente existem mais arrays com o elemento 4 :{5,4} e {4} .

Primeiro ordenando as linhas pelo comprimento de seus ids matrizes porque o DISTINCT mais tarde deve ter a matriz mais longa (porque segurando o ponto de toque {5,4} em vez de {4} ).

Em seguida, unnest os ids arrays para obter uma base para filtragem. Isso termina em:
| 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 |

Após filtrar com 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 |

Estamos interessados ​​apenas nos ids coluna com os clusters de ID exclusivos gerados. Então, precisamos de todos eles apenas uma vez. Este é o trabalho do último DISTINCT . Então CTE clustered resulta em
| ids |
|-----|
|   2 |
| 1,3 |
| 5,4 |

Agora sabemos quais IDs são combinados e devemos compartilhar seus dados. Agora juntamos os ids agrupados contra as tabelas de origem. Como fizemos isso no CTE combined podemos reutilizar essa parte (essa é a razão pela qual ela é terceirizada em um único CTE:não precisamos mais de outra junção de ambas as tabelas nesta etapa). O operador JOIN <@ diz:JOIN se o array "touch point" de combined é um subgrupo do cluster id de clustered . Isso rende em:
| 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 |

Agora podemos agrupar os endereços de e-mail usando os IDs agrupados (coluna mais à direita).

array_agg agrega os e-mails de uma coluna, array_cat concatena as matrizes de email de ambas as colunas em uma grande matriz de email.

Como existem colunas em que o email é NULL podemos filtrar esses valores antes de agrupar com o FILTER (WHERE...) cláusula.

Resultado até agora:
| array_cat |
|-----------|
|         c |
| a,b,a,b,d |
|     e,e,f |

Agora agrupamos todos os endereços de e-mail para um único id. Temos que gerar novos ids únicos. É isso que a função de janela row_number é para. Ele simplesmente adiciona uma contagem de linhas à tabela:
| array_cat | new_id |
|-----------|--------|
|         c |      1 |
| a,b,a,b,d |      2 |
|     e,e,f |      3 |

A última etapa é unnest a matriz para obter uma linha por endereço de e-mail. Como no array ainda existem algumas duplicatas, podemos eliminá-las nesta etapa com um DISTINCT também:
| new_id | email |
|--------|-------|
|      1 |     c |
|      2 |     a |
|      2 |     b |
|      2 |     d |
|      3 |     e |
|      3 |     f |