2016-09-13 2 views
45

Почему оба следующих значения возвращают ноль? Неужели второе - это отрицание первого? Я использую 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

Положите 'set ansi_nulls off'' перед вашим запросом, и вы увидите разницу. Тогда вы не получите «NULL» из сравнений, где хотя бы один операнд «NULL», но «false». –

+1

@ Ответ Луаана на 100% правильный. Однако вы можете искать оператор 'IS DISTICT FROM' (' <=> 'в MySQL). – Owen

ответ

50

Это отрицание. Однако вам нужно понять 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, когда один из их аргументов равен нулю - наиболее очевидным примером является 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) даст вам falsy истинностное значение, в то время как not(F or U) будет также дать вам falsy значение истинности, несмотря на F or U быть falsy - с F or U является U и not(U) также U, который falsy.

Это важно, чтобы объяснить, почему ваше выражение работает так, как вы ожидаете, что, когда оба аргумента равны нулю - @a is null and @b is null истинно, и true or unknown вычисляет true.

+1

По-видимому, версии SQL Server позже 2008 года НЕ позволят вам отключить анси. То есть, если вы перестраиваете свои хранимые процедуры, эти версии заставляют их иметь «правильное» поведение и горе вам, если у вас есть устаревший код, который зависит от неправильного поведения. У вас может быть много переписывания. Вот хорошее сообщение в блоге о тех злых ansi nulls: http://www.sqlservercentral.com/blogs/sqlstudies/2014/07/28/what-is-ans-_i_nulls-and-why-will-i-be-glad- когда-это-наконец-уходит/ – mickeyf

+0

что означает «ложное значение истины»? – NoSaidTheCompiler

+0

Разве 'NULL' распространяется только за условием' OR', если '@a IS NULL AND @b IS NULL' является ложным? – jpmc26

4

Это проблема с @ а = @ Ь, если любой из этой величины равно нулю, то это будет проблема

Если вы попробуете ниже код даст правильные результаты

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 
+2

Использование 'ISNULL' делает ненужным' @ ISN NULL AND @b IS NULL'. Это также * предполагает *, что значения никогда не могут быть '-1', и вы уверены, что можете даже поставить' -1' там, когда типы являются «VARCHAR»? – jpmc26

8

Эта «странное» поведение что вы столкнулись, вызвано значениями NULL.

Отрицание NOT (Something that returns NULL) не TRUE, все еще NULL.

E.G.

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. Вы должны заметить, что это не рекомендуется вообще, и вам следует избегать этого!

0

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 обрабатываются в том порядке, в котором они отображаются, и если условие соответствует, обработка завершается, и возвращается указанное значение).


Чтобы проверить все возможные (соответствующие) комбинации, вы можете использовать этот скрипт:

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 

Она возвращает:

result of query

Другими словами, в этом сценарии null является обрабатывается как значение, поэтому a='a' и b=null возвращает 0, что и есть ожидается. Только если обе переменные равны (или оба null), он возвращает 1.

Смежные вопросы