НЕ ВІД НЕ ІСНУЄ


538

Який із цих запитів швидше?

НЕ Є:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Northwind..[Order Details] od 
    WHERE p.ProductId = od.ProductId)

Або НЕ:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])

План виконання запитів говорить, що вони обидва роблять те саме. Якщо це так, яка рекомендована форма?

Це засновано на базі даних NorthWind.

[Редагувати]

Щойно знайшла цю корисну статтю: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Я думаю, я буду дотримуватися НЕ ІСНУЮТЬ.


3
ви спробували план за допомогою лівого з'єднання, де є нульовим?
Себас

1
НЕ В І НЕ ІСНУЄТЬСЯ не тотожні. Перегляньте посилання на різницю між ними: weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Ameya

2
Цікаво, чи відрізняються Бази даних, але в моєму останньому орієнтирі проти PostgreSQL цей NOT INзапит: SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)майже в 30 разів швидше, ніж цей NOT EXISTS:SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
Phương Nguyễn


1
@rcdmk Ви перевіряли дату в питаннях?
ilitirit

Відповіді:


693

Я завжди за замовчуванням NOT EXISTS.

Плани виконання можуть бути однаковими в даний момент , але якщо один стовпець змінений в майбутньому , щоб NULLвляет NOT INверсію потрібно буде робити більше роботи (навіть якщо немає NULLs не власне присутній в даних) і семантика NOT INякщо NULLs є справжній навряд чи будуть такими, яких ти хочеш.

Коли ні Products.ProductIDабо не [Order Details].ProductIDдозволяють NULLs NOT IN, трактуватимуться ідентично наступному запиту.

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId) 

Точний план може відрізнятися, але для мого прикладу я отримую наступне.

Ні NULL

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

/*Not valid syntax but better reflects the plan*/ 
SELECT p.ProductID,
       p.ProductName
FROM   Products p
       LEFT ANTI SEMI JOIN [Order Details] od
         ON p.ProductId = od.ProductId 

Якщо [Order Details].ProductIDє NULL-able, то запит стає

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL) 

Причиною цього є те, що правильна семантика, якщо вона [Order Details]містить якусь NULL ProductIds, не дає результатів. Перегляньте додаткову котушку анти-напівз’єднання та підрахунку рядків, щоб переконатися, що додано до плану.

Один НУЛЬ

Якщо Products.ProductIDтакож буде змінено, щоб стати NULLпридатним для використання, то запит стає

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL)
       AND NOT EXISTS (SELECT *
                       FROM   (SELECT TOP 1 *
                               FROM   [Order Details]) S
                       WHERE  p.ProductID IS NULL) 

Причиною цього є те, що в результатах NULL Products.ProductIdне повинно бути повернуто, за винятком випадків, коли NOT INпідзапит повинен взагалі не повертати результатів (тобто [Order Details]таблиця порожня). У такому випадку воно повинно. У плані для моїх зразкових даних це реалізується шляхом додавання ще одного антиполу приєднання, як показано нижче.

Обидва NULL

Ефект цього показаний у публікації блогу, яку вже пов’язав Баклі . У наведеному прикладі кількість логічних читань зростає приблизно від 400 до 500 000.

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

Однак це не єдиний можливий план виконання для стовпця NOT INa NULL-able. У цій статті показано ще один запит до AdventureWorks2008бази даних.

Для стовпця " NOT INon NOT NULL" або " NOT EXISTSпроти" або "nullable" (стовпчик), який не змінюється, він дає наступний план.

Не існує

Коли стовпець змінюється на NULL-able, NOT INтепер виглядає план

Not In - Null

Це додає додаткового внутрішнього оператора приєднання до плану. Цей апарат пояснюється тут . Це все для перетворення попереднього єдиного корельованого індексу на Sales.SalesOrderDetail.ProductID = <correlated_product_id>два запити на зовнішній рядок. Додатковий увімкнено WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Оскільки це відбувається під антиполовинним об'єднанням, якщо той повертає будь-які рядки, друга спроба не відбудеться. Однак якщо Sales.SalesOrderDetailне містить NULL ProductIDs, це подвоїть кількість необхідних операцій пошуку.


4
Чи можу я запитати, як ви отримуєте графік профілювання, як показано?
xis

5
@xis Це плани виконання, відкриті в провіднику плану SQL Sentry. Ви також можете графічно переглянути плани виконання у SSMS.
Мартін Сміт

Я ціную це з єдиної причини, яка: NOT EXISTSфункціонує так, як я очікую, NOT INщоб функціонувати (що, це не так).
levininja

Якщо НЕ ІСНУЄ, я намагаюся використовувати SELECT 1, такий як НЕ ІСНУЄТЬСЯ (ВИБІР 1 З деяких таблиць, де щось), так що база даних насправді не потребує повернення стовпців з диска. Використання EXPLAIN для визначення того, чи впливає це на ваш випадок, ймовірно, є хорошою ідеєю.
Mayur Patel

4
@Mayur У SQL Server цього не потрібно. stackoverflow.com/questions/1597442/…
Мартін Сміт

84

Також майте на увазі, що NOT IN не еквівалентний NOT NOT EXISTS, коли справа доходить до нуля.

Ця публікація це дуже добре пояснює

http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/

Коли підзапит повертає навіть один нуль, NOT IN не збігатиметься з жодними рядками.

Причину цього можна знайти, переглянувши деталі того, що насправді означає операція NOT IN.

Скажімо, для ілюстрації, що у таблиці під назвою t є 4 рядки, є стовпець під назвою ID із значеннями 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

еквівалентно

WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1)
AND SomeValue != (SELECT AVal FROM t WHERE ID=2)
AND SomeValue != (SELECT AVal FROM t WHERE ID=3)
AND SomeValue != (SELECT AVal FROM t WHERE ID=4)

Скажемо далі, що AVal - NULL, де ID = 4. Отже,! = Порівняння повертає НЕВІДОМО. Таблиця логічної істинності І говорить про те, що НЕВІДОМО і ПРАВИЛЬНО НЕВІДОМЛЕНО, НЕВІДОМЛЕНО і ЛІЖНЕ ЛЖЕ. Немає значення, яке може бути ТА БУДЕ НЕЗАЄМО, щоб отримати результат ІСТИНА

Отже, якщо будь-який рядок цього підзапита повертає NULL, весь оператор NOT IN буде оцінювати як FALSE або NULL, і записи не повертаються


24

Якщо планувальник виконання скаже, що вони однакові, вони однакові. Використовуйте те, що зробить ваш намір більш очевидним - у цьому випадку другим.


3
Час планування виконання може бути однаковим, але результати виконання можуть відрізнятися, тому є різниця. NOT IN призведе до несподіваних результатів, якщо у вашому наборі даних є NULL (див. Відповідь Баклі). Найкраще використовувати НЕ ІСНУЮЩЕ як за замовчуванням.
нанонерд

15

Власне, я вважаю, що це було б найшвидше:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null

2
Може не бути найшвидшим, коли оптимізатор робить свою роботу, але, безумовно, буде швидше, коли це не так.
Кейд Ру

2
Можливо, він спростив свій запит і на цю посаду
Kip

1
Погодьтеся, ліве зовнішнє з'єднання часто швидше, ніж підзапит.
HLGEM

7
@HLGEM Не погоджуюся. На мій досвід, найкращим випадком для LOJ є те, що вони однакові, і SQL Server перетворює LOJ в антиполу. У гіршому випадку SQL Server LEFT приєднується до всього і фільтрує NULL, після чого може бути набагато неефективнішим. Приклад цього в нижній частині цієї статті
Мартін Сміт

12

У мене є таблиця, що містить близько 120 000 записів, і потрібно вибрати лише ті, яких не існує (збігається зі стовпцем вархара) у чотирьох інших таблицях з кількістю рядків приблизно 1500, 4000, 40000, 200. Усі задіяні таблиці мають унікальний індекс на відповідну Varcharколонку.

NOT INзайняв близько 10 хвилин, NOT EXISTSзайняв 4 секунди.

У мене є рекурсивний запит, який, можливо, мав би якийсь не налаштований розділ, який міг би сприяти 10 хвилин, але інший варіант, який займає 4 секунди, пояснює, принаймні для мене, що NOT EXISTSце набагато краще або, принаймні, це INі EXISTSне зовсім те саме і завжди варто перевірити, перш ніж продовжувати код.


8

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

NOT INслід віддати перевагу, якщо ви тестуєте кілька рядків у зовнішньому виділенні. Підзапит в NOT INоператорі може бути оцінений на початку виконання, і тимчасова таблиця може бути перевірена щодо кожного значення у зовнішньому виділенні, а не повторного запуску підселектора щоразу, як цього потрібно з NOT EXISTSоператором.

Якщо підзапит повинен бути співвіднесений із зовнішнім вибором, то NOT EXISTSможе бути кращим, оскільки оптимізатор може виявити спрощення, що перешкоджає створенню будь-яких тимчасових таблиць для виконання тієї ж функції.


6

Я використовував

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

і виявив, що він дає неправильні результати (під помилкою я маю на увазі відсутні результати). Оскільки в TABLE2.Col1 був NULL.

Під час зміни запиту на

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

дав мені правильні результати.

З тих пір я почав використовувати НЕ ІСНУЮТЬ скрізь.


5

Вони дуже схожі, але насправді не однакові.

З точки зору ефективності, я виявив, що ліве з'єднання є null твердженням більш ефективним (коли потрібно вибрати ряд рядків)


2

Якщо оптимізатор каже, що вони однакові, то врахуйте людський фактор. Я вважаю за краще НЕ ІСНУЮТЬ :)


1

Це дуже гарне запитання, тому я вирішив написати дуже детальну статтю на цю тему у своєму блозі.

Модель таблиці баз даних

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

Таблиці SQL EXISTS

studentТаблиця є батьком, і student_gradeє дочірньою таблицею , так як він має student_id стовпець зовнішнього ключа , який посилається Ід стовпець первинного ключа в таблиці студента.

student tableМістить наступні два записи:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

А в student_gradeтаблиці зберігаються оцінки, які студенти отримали:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTS

Скажімо, ми хочемо отримати всіх учнів, які отримали 10 клас у класі математики.

Якщо нас цікавить лише ідентифікатор студента, ми можемо запустити такий запит:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Але програма зацікавлена ​​у тому, щоб відобразити повне ім’я student, а не лише ідентифікатор, тому нам потрібна інформація також із studentтаблиці.

Для фільтрації studentзаписів, які мають 10-й клас з математики, ми можемо використовувати оператор EXISTS SQL, наприклад, такий:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Запускаючи запит вище, ми бачимо, що вибрано лише рядок Alice:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Зовнішній запит вибирає studentстовпці рядків, які ми зацікавлені повернути клієнту. Однак у пункті WHERE використовується оператор EXISTS із пов'язаним внутрішнім підзапитом.

Оператор EXISTS повертає true, якщо підзапит повертає щонайменше один запис, а false, якщо не вибрано рядок. Двигун бази даних не повинен повністю виконувати підзапит. Якщо один запис збігається, оператор EXISTS повертає true, і вибирається пов'язаний інший рядок запиту.

Внутрішній підзапит має співвідношення, оскільки стовпець student_id student_gradeтаблиці співставляється зі стовпцем id зовнішньої таблиці учнів.

SQL НЕ існує

Розглянемо, що ми хочемо вибрати всіх учнів, які не мають балів нижче 9. Для цього ми можемо використовувати НЕ ІСНУЄТЬСЯ, що заперечує логіку оператора EXISTS.

Таким чином, оператор NOT EXISTS повертає true, якщо в нижньому підзапиті не повертається жодна запис. Однак якщо один запис відповідає внутрішньому підзапиту, оператор NOT EXISTS поверне помилковий, і виконання підзапиту може бути зупинено.

Щоб зіставити всі записи студентів, які не мають асоційованого student_grade зі значенням нижче 9, ми можемо запустити наступний SQL-запит:

SELECT
    id, first_name, last_name
FROM
    student
WHERE NOT EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade < 9
)
ORDER BY id

Запускаючи запит вище, ми бачимо, що відповідає лише запис Alice:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Отже, перевага використання операторів SQL EXISTS і NOT EXISTS полягає в тому, що внутрішнє виконання підзапиту можна зупинити, доки буде знайдена відповідна запис.


-1

Це залежить..

SELECT x.col
FROM big_table x
WHERE x.key IN( SELECT key FROM really_big_table );

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

Але, в залежності від оптимізатора СУБД, це не може бути різним.

Як приклад, коли EXISTS краще

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria

1
INі EXISTS отримайте той самий план у SQL Server . Питання про все-таки NOT INпроти NOT EXISTS.
Мартін Сміт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.