Выбор SQL с подзапросом IN не возвращает записей, если подзапрос содержит NULL. - PullRequest
5 голосов
/ 27 декабря 2010

Я сталкивался с этим интересным поведением. Я вижу, что левостороннее соединение - это путь, но все же хотелось бы, чтобы это было очищено. Это ошибка или поведенческое поведение? Есть объяснения?

Когда я выбираю записи из левой таблицы, где значение отсутствует в результате подзапроса в правой таблице, ожидаемая «пропущенная» запись не возвращается, если результат подзапроса имеет пустые значения. Я ожидал, что два способа написания этого запроса будут эквивалентны.

Спасибо!

declare @left table  (id int not null primary key identity(1,1), ref int null)
declare @right table (id int not null primary key identity(1,1), ref int null)

insert @left (ref) values (1)
insert @left (ref) values (2)

insert @right (ref) values (1)
insert @right (ref) values (null)

print 'unexpected empty resultset:'
select * from @left
where ref not in (select ref from @right)

print 'expected result - ref 2:'
select * from @left
where ref not in (select ref from @right where ref is not null)

print 'expected result - ref 2:'
select l.* from @left l
  left join @right r on r.ref = l.ref
where r.id is null

print @@version

дает:

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)
unexpected empty resultset:
id          ref
----------- -----------

(0 row(s) affected)

expected result - ref 2:
id          ref
----------- -----------
2           2

(1 row(s) affected)

expected result - ref 2:
id          ref
----------- -----------
2           2

(1 row(s) affected)

Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (X64) 
    Apr  2 2010 15:48:46 
    Copyright (c) Microsoft Corporation
    Standard Edition (64-bit) on Windows NT 6.0 <X64> (Build 6002: Service Pack 2) (Hypervisor)

Ответы [ 4 ]

8 голосов
/ 27 декабря 2010

Это по замыслу. Если совпадение не удается и набор содержит NULL, результат равен NULL, как указано в стандарте SQL.

'1' IN ('1', '3') => true
'2' IN ('1', '3') => false
'1' IN ('1', NULL) => true
'2' IN ('1', NULL) => NULL

'1' NOT IN ('1', '3') => false
'2' NOT IN ('1', '3') => true
'1' NOT IN ('1', NULL) => false
'2' NOT IN ('1', NULL) => NULL

Неформально логика, стоящая за этим, заключается в том, что NULL может рассматриваться как неизвестное значение. Например, здесь не имеет значения, что представляет собой неизвестное значение - «1» явно находится в наборе, поэтому результат верен.

'1' IN ('1', NULL) => true

В следующем примере мы не можем быть уверены, что '2' есть в наборе, но так как мы не знаем всех значений, мы также не можем быть уверены, что это не в наборе. Таким образом, результат равен NULL.

'2' IN ('1', NULL) => NULL

Еще один способ взглянуть на это - переписать x NOT IN (Y, Z) как X <> Y AND X <> Z. Тогда вы можете использовать правила трехзначной логики :

true AND NULL => NULL
false AND NULL => false
3 голосов
/ 27 декабря 2010

Да, так оно и было задумано. Есть также много других соображений между выполнением LEFT JOIN или NOT IN. Вы должны увидеть эту ссылку , чтобы получить очень хорошее объяснение этого поведения.

0 голосов
/ 08 марта 2014

Основная причина поведения объясняется Марком. Это может быть решено несколькими способами: - LEFT JOIN, Фильтрация значений NULL из внутреннего запроса, отфильтровывая их из предложения where ИЛИ из предложения select, используя связанный подзапрос - чтобы назвать несколько.

После трех коротких постов приведено тематическое исследование по одной и той же теме: - NOT IN Подзапрос возвращает ноль строк -Issue , NOT IN Подзапрос возвращает ноль строк -Root Cause , NOT IN Подзапрос возвращает ноль строк -Обходы

0 голосов
/ 27 декабря 2010

Так думает комитет ANSI.

Вы можете предшествовать вашим запросам с

set ansi_defaults OFF

и вы получите ожидаемый результат.

Начиная с SQL Server 7.0, Microsoft довольно строго соблюдает стандарты ANSI.

EDIT:

Не боритесь со значениями по умолчанию. В конце концов ты сдашься.

...