SQL "выбрать, где нет в подзапросе" не возвращает результатов - PullRequest
115 голосов
/ 10 сентября 2009

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

У меня есть база данных, где на одну таблицу "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, если это имеет значение.)

Ответы [ 9 ]

209 голосов
/ 11 сентября 2009

Обновление:

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


Есть три способа сделать такой запрос:

  • 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 не имеет значения nullable, все эти запросы семантически совпадают.

Когда он обнуляем, 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, в случае столбца без значения NULL, LEFT JOIN / IS NULL и NOT IN немного (на несколько процентов) более эффективны, чем NOT EXISTS. Если столбец обнуляем, NOT EXISTS является наиболее эффективным (опять же, не очень).

В Oracle все три запроса дают одинаковые планы (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, если подзапрос слишком велик для хэширования)

34 голосов
/ 10 сентября 2009

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

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

common_id not in
(
  select common_id from Table1
  where common_id is not null
)
5 голосов
/ 10 сентября 2009

Table1 или Table2 имеют некоторые нулевые значения для 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 голосов
/ 10 сентября 2009
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)
3 голосов
/ 11 сентября 2009

Давайте предположим, что эти значения для 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)

верно и не неизвестно, также неизвестно:

select *
from Common
where unknown

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

Один из способов справиться с этим - использовать оператор существующие, а не 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)
3 голосов
/ 10 сентября 2009
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
3 голосов
/ 10 сентября 2009

Просто с макушки головы ...

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 и комментарии @ rexem.

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

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

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

2 голосов
/ 08 апреля 2013

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

выберите * из общего

, где

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

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

0 голосов
/ 10 июня 2013
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...