SQL "select where not in subquery" не возвращает результатов

Отказ от ответственности: я выяснил проблему (я думаю), но я хотел добавить эту проблему в Stack Overflow, так как не мог (легко) найти ее где угодно. Кроме того, у кого-то может быть лучший ответ, чем у меня.

У меня есть база данных, где одна таблица "Common" ссылается на несколько других таблиц. Я хотел посмотреть, какие записи в общей таблице были потеряны (т.е. Не имели ссылок ни на одну из других таблиц).

Я выполнил этот запрос:

select *
from Common
where common_id not in (select common_id from Table1)
and common_id not in (select common_id from Table2)

Я знаю, что есть осиротевшие записи, но записи не возвращаются. Почему бы и нет?

(Это SQL Server, если это имеет значение.)

Ответ 1

Update:

В этих статьях в моем блоге более подробно описываются различия между методами:


Существует три способа сделать такой запрос:

  • LEFT JOIN / IS NULL:

    SELECT  *
    FROM    common
    LEFT JOIN
            table1 t1
    ON      t1.common_id = common.common_id
    WHERE   t1.common_id IS NULL
    
  • NOT EXISTS:

    SELECT  *
    FROM    common
    WHERE   NOT EXISTS
            (
            SELECT  NULL
            FROM    table1 t1
            WHERE   t1.common_id = common.common_id
            )
    
  • NOT IN:

    SELECT  *
    FROM    common
    WHERE   common_id NOT IN
            (
            SELECT  common_id
            FROM    table1 t1
            )
    

Если table1.common_id не является нулевым, все эти запросы семантически одинаковы.

Когда он равен нулю, NOT IN отличается, так как IN (и, следовательно, NOT IN) возвращает NULL, когда значение не соответствует чему-либо в списке, содержащем NULL.

Это может ввести в заблуждение, но может стать более очевидным, если вспомнить альтернативный синтаксис для этого:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

Результатом этого условия является булево произведение всех сравнений в списке. Конечно, одно значение NULL дает результат NULL, который также отображает весь результат NULL.

Мы никогда не можем однозначно сказать, что common_id не соответствует чему-либо из этого списка, так как по крайней мере одно из значений NULL.

Предположим, что у нас есть эти данные:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULL и NOT EXISTS вернется 3, NOT IN ничего не вернет (поскольку он всегда будет оценивать либо FALSE, либо NULL).

В MySQL, в случае столбца с непустым значением, LEFT JOIN / IS NULL и NOT IN немного (на несколько процентов) эффективнее, чем NOT EXISTS. Если столбец является нулевым, NOT EXISTS является наиболее эффективным (опять же, не так много).

В Oracle все три запроса дают одинаковые планы (a ANTI JOIN).

В SQL Server, NOT IN/NOT EXISTS более эффективны, так как LEFT JOIN / IS NULL не может быть оптимизирован для ANTI JOIN его оптимизатором.

В PostgreSQL, LEFT JOIN / IS NULL и NOT EXISTS более эффективны, чем NOT IN, они оптимизированы для ANTI JOIN, а NOT IN использует hashed subplan (или даже простой subplan if подзапрос слишком велик для хэша)

Ответ 2

Если вы хотите, чтобы мир был двузначным логическим местом, вы должны предотвратить случай с нулевым (третьим значением).

Не записывайте предложения IN, которые допускают значения NULL в списке. Отфильтруйте их!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)

Ответ 3

Таблица 1 или Таблица2 имеет некоторые значения null для common_id. Вместо этого используйте этот запрос:

select *
from Common
where common_id not in (select common_id from Table1 where common_id is not null)
and common_id not in (select common_id from Table2 where common_id is not null)

Ответ 4

Просто с головы...

select c.commonID, t1.commonID, t2.commonID
from Common c
     left outer join Table1 t1 on t1.commonID = c.commonID
     left outer join Table2 t2 on t2.commonID = c.commonID
where t1.commonID is null 
     and t2.commonID is null

Я провел несколько тестов, и здесь были мои результаты w.r.t. @patmortech answer и комментарии @rexem.

Если таблица Table1 или Table2 не индексируется в commonID, вы получаете сканирование таблицы, но запрос @patmortech по-прежнему в два раза быстрее (для главной таблицы строк 100K).

Если ни один из них не проиндексирован в commonID, вы получаете два сканирования таблицы, и разница незначительна.

Если оба индексируются в commonID, запрос "не существует" выполняется в 1/3 времени.

Ответ 5

SELECT T.common_id
  FROM Common T
       LEFT JOIN Table1 T1 ON T.common_id = T1.common_id
       LEFT JOIN Table2 T2 ON T.common_id = T2.common_id
 WHERE T1.common_id IS NULL
   AND T2.common_id IS NULL

Ответ 6

select *
from Common c
where not exists (select t1.commonid from table1 t1 where t1.commonid = c.commonid)
and not exists (select t2.commonid from table2 t2 where t2.commonid = c.commonid)

Ответ 7

Предположим, что эти значения для common_id:

Common - 1
Table1 - 2
Table2 - 3, null

Мы хотим, чтобы строка в Common возвращалась, потому что она не существует ни в одной из других таблиц. Однако, нуль бросает в обезьянном гаечном ключе.

С этими значениями запрос эквивалентен:

select *
from Common
where 1 not in (2)
and 1 not in (3, null)

Это эквивалентно:

select *
from Common
where not (1=2)
and not (1=3 or 1=null)

Здесь начинается проблема. При сравнении с нулевым значением ответ неизвестен. Таким образом, запрос сводится к

select *
from Common
where not (false)
and not (false or unkown)

ложно или неизвестно неизвестно:

select *
from Common
where true
and not (unknown)

true, а не unkown также не указано:

select *
from Common
where unknown

Условие where не возвращает записи, в которых результат не указан, поэтому мы не возвращаем записи.

Один из способов справиться с этим - использовать оператор exist, а не in. Exists никогда не возвращается unkown, потому что он работает с строками, а не с столбцами. (Строка либо существует, либо она отсутствует, ни одна из этой нулевой двусмысленности на уровне строк!)

select *
from Common
where not exists (select common_id from Table1 where common_id = Common.common_id)
and not exists (select common_id from Table2 where common_id = Common.common_id)

Ответ 8

это сработало для меня:)

выберите * из Common

где

common_id not in (выберите ISNULL (common_id, 'dummy-data') из таблицы 1)

и common_id не в (выберите ISNULL (common_id, 'dummy-data') из таблицы2)

Ответ 9

select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster