L’operatore NOT IN è utilizzato spesso all’interno della clausola WHERE di una query SQL per estrarre le righe di una tabella il cui valore di una colonna non è presente all’interno di un dato elenco. Tuttavia occorre fare molta attenzione nell’utilizzo della NOT IN poiché in presenza di Null potrebbe portare a risultati inaspettati.
Sintassi della NOT IN e gestione dei NULL
Consideriamo una semplice query che utilizzi l’operatore NOT IN
SELECT *
FROM Fatture
WHERE IdCliente NOT IN (1,2,3);
Essa estrae le fatture dei clienti che non sono nell’elenco 1, 2, 3. Inoltre non verranno visualizzate le righe dove IdCliente è null. La query precedente infatti è equivalente a
SELECT *
FROM Fatture
WHERE IdCliente <> 1
AND IdCliente <> 2
AND IdCliente <> 3;
e sui Database relazionali il confronto “null <> 1” non è né vero né falso; in particolare, non essendo vero, le righe relative non saranno visualizzate nell’output della query. Analogamente possiamo osservare che la query seguente restituirà sempre zero righe
SELECT *
FROM Fatture
WHERE IdCliente NOT IN (1, 2, 3, NULL);
perché è equivalente a una query che ha nella WHERE, in AND, la condizione
IdCliente <> NULL
che non sarà mai verificata, per nessun possibile contenuto di IdCliente.
NOT IN e subquery
L’elenco dei valori da utilizzare nella NOT IN può essere anche calcolato dinamicamente tramite una Subquery. In questo caso però bisogna porre ancora più attenzione: come visto nel paragrafo precedente, la presenza di un NULL all’interno dei risultati della sottoquery porterebbe immediatamente la query complessiva a restituire in output zero righe.
Per evitare questa possibilità, possiamo scrivere del codice equivalente che utilizzi una LEFT JOIN o una NOT EXISTS. Anche in questo caso però dobbiamo fare attenzione ai Null. Consideriamo ad esempio questa query pensata per estrarre i clienti senza fatture associate:
SELECT *
FROM Clienti
WHERE IdCliente NOT IN (SELECT IdCliente
FROM Fatture);
Nel caso in cui ci sia anche una sola riga nella tabella Fatture dove IdCliente è NULL, allora automaticamente la query complessiva restituirà zero righe, a prescindere dal contenuto della tabella Clienti. Per evitare questo comportamento possiamo riscrivere la query così:
SELECT *
FROM Clienti AS C
WHERE NOT EXISTS (SELECT *
FROM Fatture AS F
WHERE C.IdCliente = F.IdCliente)
AND C.IdCliente IS NOT NULL;
In questo caso ci aspettiamo che la colonna IdCliente sia chiave primaria della tabella Clienti, di conseguenza essa non sarà mai NULL. Ma in generale, quando scriviamo query del genere, potremmo voler estrarre anche le eventuali righe della tabella Clienti dove IdCliente è Null. Per far questo ci basterà eliminare l’ultima condizione in grassetto (che invece con la NOT IN sarebbe calcolata implicitamente). Ovviamente non può esserci una regola universale, ma dipenderà da ciò che vogliamo estrarre caso per caso.
L’importante è essere consapevoli che, in una query come la precedente senza l’ultimo filtro “AND C.IdCliente IS NOT NULL”:
- a differenza della NOT IN, la sola condizione NOT EXISTS mostrerà sempre anche le righe della tabella Clienti con un Null all’interno della colonna IdCliente, a prescindere dal contenuto della tabella Fatture;
- a differenza della NOT IN, un eventuale Null all’interno della colonna IdCliente tabella Fatture non comprometterà il risultato finale.
Considerazioni analoghe possono essere fatte in una query con una LEFT JOIN e un filtro successivo
SELECT *
FROM Clienti AS C
LEFT JOIN Fatture AS F
ON C.IdCliente = F.IdCliente
WHERE F.IdCliente IS NULL;
Anche in questo caso vedrò sempre le righe della tabella Clienti dove IdCliente è Null, mentre dei Null all’interno della colonna della tabella Fatture non comprometteranno il risultato.
Continua a imparare
- Segui la lezione successiva sul Create TABLE
- Torna all’indice delle lezioni
- Visita la pagina del mio video corso online di SQL