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 |