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:

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ò:
- creare una subquery o una CTE contenente l’UNION ALL
- escplicitare l’ORDER BY sulla query più esterna
- 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
- Segui la lezione successiva sulle principali funzioni SQL
- Torna all’indice delle lezioni
- Visita la pagina del mio video corso online di SQL