SQL "select where not in ququery" не дає результатів


130

Відмова: Я вирішив проблему (я думаю), але хотів додати цю проблему до переповнення стека, оскільки я не міг (легко) її знайти де завгодно. Також у когось може бути краща відповідь, ніж я.

У мене є база даних, де одна таблиця "Загальна" посилається на кілька інших таблиць. Я хотів побачити, які записи в загальній таблиці були сиротами (тобто не мали посилань на жодну з інших таблиць).

Я запустив цей запит:

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 досить добре відповідає на питання ЧОМУ.
Ручан

Відповіді:


234

Оновлення:

Ці статті в моєму блозі більш детально описують відмінності між методами:


Існує три способи зробити такий запит:

  • 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коли значення не відповідає нічого в списку, що містить a 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випадку, якщо на стовпці, що не зводиться на нуль, LEFT JOIN / IS NULLі NOT INвони трохи (на кілька відсотків) ефективніші, ніж NOT EXISTS. Якщо стовпчик є нульовим,NOT EXISTS найефективніший (знову ж таки, не сильно).

В Oracle, все три запити дають однакові плани (AN 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якщо підзапит занадто великий для хешу)


8
Чудова відповідь! Дякую!
StevenMcD

це приголомшливо і дуже корисно
kavun

1
+1, тому що через чотири з половиною роки ця відповідь допомогла мені вирішити проблему, яка мене спіткала!
Carson63000

@ Carson63000 Snap! Я подумав, що я зійшов з розуму, перш ніж побачив цю відповідь
Боббі

1
@IstiaqueAhmed: NOT EXISTSоцінює TRUE, якщо запит всередині нього повертає будь-які рядки. SELECT NULLможе також бути SELECT *або SELECT 1або що - небудь ще, NOT EXISTSпредикат не дивиться на значеннях Ряди, тільки вважає їх.
Quassnoi

36

Якщо ви хочете, щоб світ був двозначним булевим місцем, ви повинні запобігти нульовому (третьому значенню) випадку самостійно.

Не пишіть пропозиції IN, які дозволяють нулю в списку. Відфільтруйте їх!

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

6
нулі в списку in-clause є загальною причиною відсутності результатів запитів.
Емі Б

"Якщо порівнювати з нулем, відповідь невідома" - з відповіді @Jeremy Stein. З common_id not in, ми все ще можемо мати common_idцінність, яка є NULL. Тож чи не існує проблеми з отриманням результатів?
Істіак Ахмед

5

Таблиця1 або 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)

1
Що робити, якщо в одній таблиці є дані, а не в іншій? Ви хочете там "і" чи "чи"?
Філіп Келлі

1
Я шукаю записи, на які не посилається жодна таблиця, тому я хочу І. Я уточню питання.
Джеремі Штейн

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)

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

Я провів кілька тестів, і ось мої результати відповіді wrt @ patmortech та коментарі @ rexem.

Якщо або Table1, або Table2 не індексується на commonID, ви отримуєте сканування таблиці, але запит @ patmortech все ще вдвічі швидше (для основної таблиці рядків 100K).

Якщо жоден з них не індексується на commonID, ви отримуєте два сканування таблиці, і різниця незначна.

Якщо обидва індексуються на commonID, запит "не існує" запускається в 1/3 разу.


1
Це має бути AND у пункті де. Інакше це працює.
Джеремі Штейн

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

1
Цей підхід гірше, якщо використання NOT EXISTS - результат з'єднання отримує більше рядків, ніж потрібно, тоді результати порівняно для стовпців є нульовими. Це працює, але продуктивність буде не такою хорошою - можливо, гіршою, ніж використання IN з корельованими підзапитами.
OMG Ponies

3

Припустимо, ці значення для common_id:

Common - 1
Table1 - 2
Table2 - 3, null

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

З цими значеннями запит еквівалентний:

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

Умови де не повертаються записи, де результат невідомий, тому ми не отримуємо жодних записів.

Один із способів вирішити це - використовувати існуючий оператор, а не дюйм. Існуючі ніколи не повертаються непознаними, оскільки він працює на рядах, а не на стовпцях. (Рядок або існує, або його немає; жодна з цих нульових неоднозначностей на рівні рядків!)

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, 'фіктивний дані') з таблиці1)

та common_id не входить (виберіть ISNULL (common_id, 'фіктивний дані') з Таблиці2)


@marlar, підзапити завжди повертають 1 або 0, а не список значень. То як буде NOT INвиступати там?
Істіаке Ахмед

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

0

У мене був приклад, коли я дивився вгору і тому, що одна таблиця містила значення як подвійне, інша як рядок, вони не відповідали б (або не збігаються без відступу). Але тільки НЕ . Як SELECT ... IN ... працював. Дивно, але я думав, що поділюсь у випадку, якщо хтось інший зіткнеться з цим простим виправленням.


0

Дотримуйтесь наведеного нижче прикладу, щоб зрозуміти вищевказану тему:

Також ви можете відвідати наступне посилання, щоб дізнатися про антиприєднання

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 не відображаються у таблиці, будь ласка, голі зі мною.
Раджеш Саркар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.