Il capitolo uno del libro Inside Microsoft SQL Server 2008: T-SQL Querying di Itzik Ben-Gan è davvero ricco di contenuti e dopo aver parlato di uno degli aspetti singolari (che caratterizza il linguaggio SQL) nell’articolo Logical Query Processing, parleremo ora di un altro aspetto altrettanto singolare: I possibili valori che ci possiamo aspettare, in SQL, come risultato della valutazione logica di un’espressione.
In molti linguaggi di programmazione valutando un’espressione logica possiamo aspettarci vengano restituiti solo due possibili valori, l’espressione può essere vera (TRUE) oppure falsa (FALSE). In SQL, un’espressione logica può restituire oltre ai valori TRUE, FALSE anche il valore “sconosciuto” UNKNOWN.
Il valore UNKNOWN, in SQL, viene tipicamente restituito quando nell’espressione logica vengono trattati valori NULLs. Se proviamo a valutare le seguenti espressioni il risultato sarà UNKNOWN:
NULL > 50 ?
NULL = NULL ?
(X + NULL) > Y ?
Il NULL rappresenta la mancanza di un valore, quando confrontiamo un valore mancante con un valore esistente, il risultato è sconosciuto UNKNOWN (proprio perché uno dei due valori è mancante e quindi l’espressione non potrà essere ne vera ne falsa). Inoltre, mentre NOT TRUE è FALSE e NOT FALSE è TRUE, l’opposto di un valore sconosciuto è ancora sconosciuto: NOT UNKNOWN è UNKNOWN.
In aggiunta, i valori sconosciuti e i NULLs vengono trattati in modo non omogeneo dai diversi elementi del linguaggio. Ad esempio, tutti i filtri di una query (ON, WHERE e HAVING) trattano UNKNOWN come valore falso (FALSE), di conseguenza le righe per le quali il valore del filtro è UNKNOWN non vengono restituite nel result set della query. Un trattamento diverso viene riservato ai valori sconosciuto valutati in un vincolo di tipo CHECK, in questo caso infatti UNKNOWN è trattato come valore vero (TRUE).
Ipotizziamo di dover implementare un vincolo di tipo CHECK in una colonna di una tabella, per rafforzare l’integrità di dominio e assicurare che vengano inseriti solo valori numerici maggiori di zero (>0). Qualora la condizione del vincolo CHECK si trovasse a dover valutare l’inserimento di un valore NULL, SQL dovrà eseguire il confronto (NULL > 0) ? Come abbiamo detto in precedenza il risultato sarà UNKNOWN e verrà trattato dal vincolo CHECK come valore TRUE.
Con seguente frammento di codice T-SQL viene creata, nel tempdb, la tabella temporanea dbo.tmpUNKNOWN e sulla colonna valore viene implementato il vincolo CHECK chk_valore_greater_zero.
use [tempdb];
go
if OBJECT_ID(‘dbo.tmpUNKNOWN’) is not null
drop table dbo.tmpUNKNOWN;
go
create table dbo.tmpUNKNOWN
(
codice varchar(20)
,descr varchar(40)
,valore decimal(8,2)
);
alter table dbo.tmpUNKNOWN
add constraint [chk_valore_greater_zero] check (valore > 0);
Inseriamo ora una riga nella tabella temporanea, per la colonna valore specifichiamo NULL.
(
codice
,descr
,valore
)
values
(
‘TVL’
,’Three-Valued Logic’
,NULL — UNKNOWN trattato come True dal vincolo CHECK
);
L’inserimento viene eseguito correttamente, abbiamo dimostrato che il vincolo CHECK tratta i valori sconosciuti UNKNOWN come valori TRUE.
Interroghiamo ora la tabella dbo.tmpUNKNOWN, che risultato ci aspettiamo dalle due query che seguono ? Ci aspettiamo che la riga precedentemente inserita non venga estratta perché la clausola WHERE tratta i valori sconosciuti come valori falsi (FALSE)…
select
*
from
dbo.tmpUNKNOWN
where
(valore <> 0) — ???
select
*
from
dbo.tmpUNKNOWN
where
(valore = NULL) — ???
Il confronto tra due NULL nel filtro WHERE restituisce un valore sconosciuto che viene trattato come FALSE, da qui si potrebbe dedurre che ogni valore NULL è diverso da qualsiasi altro NULL. L’eccezione che conferma questa regola è rappresentata dalle clausole UNIQUE, GROUP BY, ORDER BY e dall’operatore SET che riconoscono i NULLs come valori uguali.
Per ottenere in output la riga precedentemente inserita eseguiamo la query seguente dove l’operatore di confronto “=” è stato sostituito con IS NULL.
*
from
dbo.tmpUNKNOWN
where
(valore IS NULL)
Il comportamento, compatibile con lo standard SQL-92, degli operatori di confronto “uguale a” (=) e “diverso da” (<>) quando vengono utilizzati valori NULLs è regolato dall’opzione ANSI_NULLS che appartiene al gruppo ANSI_DEFAULTS; maggiori dettagli sono disponibili in questo post: Quando l’opzione ANSI_NULLS ha effetto su optimizer e prestazioni
Conclusioni
E’ sicuramente una buona idea essere a conoscenza del fatto che il risultato di un’espressione logica può essere sconosciuto (UNKNOWN) e che i NULLs sono trattati in modo diverso dai diversi elementi del linguaggio.
Pulizia del tempdb
/* Pulizia tempdb */
IF OBJECT_ID(‘dbo.tmpUNKNOWN’) IS NOT NULL DROP TABLE dbo.tmpUNKNOWN;