sql >> Databasteknik >  >> RDS >> PostgreSQL

Postgres:Aggregera konton till en enda identitet med gemensam e-postadress

demo1:db<>fiol , demo2:db<>fiol

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

Steg för steg förklaring:

Som förklaring tar jag denna datauppsättning. Det här är lite mer komplext än ditt. Det kan illustrera mina steg bättre. Vissa problem uppstår inte i din mindre uppsättning. Tänk på tecknen som variabler för e-postadresser.

Tabell A:

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

Tabell B

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

CTE combined :

GÅ MED i båda tabellerna på samma e-postadresser för att få en kontaktpunkt. ID:n för samma Id:n kommer att sammanfogas i en 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 (ursäkta namnen...):

Målet är att få alla element exakt i endast en array. I combined Du kan till exempel se att det för närvarande finns fler arrayer med elementet 4 :{5,4} och {4} .

Ordna först raderna efter längden på deras ids matriser eftersom DISTINCT senare bör ta den längsta arrayen (eftersom håll beröringspunkten {5,4} istället för {4} ).

Sedan unnest ids arrayer för att få en grund för filtrering. Detta slutar på:

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

Efter filtrering med 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 |

Vi är bara intresserade av ids kolumn med de genererade unika id-klustren. Så vi behöver alla bara en gång. Detta är jobbet för den senaste DISTINCT . Så CTE clustered resulterar i

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

Nu vet vi vilka id som är kombinerade och bör dela deras data. Nu går vi med i klustrade ids mot ursprungstabellerna. Eftersom vi har gjort detta i CTE combined vi kan återanvända den här delen (det är anledningen till att den är outsourcad till en enda CTE förresten:vi behöver inte längre ansluta till båda tabellerna i det här steget). JOIN-operatören <@ säger:JOIN om "touch point"-arrayen av combined är en undergrupp av id-klustret för clustered . Detta ger:

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

Nu kan vi gruppera e-postadresserna genom att använda de klustrade ID:n (kolumnen längst till höger).

array_agg samlar e-postmeddelanden från en kolumn, array_cat sammanfogar e-postmatriserna för båda kolumnerna till en stor e-postmatris.

Eftersom det finns kolumner där e-post är NULL vi kan filtrera bort dessa värden innan vi klusterar med FILTER (WHERE...) klausul.

Resultat hittills:

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

Nu grupperar vi alla e-postadresser för ett enda id. Vi måste skapa nya unika ID. Det är vad fönsterfunktionen row_number är för. Det lägger helt enkelt till ett radantal i tabellen:

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

Sista steget är att unnest arrayen för att få en rad per e-postadress. Eftersom det fortfarande finns några dubbletter i arrayen kan vi eliminera dem i detta steg med en DISTINCT likaså:

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


  1. Laddar ner MySQL-dump från kommandoraden

  2. Konvertera SQL till SQL alkemi

  3. psql returkod om noll rader hittas

  4. Konvertera WM_CONCAT till Listagg