¿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
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
.
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 !
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
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:
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
.
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