SQL «выберите, где нет в подзапросе» не возвращает результатов

131

Отказ от ответственности: я понял проблему (я думаю), но я хотел добавить эту проблему в 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, если это важно.)

Джереми Штайн
источник
Этот stackoverflow.com/a/129152/1667619 довольно хорошо отвечает на вопрос ПОЧЕМУ.
Ruchan

Ответы:

236

Обновить:

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


Есть три способа выполнить такой запрос:

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

Когда он допускает значение NULL, 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 вернет (так как всегда будет оцениваться как или ).FALSENULL

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

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

In 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если подзапрос является слишком большим , чтобы хэш)

Quassnoi
источник
8
Отличный ответ! Спасибо!
StevenMcD
это потрясающе и очень полезно
Кавун
1
+1 потому что четыре с половиной года спустя этот ответ помог мне с проблемой, которая поставила меня в тупик!
Carson63000
@ Carson63000 Snap! Я думал, что схожу с ума, прежде чем увидел этот ответ
Бобби
1
@IstiaqueAhmed: NOT EXISTSоценивается как ИСТИНА, если запрос внутри него возвращает какие-либо строки. SELECT NULLможет быть SELECT *или SELECT 1что-то еще, NOT EXISTSпредикат не смотрит на значения строк, а только подсчитывает их.
Quassnoi
36

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

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

common_id not in
(
  select common_id from Table1
  where common_id is not null
)
Эми Б
источник
6
Нулевые значения в списке in-clause-list являются частой причиной отсутствия результатов запроса.
Amy B,
«При сравнении с нулем ответ неизвестен» - из ответа @Jeremy Stein. От common_id not in, мы все еще можем иметь common_idзначение, которое есть NULL. Так не остается ли проблема отсутствия результатов?
Istiaque Ahmed
5

Таблица1 или Таблица2 имеет несколько нулевых значений для 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)
Джереми Штайн
источник
1
Что делать, если в одной таблице есть данные, а в другой нет? Вы хотите "и" или "или" там?
Филип Келли,
1
Я ищу записи, на которые нет ссылок ни в одной таблице, поэтому я хочу AND. Уточню вопрос.
Джереми Штайн,
4
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)
patmortech
источник
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

Я провел несколько тестов, и вот мои результаты с ответом @ patmortech и комментариями @rexem.

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

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

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

Остин Салонен
источник
1
Это должно быть AND в предложении where. В противном случае это работает.
Джереми Стейн,
1
изменено в соответствии с вашим комментарием. «Или» выбирает сирот в любой таблице.
Остин Салонен,
1
Так-то лучше. Кстати, есть ли причина, по которой я должен использовать внешние соединения, а не подзапрос?
Джереми Штайн,
3
Читаемость - первостепенная. Я подозреваю, что будет сгенерирован лучший план выполнения, но без плана запроса я не могу подтвердить.
Остин Салонен,
2
Этот подход хуже, чем использование НЕ СУЩЕСТВУЕТ - объединение приводит к получению большего количества строк, чем необходимо, а затем результаты, сравниваемые для столбцов, являются нулевыми. И NOT EXISTS более читабелен для загрузки.
OMG Ponies
3
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
Manji
источник
1
Этот подход хуже, чем использование НЕ СУЩЕСТВУЕТ - объединение приводит к получению большего количества строк, чем необходимо, а затем результаты, сравниваемые для столбцов, являются нулевыми. Это работает, но производительность будет не такой хорошей - возможно, хуже, чем при использовании IN с коррелированными подзапросами.
OMG Ponies
3

Предположим, эти значения для 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 и not unkown также неизвестно:

select *
from Common
where unknown

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

Один из способов справиться с этим - использовать оператор exists, а не 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)
Джереми Штайн
источник
2

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

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

где

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

и common_id не в (выберите ISNULL (common_id, 'dummy-data') из Table2)

дугообразных
источник
@marlar, подзапросы всегда возвращают 1 или 0, а не список значений. Так как же там NOT INвыступят?
Istiaque Ahmed
0
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster
Донга Джайеш
источник
0

У меня был пример, когда я искал, и поскольку одна таблица содержала значение как double, а другая как строку, они не совпадали (или не соответствовали без приведения). Но только НЕ . Как SELECT ... IN ... работал. Странно, но подумал, что поделюсь, если кто-нибудь еще столкнется с этим простым исправлением.

ransems
источник
0

Следуйте приведенному ниже примеру, чтобы понять тему выше:

Также вы можете перейти по следующей ссылке, чтобы узнать Anti-присоединиться

select department_name,department_id from hr.departments dep
where not exists 
    (select 1 from hr.employees emp
    where emp.department_id=dep.department_id
    )
order by dep.department_name;
DEPARTMENT_NAME DEPARTMENT_ID
Benefits    160
Construction    180
Contracting 190
.......

Но если мы используем NOT INв этом случае, мы не получим никаких данных.

select Department_name,department_id from hr.departments dep 
where department_id not in (select department_id from hr.employees );

данные не найдены

Это происходит, поскольку ( select department_id from hr.employees) возвращает нулевое значение, и весь запрос оценивается как ложный. Мы можем увидеть это, если немного изменим SQL, как показано ниже, и обработаем нулевые значения с помощью функции NVL.

select Department_name,department_id from hr.departments dep 
where department_id not in (select NVL(department_id,0) from hr.employees )

Теперь получаем данные:

DEPARTMENT_NAME DEPARTMENT_ID
Treasury    120
Corporate Tax   130
Control And Credit  140
Shareholder Services    150
Benefits    160
....

Мы снова получаем данные, поскольку обработали нулевое значение с помощью функции NVL.

Раджеш Саркар
источник
Результаты SQl не отображаются в табличной форме, пожалуйста, поделитесь со мной.
Раджеш Саркар