Union all, union e altri operatori insiemistici di SQL

In questa lezione studieremo come utilizzare i più importanti operatori insiemistici di SQL come UNION e UNION ALL tramite una serie di esempi e applicazioni pratiche. L’articolo è tratto dal mio ebook Come scrivere una query

Unire il risultato di due query con Union All

All’interno del Database che potete creare a partire dagli script che trovate sul mio repository Github, abbiamo due tabelle che contengono dei saldi monetari: la CarteCredito e la ContiCorrenti. Per ottenere il codice identificativo del prodotto e il relativo saldo, possiamo interrogare agevolmente le due tabelle separatamente.

SELECT  NumeroCarta,
             Saldo  
FROM   CarteCredito; 

e poi

SELECT NumeroConto,  
             Saldo  
FROM  ContiCorrenti;

In questo modo otterremo due risultati differenti con tutte le informazioni richieste. La domanda che ci poniamo ora è:

possiamo visualizzare entrambi i risultati in un’unica query e quindi in un unico output?

Vi anticipo già che la risposta è sì! Occorrerà anche in questo caso combinare i risultati,  questa volta però non “orizzontalmente” (come facciamo solitamente con la Join in cui i dati finali risultano affiancati) ma “verticalmente”, visualizzando le informazioni una dopo l’altra.

Per far ciò ci basterà inserire l’operatore UNION ALL tra le due query. Facciamo un primo tentativo scrivendo le due query combinate (ricordiamoci di mettere il punto e virgola solo dopo l’ultima query) e osserviamo il risultato ottenuto con MySQL.

SELECT  NumeroCarta,
             Saldo  
FROM  CarteCredito
    UNION ALL
SELECT  NumeroConto,  
              Saldo  
FROM  ContiCorrenti;

Ecco il risultato:

Esempio risultato UNION ALL

Direi che siamo sulla strada giusta! Occorrerà fare due interventi:

  • modificare il nome della prima colonna che deve fare riferimento ad un prodotto generico (senza specificare se si tratta di carta o di conto);
  • aggiungere una terza colonna che indichi la provenienza del dato

Riscriviamo allora la query in questo modo

SELECT   NumeroCarta AS NumeroProdotto,
               'Carta Credito' AS TipologiaProdotto, 
               Saldo  
FROM  CarteCredito
     UNION ALL
SELECT   NumeroConto AS NumeroProdotto,
               'Conti Corrente' AS TipologiaProdotto, 
               Saldo  
FROM  ContiCorrenti;

Abbiamo dunque rinominato l’output della prima colonna per entrambe le query. Tecnicamente sarebbe bastato farlo solo per quella più in alto, ma così penso che il codice sia molto più chiaro.

Per aggiungere la provenienza del dato non serve una funzione specifica, basterà aggiungere alle SELECT una colonna con un valore fisso, che imposteremo manualmente con un testo che fa riferimento al nome della tabella utilizzato nella FROM.

Ricordiamo che in generale una query senza l’ORDER BY  non ha un ordine deterministico. Anche se in pratica può sembrare che MySQL riporti all’inizio i dati della prima query e successivamente quelli della seconda, se voglio essere certo di ottenere questo risultato dovrò:

  1. creare una subquery o una CTE contenente l’UNION ALL
  2. escplicitare l’ORDER BY sulla query più esterna
  3. utilizzare l’istruzione CASE WHEN nella clausola ORDER BY

Scriverò dunque:

WITH CTE AS (
   SELECT  NumeroCarta AS NumeroProdotto,
                  'Carta Credito' AS TipologiaProdotto, 
                  Saldo  
   FROM   CarteCredito; 
          UNION ALL
   SELECT   NumeroConto AS NumeroProdotto,
                  Conti Corrente' AS TipologiaProdotto, 
                  Saldo  
   FROM   ContiCorrenti
    )
SELECT  *
FROM   CTE
ORDER BY
       CASE WHEN TipologiaProdotto = 'Carta Credito'   THEN 1 
                 ELSE  2 
      END;

Come al solito, facciamo attenzione alle performance quando utilizziamo una ORDER BY.

Differenze tra Union e Union All

Oltre alla UNION ALL che abbiamo appena visto, esiste anche l’operatore UNION che ha una piccola ma fondamentale differenza.

UNION rimuove dall’output finale tutte le righe duplicate, sia quelle che si sono generate dopo l’unione delle due query, sia quelle che potevano essere già presenti nelle query considerate singolarmente.

Facciamo dunque molta attenzione ad utilizzare UNION soltanto quando è necessaria l’operazione di rimozione dei duplicati.

L’operatore Intersect di SQL

La maggior parte dei database supporta l’operatore INTERSECT: con esso estraiamo le righe comuni alle due query, con l‘effetto aggiuntivo di rimuovere quelle duplicate. Ad esempio con la query

SELECT CodiceFiscale 
FROM  ClientiContiCorrenti     
       INTERSECT 
SELECT CodiceFiscale 
FROM  CarteCredito;  

estrarrò i valori del CodiceFiscale presenti almeno una volta in entrambe le query. Il risultato finale comunque non avrà duplicati. Anche se uno stesso valore fosse presente due volte in entrambe le tabelle, nel risultato finale della query con l’INTERSECT lo visualizzerei comunque una volta sola.

Su PostgreSQL e dalla versione 8.0.31 di MySQL è disponibile anche l’operatore INTERSECT ALL per gestire le ripetizioni. Se un codice fiscale fosse presente tre volte nella tabella ClientiContiCorrenti e due volte nella tabella CarteCredito allora la query seguente con l’INTERSECT ALL lo restituirebbe esattamente due volte.

SELECTCodiceFiscale 
FROM  ClientiContiCorrenti     
       INTERSECT ALL
SELECTCodiceFiscale 
FROM  CarteCredito;  

Risulta importante osservare che tutti gli operatori insiemistici trattano i NULL come gli altri valori, inserendo anch’essi nei confronti effettuati. Ad esempio la semplice query

SELECT NULL
INTERSECT
SELECT NULL;

restituisce effettivamente NULL.

Except e Minus

L’ultimo operatore che studiamo è l’EXCEPT disponibile su SQL Server e MySQL. Su Oracle useremo l’istruzione equivalente MINUS. A differenza delle query precedenti, l’operatore EXCEPT non è simmetrico, nel senso che scambiando l’ordine delle due query otterremo dei risultati differenti (oltre a un’ordine potenzialmente diverso).

Infatti tramite EXCEPT otteniamo le righe della prima query che non sono presenti nella seconda, rimuovendo preliminarmente anche i duplicati. Facciamo un esempio per assicurarci che ci sia chiaro il comportamento di EXCEPT anche nel caso più complesso:

  • supponiamo di effettuare l’EXCEPT tra due query che estraggono una sola colonna
  • uno specifico valore è ripetuto due volte nella prima query
  • lo stesso valore è ripetuto una sola volta nella seconda query

allora la query con l’EXCEPT non restituirà tale valore, in quanto esegue la rimozione dei duplicati già prima di eseguire il confronto. Se volessi ottenere una logica diversa potrei usare l’istruzione EXCEPT ALL disponibile su PostgreSQL e su MySQL (dalla versione 8.0.31)

Anche l’EXCEPT tratta il NULL come gli altri valori, inserendolo nei confronti effettuati.

Continua a imparare

Torna in alto
Torna su