Firebird Documentation Index → Guida sull'uso di NULL nel linguaggio SQL di Firebird → Frasi condizionali e cicliche |
Se l'espressione di test di uno statement IF
risolve a NULL
, la clausola
THEN viene saltata e la parte
ELSE (se presente) viene eseguita. In altre parole,
NULL
e false
di comportano
allo stesso modo in questo contesto, pertanto nelle situazioni in cui si
attende false
ma arriva NULL
,
non ci sono problemi. Però abbiamo già visto esempi di
NULL
arrivati al posto di true
e quindi possono succedere cose strane perchè questo modificherebbe
completamente il funzionamento del
programma!
Gli esempi seguenti mostrano alcuni fra i diabolici (e quindi
perfettamente logici) effetti del NULL
negli
statement di IF.
Usando Firebird 2 o successivi, si possono raggirare tutte le
trappole di cui qui stiamo parlando, semplicemente usando l'opratore
[NOT] DISTINCT invece di
«=
» e
«<>
» !
if (a = b) then Variabile = 'uguali'; else Variabile = 'diversi';
Se a
e b
sono entrambi
NULL
, la Variabile
sarà
«diversi
» dopo aver eseguito il
codice. La ragione è che l'espressione «a =
b
», come abbiamo visto precedentemente, vale
NULL
se almeno uno dei termini è
NULL
. Con l'espressione di test che vale
NULL
, il blocco then
non
viene eseguito, ed invece viene eseguito il blocco
else
.
if (a <> b) then Variabile = 'diversi'; else Variabile = 'uguali';
In questo caso, Variabile
diventerà
«uguali
» se a
vale NULL
ed invece b
no,
oppure viceversa. La spiegazione è analoga a quella dell'esempio
precedente.
Allora come si può mettere in piedi un test di egualglianza che
dia effettivamente il risultato aspettato in tutti
i casi, anche con operandi a NULL
? In Firebird 2
abbiamo già visto che si può usare DISTINCT (in
Controllare la
diversità (Firebird 2+)). Nelle precedenti versioni
bisogna scrivere un po' di codice. Questo è mostrato nella sezione Controlli di
eguaglianza, più avanti. Per ora è sufficiente
ricordare che bisogna andare con i piedi di piombo con gli
IF nei condizionali se possono trasformarsi in
NULL
.
Altro aspetto da non scordare: un'espressione di controllo a
NULL
può comportarsi come un
false
in una IF, ma
non vale false
. È sempre ed
ancora NULL
, ciò significa che la sua negazione è
ancora (oibò!) NULL
e non
«true
». Come conseguenza, negare
l'espressione di controllo e contemporaneamente scambiare le parti
THEN ed ELSE fra loro può
cambiare il comportamento dello statement IF. Nella
logica binaria, dove esistono solo true
e
false
, una cosa del genere non potrebbe mai
accadere.
Per mostrare questo fatto, riformuliamo l'ultimo esempio in questo modo:
if (not (a <> b)) then Variabile = 'uguali'; else Variabile = 'diversi';
Nella versione originale, se un operando fosse stato
NULL
e l'altro no (quindi intuitivamente
diversi) il risultato sarebbe stato
«uguali
». Qui invece è
«diversi
». Spiegazione: un operando
è NULL
, pertanto «a <>
b
» è NULL
, pertanto
«not(a <> b)
» è ancora
NULL
, e quindi viene eseguito
l'ELSE. Ma non c'è modo di gioire del fatto che
questo risultato è corretto mentre il precedente non lo era: in
questa versione riformulata, il risultato è
«diversi
» se sono entrambi
NULL
, mentre la versione originale almeno
questo lo «imbroccava» giusto.
Naturalmente, finchè nessuno dei due operandi nell'espressione di
test rischia di essere NULL
, si possono fare gli
IF come sopra. Inoltre, negando il test e
contemporaneamente scambiando le parti THEN e
ELSE continua a funzionare giusto, per quanto
complessa sia la frase, finchè restano diversi da
NULL
gli operandi. È tremendamente perfido quel che
succede quando gli operandi sono quasi sempre
diversi da NULL
, cosicchè nella stragran
maggioranza dei casi fila tutto liscio, In queste situazioni, qualche
raro NULL
vagante può rimanere nascosto per molto
tempo, rovinando i dati silenziosamente.
Firebird ha introdotto il costrutto CASE nella versione 1.5, con due varianti sintattiche. La prima, detta sintassi semplice;
case <espressione> when <espr1> then <valore1> when <espr2> then <valore2> ... [else <valoredefault>] end
Questa funziona più o meno come il case
del Pascal o
lo switch
del C:
l'<espressione>
viene confrontata con
<espr1>
,
<espr2>
ecc., finchè non viene trovata
una corrispondenza, nel qual caso viene riportato il valore associato.
Se non c'è corrispondenza e c'è la clausola ELSE,
si riporta il <valoredefault>
. Se manca
anche la clausola ELSE, il valore è un bel
NULL
.
Importante è sapere che i confronti sono fatti proprio con
l'operatore «=
», per cui se
<espressione>
è
NULL
, ignora tutte le
<esprN>
. In questo caso, per avere un
risultato non nullo, bisogna adoperare la clausola
ELSE.
Ad ogni modo, è corretto specificare NULL
(o
una qualsiasi espressione valida che possa valere
NULL
) per il valore risultante.
La seconda sintassi, o sintassi analitica:
case when <condizione1> then <valore1> when <condizione2> then <valore2> ... [else <valoredefault>] end
Qui, le varie <condizioneN>
sono
confronti che possono dare uno dei tre possibili risultati:
true
, false
, oppure
NULL
. Ancora una volta, solo
true
va bene, per cui una condizione come «A
= 3» o perfino «A = null» non viene soddisfatta
quando A è NULL
. Ricordando che «IS
[NOT] NULL» non riporta mai NULL
,
se A è NULL
, la condizione «A is null»
riporta true
ed il corrispondente
<valoreN>
viene riportato. In Firebird
2+ si può usare anche «IS [NOT] DISTINCT
FROM» nelle condizioni, in quanto anche questo
operatore non riporta mai NULL
.
Quando si valuta la condizione di una ciclica
WHILE, NULL
si comporta come
in una frase IF: se la condizione risolve a
NULL
, non si rientra nel ciclo, come se fosse
false
. Ancora una volta, vediamo cosa succede con
l'inverso, cioè usando NOT. In una condizione
come
while ( Contatore > 12 ) do
che salterebbe tutto il blocco del ciclo se Contatore è
NULL
, negandola con
while ( not Contatore > 12 ) do
farà lo stesso. Può essere che entrambe le situazioni vadano nel verso desiderato, solo che è neccessario essere informati del fatto che questi controlli apparentemente complementari in realtà si comportano allo stesso modo.
Per evitare ogni possibile confusione, bisogna evidenziare che i
cicli di FOR nel PSQL di Firebird hanno una
funzione completamente diversa dai cicli di WHILE,
o dai cicli di for
nei
linguaggi di programmazione in generale. Il ciclo di
FOR in Firebird ha la forma:
for<select-statement>
into<var-list>
do<code-block>
ed esegue il blocco di codice tante volte quante sono le righe
lette attraverso la SELECT. Continua finchè non vengono lette tutte le
righe, a meno che non intervenga una eccezione oppure uno statement tipo
BREAK, LEAVE or
EXIT. Leggere un NULL
, o una
riga di campi tutti a NULL
,
non ferma il ciclo!
Firebird Documentation Index → Guida sull'uso di NULL nel linguaggio SQL di Firebird → Frasi condizionali e cicliche |