Когда "НЕ" не отрицание?
Почему оба возвращаемых значения равны нулю? Неужели второе - это отрицание первого? Я использую 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
Ответы
Ответ 1
Это отрицание. Однако вам нужно понять ANSI NULLs - отрицание NULL также является NULL. И NULL - значение ложной истины.
Следовательно, если любой из ваших аргументов равен NULL, результат @a = @b
будет null (фальшивый), а отрицание этого также будет нулевым (фальшивым).
Чтобы использовать отрицание так, как вы хотите, вам нужно избавиться от NULL. Однако проще было бы просто изменить результаты сравнения:
case when (...) then 1 else 0 end,
case when (...) then 0 else 1 end
Это всегда даст вам 1, 0
или 0, 1
.
EDIT:
Как отмечалось в jpmc26, может быть полезно развернуть бит о том, как ведут себя NULL, так что вы не понимаете, что один NULL
сделает все NULL
. Существуют операторы, которые не всегда возвращают NULL
, когда один из их аргументов равен нулю - наиболее очевидным примером является is null
, конечно.
В более широком примере логические операторы в T-SQL используют алгебру Клейна (или что-то подобное), которая определяет значения истинности выражения OR
следующим образом:
| T | U | F
T | T | T | T
U | T | U | U
F | T | U | F
(AND
аналогичен, как и другие операторы)
Итак, вы можете видеть, что если хотя бы один из аргументов истинен, результат также будет истинным, даже если другой неизвестен ( "null" ). Это также означает, что not(T or U)
даст вам значение ложной истины, а not(F or U)
также даст вам значение ложной истины, несмотря на то, что F or U
является ложным - поскольку F or U
есть U
, а not(U)
также U
, что является ложным.
Это важно, чтобы объяснить, почему ваше выражение работает так, как вы ожидаете, когда оба аргумента равны нулю. @a is null and @b is null
оценивает значение true, а true or unknown
оценивается как true
.
Ответ 2
Это странное поведение, с которым вы сталкиваетесь, вызвано значениями NULL
.
Отрицание NOT (Something that returns NULL)
не TRUE
, оно все еще NULL
.
например.
SELECT * FROM <Table> WHERE <Column> = null -- 0 rows
SELECT * FROM <Table> WHERE NOT (<Column> = null) -- Still 0 rows
В дополнение к тому, что было сказано здесь, вы можете избежать этого поведения, используя
SET ANSI_NULLS OFF
Это позволит оптимизатору обрабатывать NULL
как нормальное значение и возвращать TRUE\FALSE
. Вы должны заметить, что это не рекомендуется вообще, и вам следует избегать этого!
Ответ 3
Это проблема с @a = @b, если любое из этого значения равно null, то это будет проблема
Если вы попробуете ниже код, вы получите правильные результаты
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
NOT
всегда является отрицанием. Причина такого поведения T-SQL заключается в том, что значения null
обрабатываются особым образом в зависимости от настройки конфигурации базы данных (известной как ansi_nulls
). В зависимости от этой настройки null
обрабатывается так же, как любое другое значение, или обрабатывается как "значение не установлено". В этом случае все выражения, содержащие нулевые значения, считаются недействительными.
Кроме того, выражение
(@a IS NULL AND @b IS NULL)
OR
@a = @b
охватывает только случай, когда обе переменные равны null
, это не касается случаев, когда либо @a
, либо @b
равно null
. Если это произойдет, результат зависит от параметра ansi_nulls
: если он on
, тогда результат @a = @b
всегда false
, если одна из переменных null
.
Если ansi_nulls
- off
, тогда null
рассматривается как значение и ведет себя так, как вы ожидаете.
Чтобы избежать такого неожиданного поведения, вы должны охватить все случаи следующим образом:
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 (@[email protected]) THEN 1
ELSE 0
END
Примечание, что в этом примере все нулевые случаи рассматриваются до проверки события @[email protected]
(в операторе CASE
, WHEN
обрабатываются в порядке по мере их появления, и если условие соответствует, обработка завершена и возвращается указанное значение).
Чтобы проверить все возможные (соответствующие) комбинации, вы можете использовать этот 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
Он возвращает:
![результат запроса]()
Другими словами, в этом script null
рассматривается как значение, поэтому a='a'
и b=null
возвращает 0
, что и ожидалось. Только если обе переменные равны (или оба null
), он возвращает 1
.