In SQL, NULL non è zero, non è una stringa vuota e non è falso. Significa «valore sconosciuto» o «non applicabile». Detto così sembra semplice, ma il suo comportamento genera alcuni degli errori più insidiosi nelle query: quelli che non producono messaggi di errore, ma risultati sbagliati che sembrano giusti.
La logica a tre valori
Nella maggior parte dei linguaggi una condizione è vera o falsa. In SQL esiste un terzo esito: sconosciuto (unknown). Qualunque confronto con NULL restituisce sconosciuto:
5 = NULL→ sconosciutoNULL = NULL→ sconosciutoNULL <> 3→ sconosciuto
Il ragionamento è coerente: se non conosco l'età di due persone, non posso dire che sia uguale. Ma la conseguenza pratica è che WHERE colonna = NULL non restituisce mai nessuna riga. La forma corretta è:
SELECT * FROM clienti WHERE telefono IS NULL;
SELECT * FROM clienti WHERE telefono IS NOT NULL;
La clausola WHERE conserva solo le righe per cui la condizione è vera. Le righe per cui è falsa e quelle per cui è sconosciuta vengono scartate allo stesso modo.
Trappola 1: il filtro «diverso da»
Una tabella di prodotti ha una colonna categoria, non sempre compilata. Si vogliono tutti i prodotti che non appartengono alla categoria «Outlet»:
SELECT * FROM prodotti WHERE categoria <> 'Outlet';
I prodotti senza categoria non compaiono, perché NULL <> 'Outlet' è sconosciuto. Se devono essere inclusi, va detto esplicitamente:
SELECT * FROM prodotti
WHERE categoria <> 'Outlet' OR categoria IS NULL;
Alcuni database offrono anche l'operatore IS DISTINCT FROM, che tratta NULL come un valore confrontabile.
Trappola 2: NOT IN con una sottoquery
È la trappola più pericolosa. Si cercano i clienti che non compaiono nella lista dei clienti bloccati:
SELECT * FROM clienti
WHERE id NOT IN (SELECT id_cliente FROM blocchi);
Se anche una sola riga di blocchi ha id_cliente vuoto, la query non restituisce nessun cliente. Il motivo: id NOT IN (1, 2, NULL) equivale a id <> 1 AND id <> 2 AND id <> NULL, e l'ultimo termine è sempre sconosciuto, quindi l'intera condizione non è mai vera.
La soluzione robusta è NOT EXISTS, che non ha questo problema:
SELECT * FROM clienti c
WHERE NOT EXISTS (
SELECT 1 FROM blocchi b WHERE b.id_cliente = c.id
);
Trappola 3: i conteggi
Le funzioni di aggregazione ignorano i valori NULL, con un'eccezione importante.
| Espressione | Che cosa conta o calcola |
|---|---|
COUNT(*) |
Tutte le righe |
COUNT(telefono) |
Solo le righe con telefono non nullo |
SUM(importo) |
Somma dei valori non nulli |
AVG(voto) |
Media dei soli valori non nulli |
La media è il caso più delicato. Se in una tabella di valutazioni chi non ha risposto ha NULL, AVG(voto) calcola la media solo di chi ha risposto. Se invece l'assenza di risposta andava considerata come zero, il risultato è gonfiato. Non esiste una risposta giusta in assoluto: bisogna decidere che cosa significa l'assenza e scriverlo nella query.
Un'altra particolarità: SUM su un insieme in cui tutti i valori sono NULL, o su nessuna riga, restituisce NULL e non zero. In un report questo può far comparire celle vuote dove ci si aspetta 0.
Trappola 4: i calcoli
Qualunque operazione aritmetica o concatenazione con NULL produce NULL:
SELECT prezzo + spedizione FROM ordini;
Se la spedizione non è indicata, il totale diventa sconosciuto. La funzione COALESCE restituisce il primo valore non nullo di un elenco ed è lo strumento standard per gestire questi casi:
SELECT prezzo + COALESCE(spedizione, 0) AS totale FROM ordini;
Va usata con criterio: sostituire NULL con 0 ha senso per una spedizione gratuita, ma non per una temperatura non misurata.
Trappola 5: ordinamenti e vincoli di unicità
Nell'ordinamento, i database non sono d'accordo su dove mettere i NULL: alcuni li considerano più grandi di ogni valore, altri più piccoli. Dove supportato, ORDER BY colonna NULLS LAST rende esplicita la scelta.
Nei vincoli UNIQUE, molti database permettono più righe con NULL nella stessa colonna, proprio perché due NULL non sono considerati uguali. È un comportamento utile, per esempio per un codice facoltativo che deve essere unico quando presente, ma sorprende chi non se lo aspetta.
Regole pratiche di progettazione
- Dichiarare
NOT NULLogni colonna che deve sempre avere un valore. È la difesa più efficace. - Usare valori predefiniti con
DEFAULTquando esiste un valore sensato, come 0 per una quantità. - Non usare valori convenzionali come -1 o «N/D» al posto di
NULL: finiscono nelle somme e nelle medie. - Preferire
NOT EXISTSaNOT INcon le sottoquery. - Quando si scrive un report, chiedersi per ogni colonna: che cosa deve succedere se è vuota?
In sintesi
NULLsignifica sconosciuto, e ogni confronto conNULLdà sconosciuto.- Si filtra con
IS NULLeIS NOT NULL, mai con= NULL. NOT INcon una sottoquery che contieneNULLnon restituisce nulla: meglioNOT EXISTS.COUNT(colonna),AVGeSUMignorano iNULL, con effetti sui report.- Il vincolo
NOT NULLevita gran parte dei problemi alla radice.