Vai al contenuto

Il “dramma” del valore NULL in SQL

Un database SQL consente, per qualsiasi tipo di dati, di avere un valore NULL. Il valore NULL non è lo stesso di una stringa vuota o di un numero intero uguale a zero. In parole povere significa “sconosciuto”: rappresenta quindi informazioni mancanti e informazioni inapplicabili.

Avere una stringa NULL, ad esempio, non equivale ad avere una stringa vuota. Significa che non sai cosa c’è nella stringa. Se stai facendo un database e in un record imposti il ​​valore NULL in un campo, significa che ancora non conosci il valore ma ciò non implica che il record non esista. NULL è uno stato, non un valore.

NULL in un confronto nelle query di SELECT

Se tratti il concetto di NULL al pari di un numero o di una costante come PI, allora il risultato delle interrogazioni su SQL potrebbero portare a risultati inattesti e insperati. Ad esempio molti si mettono nei guai quando cercano di inserire nella condizione della clausula WHERE una colonna filtrata per il valore NULL.

Immaginiamo di avere un database di anagrafiche. In questo database è presente una tabella persone con due campi (nome e data_di_nascita). Se un soggetto non ha impostato la data di nascita, allora il valore del campo corrispondente per quel record è NULL.

Che succede se cerchiamo di trovare con una SELECT tutti i soggetti che non hanno una data di nascita? La tendenza, per chi ancora non è incappato in questo problema, è scrivere una cosa del genere:

SELECT * FROM persone WHERE data_di_nascita = NULL
A prima vista l’espressione sembra andare bene, è sintatticamente corretta. Tuttavia non vengono restituite righe e quindi tu pensi che non esistono persone senza data di nascita. Eccellente, tiri un sospiro di sollievo. Eppure è sbagliato! E l’errore ricade proprio su questa parte della tua espressione data_di_nascita = NULL. La query scritta poc’anzi sta a significare: seleziona tutti i record dove la data_di_nascita è uguale a qualcosa di sconosciuto.

Inserire un NULL nel campo data_di_nascita potrebbe significare che non conosciamo la data di nascita del soggetto oppure più semplicemente che sia stata omessa di proposito, quindi come si può mai dire che qualcosa di sconosciuto è uguale a qualcos’altro sconosciuto?

Lo standard SQL afferma che questo uso di WHERE con la condizione <expression> = NULL non è valida e deve generare un messaggio di errore o un’eccezione. Sfortunatamente, molti RDBMS  non lo fanno: restituisce solo risultati “errati”.

Allora quello che bisogna chiedere è se il valore nel tipo di dati è sconosciuto, non se è uguale a qualcosa che è sconosciuto. In altre parole, lo standard SQL insiste sul fatto che non è possibile utilizzare = NULL, o <>NULL, e invece dovrebbe usare ISNULLo ISNOTNULL. La query precedente diventa quindi:

SELECT * FROM persone WHERE data_di_nascita IS NULL

NULL utilizzato nelle espressioni

Il problema non esiste solo nei confronti nelle condizioni WHERE. Quando si esegue un’operazione aritmetica su tipi di dati numerici e uno degli operandi o argomenti è NULL, è probabile che il risultato sia NULL. Immagina di provare a eseguire questa query:

SELECT 22.0/(7.0*NULL)

Il risultato sarà NULL. Ma questo è un errore creato di proposito. Nessuno si immaginerebbe di inserire NULL in un’operazione matematica di questo tipo. Una cosa più comune invece potrebbe accadere quando in una procedura si crea una variabile e si dimentica di inizializzarla. In questo caso il risultato sarà NULL. Ovviamente, se hai sette mele e le moltiplichi per una quantità sconosciuta di mele, il risultato è una quantità sconosciuta.

NULL nelle concatenazioni di stringhe

Capita a volte di dover concatenare dei campi in una SELECT. La funzione in MySql è CONCAT(stringa1, stringa2, …, stringa n) ed è facile intuire come viene utilizzata. Dobbiamo ad esempio concatenare il nome, l’eventuale secondo nome e il cognome di una persona. Sappiamo, quando è stata creata la tabella, che i campi cognome e nome sono stati definiti NOT NULL, mentre il secondo_nome potrebbe essere NULL (cioè significa che per alcune persone semplicemente non sappiamo il secondo nome oppure più semplicemente hanno solo un nome…)

Vogliamo tirare fuori un elenco di nominativi completo, magari ordinato per cognome. Usiamo a tale scopo la funzione CONCAT() di MySql. La query sarà questa:

SELECT CONCAT(cognome, " ", nome, " ", secondo_nome) AS p FROM persone ORDER BY p ASC

Il risultato potrebbe essere disastroso (un elenco di NULL), soprattutto nel caso in cui nessuna persona archiviata avesse un secondo nome. E’ un errore semantico, per il motore SQL la query è logicamente corretta. Se abbiamo avuto sfortuna i valori NULL in ogni riga in corrispondenza del campo secondo_nome si sono “propagati” per creare un risultato NULL. Ha senso come l’operazione matematica precedente. Se concatenate stringhe e non sapete cosa si trova in una delle stringhe, il risultato della concatenazione deve essere effettivamente NULL. Dal punto del motore SQL, hai posto una domanda stupida e hai ottenuto una risposta stupida, ma corretta.

Quello che forse intendevamo dire era “se non sai cos’è, ignoralo”. Per questo compito esiste la funzione COALESCE (valore_1, valore_2, …., valore_n): prende in input un elenco di parametri e restituisce il primo valore non NULL. Se sono tutti NULL, restituisce NULL. In questo caso, possiamo usarlo per restituire una stringa vuota se il valore nella colonna è NULL.

La query modificata sarà questa:

SELECT CONCAT(COALESCE(cognome, ""), " ", COALESCE(nome, ""), " ", COALESCE(secondo_nome, "")) AS p FROM persone ORDER BY p ASC
Possiamo fare di meglio. Si nota infatti che se il secondo_nome è NULL, allora la concatenazione restituirà un valore con uno spazio alla fine del nome, in questo modo “Tramontana Gianluca “. In questo caso possiamo usare LTRIM, RTRIM e REPLACE per dare una ripulita a destra e a sinistra di eventuali spazi bianchi in più.

La query modificata, sapendo che a sinistra non può esserci uno spazio bianco poichè il cognome non è mai NULL, sarà questa:

SELECT RTRIM(CONCAT(COALESCE(cognome, ""), " ", COALESCE(nome, ""), " ", COALESCE(secondo_nome, ""))) AS p FROM persone ORDER BY p ASC

NULL nel raggruppamento, ordinamento e altre clausule

Lo standard SQL afferma che NULL è uno stato e non un valore. Come si ordinano i dati? Potresti aspettarti che l’ordinamento in base a una colonna nullable comporterà che le righe che hanno un valore NULL siano sparse casualmente attorno all’ordinamento. Non è così, infatti i valori NULL sono ordinati come se avessero tutti lo stesso valore. Dove dovrebbero essere nell’ordinamento? Lo standard SQL consente di specificare se fluttuano in alto o in fondo all’ordine con le parole chiave NULLSFIRST e NULLSLAST. Ad esempio per SQL Server è sempre NULLSFIRST.

Come l’ordinamento, il raggruppamento viene eseguito come se tutti i valori NULL fossero considerati uguali, in contrasto con il modo in cui vengono trattati in un’espressione. In pratica sono raccolti in un singolo gruppo.

Che succede quando si prova ad eseguire un’operazione di unione? L’operazione UNION rimuove tutte le righe duplicata, eseguendo la classica operazione di unione matematica. Se due righe hanno entrambe NULL in una o più colonne ma sono identiche, che succede? Se il motore di database non è in grado di confrontare i valori NULL, non è in grado di dire che sia un record “duplicato” durante le operazioni UNION, quindi confrontando le righe in base ai valori NULL, decide che sono identiche e rimuove la riga “duplicata”.

Per farla breve, l’uso di clausule di tipo PARTITION BY, DISTINCT, GROUP BY, UNION, INTERSECT, ORDER BY e EXCEPT in particolari situazioni con la presenza di valori NULL, non causa errori di sintassi e il motore SQL processa correttamente la richiesta, tuttavia i risultati potrebbero essere difformi a causa del fatto che i valori NULL vengono trattati in una maniera un po particolare, in contrasto con il modo in cui i valori NULL vengono gestiti poi nelle espressioni o confronti.