¿Cuándo "NO" no es una negación?


¿Por qué los dos siguientes devuelven cero? Seguramente la segunda es una negación de la primera? Estoy usando 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
Author: Matt, 2016-09-13

4 answers

Es una negación. Sin embargo, necesita entender los NULOs ANSI - una negación de un NULL es también un NULL. Y NULL es un falso valor de verdad.

Por lo tanto, si alguno de sus argumentos es nulo, el resultado de @a = @b será nulo (falsy), y una negación de eso también será nulo (falsy).

Para usar la negación de la manera que desee, debe deshacerse del NULL. Sin embargo, podría ser más fácil simplemente revertir los resultados de la comparación:

case when (...) then 1 else 0 end,
case when (...) then 0 else 1 end

Que siempre dará 1, 0 o 0, 1.

EDITAR:

Como señaló jpmc26, podría ser útil expandir un poco sobre cómo se comportan los nulos para que no tenga la idea de que un solo NULL hará todo NULL. Hay operadores que no siempre devuelven null cuando uno de sus argumentos es null - el ejemplo más obvio es is null, por supuesto.

En un ejemplo más amplio, los operadores lógicos en T-SQL usan el álgebra de Kleene (o algo similar), que define los valores de verdad de una expresión OR de la siguiente manera:

  | T | U | F
T | T | T | T
U | T | U | U
F | T | U | F

(AND es análogo, al igual que los otros operadores)

Así que puede ver que si al menos uno de los argumentos es verdadero, el resultado también será verdadero, incluso si el otro es un desconocido ("null"). Lo que también significa que not(T or U) te dará un valor de verdad falso, mientras que not(F or U) también te dará un valor de verdad falso, a pesar de que F or U es falso, ya que F or U es U, y not(U) es también U, que es falsy.

Esto es importante para explicar por qué su expresión funciona de la manera que espera cuando ambos argumentos son null - el @a is null and @b is null se evalúa como verdadero, y true or unknown se evalúa como true.

 50
Author: Luaan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-14 14:48:15

Este comportamiento 'extraño' que está encontrando es causado por los valores NULL.

La negación de NOT (Something that returns NULL) no es TRUE, sigue siendo NULL.

E. G.

SELECT * FROM <Table> WHERE <Column> = null -- 0 rows 
SELECT * FROM <Table> WHERE NOT (<Column> = null) -- Still 0 rows

Además de lo que se ha dicho aquí, puede evitar ese comportamiento utilizando

SET ANSI_NULLS OFF

Que permitirá al optimizador tratar NULL como valor normal, y devolver TRUE\FALSE. Usted debe tener en cuenta que esto no es recomendable en absoluto y usted debe evitarlo !

 8
Author: sagi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-13 13:57:56

Es problema con @a=@b, si este valor es null, entonces será problema

Si intenta el siguiente código dará resultados correctos

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
Author: Kannan Kandasamy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-13 13:32:09

NOT siempre es una negación. La razón de este comportamiento de T-SQL radica en el hecho de que los valores null se tratan de una manera especial dependiendo de una configuración de base de datos (conocida como ansi_nulls). Dependiendo de esta configuración, null se trata de la misma manera que cualquier otro valor o se trata como "valor no establecido". En este caso, todas las expresiones que contienen valores nulos se consideran inválidas.

Además, la expresión

(@a IS NULL AND @b IS NULL)
OR 
@a = @b

Cubre solo el caso cuando ambas variables son NULL, no se trata de casos cuando @a o @b es NULL. Si eso sucede, el resultado depende de la configuración de ansi_nulls: si es on, entonces el resultado de @a = @b es siempre false si una de las variables es NULL.

Si ansi_nulls es off, entonces NULL se trata como un valor y se comporta como se espera.

Para evitar este tipo de comportamiento inesperado, debe cubrir todos los casos de la siguiente manera:

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 

Nota que en este ejemplo todos los casos nulos se tratan antes de marcar el caso @a=@b (en una instrucción CASE, los WHEN se procesan en el orden en que aparecen, y si una condición coincide, el procesamiento finaliza y se devuelve el valor especificado).


Para probar todas las combinaciones posibles (relevantes), puede usar este 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 

Devuelve:

resultado de la consulta

En otras palabras, en este script null se trata como un valor, por lo tanto a='a' y b=null devuelve 0, que es lo que esperabas. Solo si ambas variables son iguales (o ambas null), devuelve 1.

 0
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-19 08:30:11