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
Explicación paso a paso:
Para una explicación, tomaré este conjunto de datos. Esto es un poco más complejo que el tuyo. Puede ilustrar mejor mis pasos. Algunos problemas no ocurren en su conjunto más pequeño. Piense en los caracteres como variables para las direcciones de correo electrónico.
Tabla A:
| id | email |
|----|-------|
| 1 | a |
| 1 | b |
| 2 | c |
| 5 | e |
Tabla B
| id | email |
|----|-------|
| 3 | a |
| 3 | d |
| 4 | e |
| 4 | f |
| 3 | b |
CTE combined
:
ÚNASE de ambas mesas en las mismas direcciones de correo electrónico para obtener un punto de contacto. Los ID de los mismos ID se concatenarán en una 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
(perdón por los nombres...):
El objetivo es obtener todos los elementos exactamente en una sola matriz. En combined
puede ver, por ejemplo, actualmente hay más matrices con el elemento 4
:{5,4}
y {4}
.
Primero ordenando las filas por la longitud de sus ids
matrices porque DISTINCT
luego debería tomar la matriz más larga (porque mantener el punto de contacto {5,4}
en lugar de {4}
).
Entonces unnest
los ids
matrices para obtener una base para el filtrado. Esto termina en:
| 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 |
Después de filtrar 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 |
Solo estamos interesados en los ids
columna con los clústeres de ID únicos generados. Entonces los necesitamos todos solo una vez. Este es el trabajo del último DISTINCT
. Entonces CTE clustered
da como resultado
| ids |
|-----|
| 2 |
| 1,3 |
| 5,4 |
Ahora sabemos qué identificaciones se combinan y debemos compartir sus datos. Ahora unimos los ids
agrupados contra las tablas de origen. Ya que hemos hecho esto en el CTE combined
podemos reutilizar esta parte (esa es la razón por la que se subcontrata en un solo CTE por cierto:ya no necesitamos otra unión de ambas tablas en este paso). El operador JOIN <@
dice:ÚNETE si la matriz de "puntos de contacto" de combined
es un subgrupo del grupo de id de clustered
. Esto da como resultado:
| 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 |
Ahora podemos agrupar las direcciones de correo electrónico utilizando los identificadores agrupados (columna más a la derecha).
array_agg
agrega los correos de una columna, array_cat
concatena las matrices de correo electrónico de ambas columnas en una gran matriz de correo electrónico.
Dado que hay columnas donde el correo electrónico es NULL
podemos filtrar estos valores antes de agruparlos con FILTER (WHERE...)
cláusula.
Resultado hasta ahora:
| array_cat |
|-----------|
| c |
| a,b,a,b,d |
| e,e,f |
Ahora agrupamos todas las direcciones de correo electrónico para una sola identificación. Tenemos que generar nuevas identificaciones únicas. Eso es lo que hace la función de ventana
row_number
es para. Simplemente agrega un recuento de filas a la tabla:
| array_cat | new_id |
|-----------|--------|
| c | 1 |
| a,b,a,b,d | 2 |
| e,e,f | 3 |
El último paso es unnest
la matriz para obtener una fila por dirección de correo electrónico. Dado que en la matriz todavía hay algunos duplicados, podemos eliminarlos en este paso con un DISTINCT
también:
| new_id | email |
|--------|-------|
| 1 | c |
| 2 | a |
| 2 | b |
| 2 | d |
| 3 | e |
| 3 | f |