Leggendo qua e là sui vari newsgroup, mi sono accorto che una delle principali fonti di confusione è data dall'utilizzo, in SQL dei NULLs.
Cerchiamo di fare un po' di luce su questo "oscuro" concetto..
Che cosa è un NULL?Nel linguaggio SQL (ANSI-SQL) si definisce come NULL quel valore speciale che identifica un'assenza di valore.
Secondo lo standard ANSI-92 il NULL non è un "dato", ma piuttosto indica che si è tralasciato il dato stesso.
Facciamo un esempio: in una tabella indicante Nome, Cognome e Città
USE tempdb
-- Definisco la tabella myTable
CREATE TABLE myTable
(
Nome varchar(25) NOT NULL,
Cognome varchar(25) NOT NULL,
Citta varchar(25) NULL
) ON [PRIMARY]
GO
-- La popolo
SET NOCOUNT ON
INSERT dbo.myTable VALUES('Sandro', 'Bizioli', 'Brescia')
INSERT dbo.myTable VALUES('Mario', 'Rossi', 'Milano')
INSERT dbo.myTable VALUES('Andrea', 'Ferro', NULL)
SET NOCOUNT OFF
-- Seleziono i dati
Select Nome, Cognome, Citta FROM myTable
-- Elimino la tabella
Drop table myTable
/* OUTPUP
Nome Cognome Citta
---------------------- ---------------------- ----------------------
Sandro Bizioli Brescia
Mario Rossi
Andrea Ferro NULL
(3 row(s) affected)
*/
Quello che notiamo è che la città di Sandro è Brescia, mentre la colonna che identifica la città di Mario, contiene una stringa di lunghezza zero.
E' importante, però, capire che sia per Sandro che per Mario, noi
conosciamo il valore.
Per Andrea le cose non sono così semplici, perchè la colonna città contiene NULL che indica la nostra NON conoscenza del fatto che ci sia oppure no una città di appartenenza.
Un primo consiglio è quello di utilizzare esclusivamente i NULL per indicare la nostra effettiva "
non conoscenza" di un valore e mai in sostituzione di valori a zero, o di stringhe a lunghezza zero (blank).
Fin qui sembra che le cose siano abbastanza gestibili e che non ci siano particolari problemi se non di organizzazione logica dei nostri dati. Purtroppo non è così, perchè i veri problemi li avremo nel momento in cui andremo ad interrogare tabelle e campi contenenti valori NULL.
Ad esempio, supponiamo di confrontare tra loro due valori boolean, TRUE e FALSE:
TRUE AND TRUE = TRUE
TRUE AND FALSE = FALSE
FALSE AND FALSE = FALSE
FALSE AND TRUE = FALSE
TRUE OR TRUE = TRUE
TRUE OR FALSE = TRUE
FALSE OR FALSE = FALSE
FALSE OR TRUE = TRUE
NOT TRUE = FALSE
NOT FALSE = TRUE
Le cose cambiano notevolmente se introduciamo il concetto di "non conosciuto", NULL per l'appunto.
Lo Standard ANSI-92 SQL, indica che se uno degli operatori è NULL il risultato della comparazione sarà sempre "Sconosciuto"
TRUE AND NULL =
UnknownFALSE AND NULL =
UnknownNULL AND TRUE =
UnknownNULL AND FALSE =
UnknownNULL AND NULL =
UnknownFacciamo un esempio concreto:
USE tempdb
-- Definisco la tabella myTable
CREATE TABLE myTable
(
Nome varchar(25) NOT NULL,
Cognome varchar(25) NOT NULL,
Citta varchar(25) NULL
) ON [PRIMARY]
-- La popolo
SET NOCOUNT ON
INSERT dbo.myTable VALUES('Sandro', 'Bizioli', 'Brescia')
INSERT dbo.myTable VALUES('Mario', 'Rossi', '')
INSERT dbo.myTable VALUES('Andrea', 'Ferro', NULL)
INSERT dbo.myTable VALUES('Paolo', 'Bianchi', 'Milano')
INSERT dbo.myTable VALUES('Luca', 'Verdi', 'Torino')
INSERT dbo.myTable VALUES('Marco', 'Grandi', NULL)
SET NOCOUNT OFF
-- Estraggo i valori
SELECT *
FROM myTable
WHERE Citta IS NULL
/* OUTPUP
Nome Cognome Citta
---------------------- ---------------------- ----------------------
Andrea Ferro NULL
Marco Grandi NULL
(2 row(s) affected)
*/
-- Estraggo i valori
SELECT *
FROM myTable
WHERE Citta = NULL
/* OUTPUP
Nome Cognome Citta
---------------------- ---------------------- ----------------------
(0 row(s) affected)
*/
-- Elimino la tabella
Drop table myTable
Come è possibile notare nella prima select le righe restituite sono 2, mentre nel secondo caso nessuna: come sostenuto dallo Standard ANSI-92 SQL, se uno degli operatori è NULL il risultato sarà "sconosciuto".
Particolare attenzione anche alle clausole COUNT() che potrebbero comportarsi in modo anomalo:
-- Estraggo i valori
SELECT count(*)
FROM myTable
/* Output
6
*/
SELECT count(citta)
FROM myTable
/* Output
4
*/
Nel primo caso count(*) restituisce il numero totale di righe, mentre count(citta) conta il numero delle riche che posseggono citta, ovviamente escludendo i valori NULL!
Attenzione che se voglio ottenere tutte il totale delle righe che hanno citta a NULL, non dovrò usare count(citta), bensì count(*)
SELECT count(citta)
FROM myTable
WHERE citta is NULL
/* Output
0
*/
SELECT count(*)
FROM myTable
WHERE citta is NULL
/* Output
2
*/
In ANSI SQL, i NULL non sono uguali e comparabili a nulla, neppure ad altri NULL. Il confronto restituirà sempre come valore SCONOSCIUTO. Per verificare, ulteriormente, questa situazione scriviamo:
SET ANSI_NULLS ON
DECLARE @mioVal CHAR(4)
SET @mioVal = NULL
If @mioVal = NULL
PRINT 'TRUE'
ELSE IF NOT(@mioVal = NULL)
PRINT 'FALSE'
ELSE
PRINT 'UNKNOWN'
/* OUTPUT
UNKNOWN
*/
Nell'istruzione IF sono stati verificati entrambi i casi e cioè se NULL = NULL se NULL <> NULL, ma entrambi hanno dato esito negativo entrando nell'ultimo ELSE che indica, per l'appunto, l'impossibilità di confronto.
Da notare il fatto che tale regola sussiste solo se si rispetta lo Standard ANSI-92 SQL, infatti, se cambiamo la prima riga a
"SET ANSI_NULLS OFF" il risultato diverrà "TRUE".
Questa impostazione pregiudica la portabilità del codice SQL su altri sistemi creando confusione là dove, invece, lo Standard Ansi è rispettato.
E' decisamente consigliabile non modificare l'impostazione ANSI_NULLS ON!
Uguale attenzione va fatta nelle
operazioni matematiche: qualora uno degli operatori fosse un NULL il risultato sarà sempre NULL.
SELECT 1 + 2 + NULL + 3
/* OUTPUT
NULL
*/
Ricordiamoci, inoltre, che nel momento in cui dichiariamo una variabile, essa assume il valore NULL fino a quando non la inizializziamo attraverso l'istruzione SET.
Scrivere:
Declare @mioVal as char(4)
Set @mioVal = NULL
Select @mioVal
/* OUTPUT
NULL
*/
sarà perfettamente uguale allo scrivere:
Declare @mioVal as char(4)
Select @mioVal
/* OUTPUT
NULL
*/
Non è neppure possibile utilizzare una variabile valorizzata a NULL al posto della clausola IS NULL poichè verrebbe sollevato un errore di sintassi non valida!
DECLARE @myVal AS Char(4)
SET @myVal = NULL
SELECT *
FROM myTable
WHERE Citta IS @myVal