sql >> Base de Datos >  >> RDS >> PostgreSQL

Postgres:agregue cuentas en una sola identidad por dirección de correo electrónico común

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 |