NOT IN: perché è meglio evitare il suo uso in SQL

not in nel linguaggio SQL

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

 

Articoli correlati

In questa pagina trovi altre query comuni in cui, senza un’opportuna conoscenza del linguaggio, potremmo ottenere un risultato diverso da quello aspettato.

Torna su