Як працюють оператори SQL EXISTS?


88

Я намагаюся вивчити SQL, і мені важко зрозуміти твердження EXISTS. Я натрапив на цю цитату про "існує" і чогось не розумію:

Використовуючи оператор існує, ваш підзапит може повернути нуль, один або багато рядків, а умова просто перевіряє, чи повертав підзапит будь-які рядки. Якщо ви подивитесь на пункт вибору підзапиту, то побачите, що він складається з одного літералу (1); оскільки умові у запиті, що містить, потрібно лише знати, скільки рядків було повернуто, фактичні дані, які повертається підзапит, не мають значення.

Я не розумію, як зовнішній запит знає, який рядок перевіряє підзапит? Наприклад:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

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

Мені здається, що не існує взаємозв'язку між зовнішнім запитом та підзапитом.

Відповіді:


98

Подумайте про це так:

Для "кожного" рядка з Suppliers, перевірте, чи "існує" рядок у Orderтаблиці, який відповідає умові Suppliers.supplier_id(це походить із поточного "рядка" зовнішнього запиту) = Orders.supplier_id. Коли ви знайдете перший відповідний рядок, зупиніться прямо там - це WHERE EXISTSбуло задоволено.

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

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

Це НЕ так, як підзапит виконується в цілому і отримує 'true / false', а потім намагається зіставити цю умову 'true / false' із зовнішнім запитом.


7
Дякую! "Це НЕ як підзапит, який виконується в цілому і отримує" true / false ", а потім намагається зіставити цю умову" true / false "із externalquery." це те, що насправді зрозуміло для мене, я продовжую думати, що саме так працюють підзапити (і багато разів вони це роблять), але те, що ви сказали, має сенс, оскільки підзапит спирається на зовнішній запит і тому повинен виконуватися один раз на рядок
Кларенс Лю

32

Мені здається, що не існує взаємозв'язку між зовнішнім запитом та підзапитом.

Як ви думаєте, що робить речення WHERE у прикладі EXISTS? Як ви прийшли до такого висновку, коли посилання SUPPLIERS відсутнє в реченнях FROM або JOIN у реченні EXISTS?

EXISTS оцінює значення TRUE / FALSE і виходить як TRUE при першому збігу критеріїв - ось чому це може бути швидше, ніж IN. Також майте на увазі, що речення SELECT у EXISTS ігнорується - IE:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

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

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


1
Мені ще чогось не вистачає. Якщо він виходить у першому збігу, як результат закінчується тим, що всі результати, де o.supplierid = s.supplierid? Хіба це не просто виведе перший результат замість цього?
Ден

3
@Dan: Виходить EXISTS, повертаючи TRUE на першому збігу - оскільки постачальник існує принаймні один раз у таблиці ORDERS. Якщо ви хотіли побачити дублювання даних ПОСТАЧАЛЬНИКА через те, що у ЗАМОВЛЕННЯХ є більше ніж одна дочірня взаємозв'язок, вам доведеться скористатися ПРИЄДНАЙТЕСЯ. Але більшість не хочуть такого дублювання, і запуск GROUP BY / DISTINCT може додати накладні витрати на запит. EXISTSє ефективнішим, ніж SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...на SQL Server, останнім часом не тестував на Oracle або MySQL.
OMG Ponies

У мене виникло запитання, чи виконується збіг для кожного запису, який ВИБРАНО у зовнішньому запиті. Як і в тому випадку, ми отримуємо замовлення 5 разів, якщо з постачальників вибрано 5 рядків.
Рахул Кадукар

24

Ви можете виробляти однакові результати , використовуючи або JOIN, EXISTS, INабо INTERSECT:

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o

1
чудова відповідь, але також пам’ятайте, що краще не використовувати, щоб уникнути кореляції
Флоріан Фреліх

1
Як ви думаєте, який запит буде виконуватися швидше, якщо постачальники мають 10 мільйонів рядків, а замовлення мають 100 мільйонів рядків, і чому?
Тея

7

Якщо у вас було речення where, яке виглядало так:

WHERE id in (25,26,27) -- and so on

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

Коли речення where таке:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

це просто означає: повернути рядки, які мають наявний запис у таблиці замовлень з однаковим ідентифікатором.


2

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

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

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

SQL ІСНУЄ таблиці

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 ІСНУЄ

Скажімо, ми хочемо отримати всіх учнів, які отримали 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, ми можемо використовувати оператор 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

При запуску запиту вище ми бачимо, що вибрано лише рядок Аліса:

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

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

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

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


Яка чудова відповідь. Я думаю, що я не зрозумів концепції, оскільки використовував неправильний приклад. Чи EXISTпрацює лише корельований підзапит? Я бавився із запитом, що містить лише 1 таблицю, наприклад SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Я знаю, що написаного мною можна досягти одним простим запитом WHERE, але я просто використовував його для розуміння ІСНУЄ. Я отримав усі ряди. Це справді пов’язано з тим, що я не використовував корельований підзапит? Дякую.
Боуен Лю

Це має сенс лише для корельованих підзапитів, оскільки ви хочете фільтрувати записи зовнішнього запиту. У вашому випадку внутрішній запит можна замінити ДЕ ПРАВДА
Влад Міхалча

Дякую Владе. Це те, що я думав. Це просто дивна ідея, яка виникла, коли я возився з нею. Я, чесно кажучи, не знав поняття корельованого підзапиту. І тепер набагато більше сенсу відфільтровувати рядки зовнішнього запиту за допомогою внутрішнього.
Боуен Лю

0

ІСНУЄ означає, що підзапит повертає принаймні один рядок, це справді все. У цьому випадку це корельований підзапит, оскільки він перевіряє ідентифікатор постачальника зовнішньої таблиці до ідентифікатора постачальника внутрішньої таблиці. Цей запит фактично говорить:

ВИБЕРІТЬ усіх постачальників Для кожного ідентифікатора постачальника перевірте, чи існує замовлення для цього постачальника. Якщо постачальника немає в таблиці замовлень, видаліть постачальника з результатів.

Ви могли б зробити те саме в цьому випадку за допомогою ВНУТРІШНЬОГО ПРИЄДНАННЯ.

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

Коментар поні вірний. Вам потрібно буде зробити групування з цим об’єднанням або вибрати окреме залежно від даних, які вам потрібні.


4
Внутрішнє приєднання дасть інші результати, ніж ІСНУЄ, якщо більше ніж один дочірній запис пов'язаний з батьком - вони не ідентичні.
OMG Ponies

Я думаю, що моя плутанина може бути в тому, що я прочитав, що підзапит із ІСНУЄ повертає true або false; але це не може бути єдиним, що воно повертається, так? Чи повертає підзапит також усіх "постачальників, які мають відповідні рядки в таблиці замовлень"? Але якщо це так, то як оператор EXISTS повертає логічний результат? Усе, що я читаю в підручниках, говорить, що воно повертає лише логічний результат, тому мені важко узгодити результат коду з тим, що мені кажуть, що він повертає.
Ден,

Читайте EXISTS як функцію ... EXISTS (resultset). Потім функція EXISTS повертає true, якщо набір результатів має рядки, false, якщо він порожній. Це в основному це.
Девід Феллс,

3
@Dan, враховуйте, що EXISTS () логічно обчислюється для кожного вихідного рядка незалежно - це не одне значення для всього запиту.
Арво

-1

Те, що ви описуєте, - це так званий запит із корельованим підзапитом .

(Загалом) це те, чого вам слід спробувати уникнути, написавши запит, використовуючи замість цього об’єднання:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

Оскільки інакше підзапит буде виконаний для кожного рядка зовнішнього запиту.


2
Ці два рішення не є рівнозначними. JOIN дає інший результат, ніж підзапит EXISTS, якщо в декількох рядках ordersвідповідає умові приєднання.
a_horse_with_no_name

1
дякую за альтернативне рішення. але ви припускаєте, що якщо вам буде запропоновано варіант між корельованим підзапитом та об’єднанням, я повинен піти з об’єднанням, оскільки це ефективніше?
sunny_dev
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.