web-dev-qa-db-fra.com

Quand "NON" n'est-il pas une négation?

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
45
ChrisN

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.

50
Luaan

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!

8
sagi

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
4
Kannan Kandasamy

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:

result of query

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.

0
Matt