Pourquoi les deux éléments suivants renvoient-ils zéro? Sûrement le second est une négation du premier? J'utilise SQL Server 2008.
DECLARE
@a VARCHAR(10) = NULL ,
@b VARCHAR(10) = 'a'
SELECT
CASE WHEN ( ( @a IS NULL
AND @b IS NULL
)
OR @a = @b
) THEN 1
ELSE 0
END , -- Returns 0
CASE WHEN NOT ( ( @a IS NULL
AND @b IS NULL
)
OR @a = @b
) THEN 1
ELSE 0
END -- Also returns 0
C'est une négation. Cependant, vous devez comprendre les NULL ANSI - une négation d'un NULL est également un NULL. Et NULL est une valeur de vérité fausse.
Par conséquent, si l'un de vos arguments est nul, le résultat de @a = @b
Sera nul (faux), et une négation de cela sera également nul (faux).
Pour utiliser la négation comme vous le souhaitez, vous devez vous débarrasser du NULL. Cependant, il pourrait être plus facile de simplement inverser les résultats de la comparaison à la place:
case when (...) then 1 else 0 end,
case when (...) then 0 else 1 end
Ce qui vous donnera toujours soit 1, 0
Soit 0, 1
.
MODIFIER:
Comme l'a noté jpmc26, il pourrait être utile de développer un peu le comportement des valeurs Null afin de ne pas avoir l'idée qu'un seul NULL
fera tout NULL
. Il existe des opérateurs qui ne renvoient pas toujours null
lorsque l'un de leurs arguments est nul - l'exemple le plus évident étant is null
, Bien sûr.
Dans un exemple plus large, les opérateurs logiques dans T-SQL utilisent l'algèbre de Kleene (ou quelque chose de similaire), qui définit les valeurs de vérité d'une expression OR
comme ceci:
| T | U | F
T | T | T | T
U | T | U | U
F | T | U | F
(AND
est analogue, tout comme les autres opérateurs)
Vous pouvez donc voir que si au moins l'un des arguments est vrai, le résultat sera également vrai, même si l'autre est inconnu ("null"). Ce qui signifie également que not(T or U)
vous donnera une valeur de vérité fausse, tandis que not(F or U)
vous donnera également un valeur de vérité fausse, même si F or U
est fausse - puisque F or U
est U
et not(U)
est aussi U
, ce qui est falsifié.
Ceci est important pour expliquer pourquoi votre expression fonctionne comme vous vous y attendez lorsque les deux arguments sont nuls - @a is null and @b is null
A la valeur true et true or unknown
Est évalué à true
.
Ce comportement "bizarre" que vous rencontrez est provoqué par les valeurs NULL
.
La négation de NOT (Something that returns NULL)
n'est pas TRUE
, c'est toujours NULL
.
PAR EXEMPLE.
SELECT * FROM <Table> WHERE <Column> = null -- 0 rows
SELECT * FROM <Table> WHERE NOT (<Column> = null) -- Still 0 rows
En plus de ce qui a été dit ici, vous pouvez éviter ce comportement en utilisant
SET ANSI_NULLS OFF
Ce qui permettra à l'optimiseur de traiter NULL
comme valeur normale et de retourner TRUE\FALSE
. Vous devriez noter que ce n'est pas recommandé du tout et vous devriez l'éviter!
C'est un problème avec @ a = @ b si l'une de ces valeurs est nulle alors ce sera un problème
Si vous essayez ci-dessous, le code donnera des résultats corrects
DECLARE
@a VARCHAR(10) = NULL ,
@b VARCHAR(10) = 'a'
SELECT
CASE WHEN ( ( @a IS NULL
AND @b IS NULL
)
OR @a = @b
) THEN 1
ELSE 0
END , -- returns 0
CASE WHEN NOT ( ( @a IS NULL
AND @b IS NULL
)
OR ISNULL(@a,-1) = ISNULL(@b,-1)
) THEN 1
ELSE 0
END -- also returns 0
NOT
est toujours une négation. La raison de ce comportement de T-SQL réside dans le fait que les valeurs null
sont traitées d'une manière spéciale en fonction d'un paramètre de configuration de base de données (appelé ansi_nulls
). Selon ce paramètre, null
est soit traité de la même manière que toute autre valeur, soit traité comme "valeur non définie". Dans ce cas, toutes les expressions contenant des valeurs nulles sont considérées comme invalides.
De plus, l'expression
(@a IS NULL AND @b IS NULL)
OR
@a = @b
ne couvre que le cas où les deux variables sont NULL
, il ne traite pas les cas où soit @a
ou @b
est NULL
. Si cela se produit, le résultat dépend du réglage de ansi_nulls
: si c'est on
, alors le résultat de @a = @b
est toujours false
si l'une des variables est NULL
.
Si ansi_nulls
est off
, puis NULL
est traité comme une valeur et se comporte comme prévu.
Pour éviter un tel comportement inattendu, vous devez couvrir tous les cas comme suit:
DECLARE
@a VARCHAR(10) = 'a',
@b VARCHAR(10) = null
SELECT
CASE
WHEN (@a IS NOT null AND @b IS null) THEN 0
WHEN (@a IS null AND @b IS NOT null) THEN 0
WHEN (@a IS null AND @b IS null) THEN 1
WHEN (@a=@b) THEN 1
ELSE 0
END
Notez que dans cet exemple tous les cas nuls sont traités avant le @a=@b
la casse est vérifiée (dans une instruction CASE
, les WHEN
sont traités dans l'ordre où ils apparaissent, et si une condition correspond, le traitement est terminé et la valeur spécifiée est renvoyée).
Pour tester toutes les combinaisons possibles (pertinentes), vous pouvez utiliser ce script:
DECLARE @combinations TABLE (
a VARCHAR(10),b VARCHAR(10)
)
INSERT INTO @combinations
SELECT 'a', null
UNION SELECT null, 'b'
UNION SELECT 'a', 'b'
UNION SELECT null, null
UNION SELECT 'a', 'a'
SELECT a, b,
CASE
WHEN (a IS NOT null AND b IS null) THEN 0
WHEN (a IS null AND b IS NOT null) THEN 0
WHEN (a IS null AND b IS null) THEN 1
WHEN (a=b) THEN 1
ELSE 0
END as result
from @combinations
order by result
Il renvoie:
En d'autres termes, dans ce script null
est traité comme une valeur, donc a='a'
et b=null
Retour 0
, ce à quoi vous vous attendiez. Seulement si les deux variables sont égales (ou les deux null
), elle retourne 1
.