Как работают операторы SQL EXISTS?

88

Я пытаюсь изучить SQL, и мне трудно понять операторы EXISTS. Я наткнулся на эту цитату о "существует" и чего-то не понимаю:

Используя оператор exists, ваш подзапрос может вернуть ноль, одну или несколько строк, а условие просто проверяет, вернул ли подзапрос какие-либо строки. Если вы посмотрите на предложение select подзапроса, вы увидите, что он состоит из одного литерала (1); поскольку условию в содержащем запросе нужно только знать, сколько строк было возвращено, фактические данные, возвращенные подзапросом, не имеют значения.

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

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

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

Мне кажется, что нет никакой связи между внешним запросом и подзапросом.

Дэн
источник

Ответы:

98

Подумайте об этом так:

Для «каждой» строки из Suppliersпроверьте, существует ли в Orderтаблице «существует» строка , удовлетворяющая условию Suppliers.supplier_id(это происходит из текущей «строки» внешнего запроса) = Orders.supplier_id. Когда вы найдете первую совпадающую строку, остановитесь прямо здесь - результат WHERE EXISTSбыл удовлетворен.

Магическая связь между внешним запросом и подзапросом заключается в том факте, что он Supplier_idпередается из внешнего запроса в подзапрос для каждой оцениваемой строки.

Или, другими словами, подзапрос выполняется для каждой строки таблицы внешнего запроса.

Это НЕ похоже на то, что подзапрос выполняется в целом и получает «истина / ложь», а затем пытается сопоставить это условие «истина / ложь» с внешним запросом.

Sojin
источник
7
Благодарность! «Это НЕ похоже на подзапрос, выполняемый в целом и получающий« истина / ложь », а затем пытающийся сопоставить это условие« истина / ложь »с внешним запросом». это то, что действительно прояснило это для меня, я продолжаю думать, как работают подзапросы (и часто они работают), но то, что вы сказали, имеет смысл, потому что подзапрос полагается на внешний запрос и, следовательно, должен выполняться один раз в строке
Кларенс Лю
32

Мне кажется, что нет никакой связи между внешним запросом и подзапросом.

Как вы думаете, что делает предложение WHERE в примере EXISTS? Как вы пришли к такому выводу, если ссылки SUPPLIERS нет в предложениях FROM или JOIN в предложении EXISTS?

EXISTS оценивает значение ИСТИНА / ЛОЖЬ и завершает работу как ИСТИНА при первом совпадении критериев - поэтому может быть быстрее, чем 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, потому что будут дублироваться родительские записи, если с родителем связано более одной дочерней записи.

OMG Пони
источник
1
Я все еще что-то упускаю. Если он завершается при первом совпадении, как на выходе будут все результаты, где o.supplierid = s.supplierid? Разве вместо этого он не выводил бы просто первый результат?
Дэн
3
@Dan: EXISTSвыходы, возвращающие TRUE при первом совпадении - потому что поставщик существует хотя бы один раз в таблице ORDERS. Если вы хотите увидеть дублирование данных SUPPLIER из-за наличия более одного дочернего отношения в ORDERS, вам придется использовать JOIN. Но большинство из них не хотят этого дублирования, и выполнение 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 миллионов строк и почему?
Teja
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 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 СУЩЕСТВУЕТ

Допустим, мы хотим получить всех учеников, получивших 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

При выполнении запроса выше мы видим, что выбрана только строка Алисы:

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

Внешний запрос выбирает studentстолбцы строк, которые мы хотим вернуть клиенту. Однако предложение WHERE использует оператор EXISTS со связанным внутренним подзапросом.

Оператор EXISTS возвращает true, если подзапрос возвращает хотя бы одну запись, и false, если строка не выбрана. Механизм базы данных не должен полностью выполнять подзапрос. Если совпадает одна запись, оператор EXISTS возвращает true, и выбирается связанная другая строка запроса.

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

Влад Михалча
источник
Какой отличный ответ. Думаю, я не понял концепцию, потому что использовал неправильный пример. Работает EXISTтолько с коррелированным подзапросом? Я играл с запросом, содержащим только одну таблицу, например SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Я знаю, что то, что я написал, может быть достигнуто одним простым запросом WHERE, но я просто использовал его, чтобы понять EXISTS. Я получил все строки. Действительно ли это связано с тем, что я не использовал коррелированный подзапрос? Спасибо.
Боуэн Лю,
Это имеет смысл только для коррелированных подзапросов, поскольку вы хотите фильтровать записи внешнего запроса. В вашем случае внутренний запрос можно заменить на WHERE TRUE
Влад Михалча
Спасибо Влад. Это то, о чем я думал. Это просто странная идея, которая возникла, когда я возился с ней. Честно говоря, я не знал концепции коррелированного подзапроса. И теперь имеет смысл отфильтровывать строки внешнего запроса с помощью внутреннего запроса.
Боуэн Лю
0

EXISTS означает, что подзапрос возвращает хотя бы одну строку, это действительно так. В этом случае это коррелированный подзапрос, поскольку он проверяет поставщик_id внешней таблицы на поставщик_ид внутренней таблицы. Этот запрос фактически говорит:

ВЫБЕРИТЕ всех поставщиков. Для каждого идентификатора поставщика проверьте, существует ли заказ для этого поставщика. Если поставщик отсутствует в таблице заказов, удалите его из результатов.ВЕРНУТЬ всех поставщиков, у которых есть соответствующие строки в таблице заказов.

В этом случае вы можете сделать то же самое с INNER JOIN.

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

Комментарий пони правильный. Вам нужно будет выполнить группировку с этим объединением или выбрать отдельные в зависимости от того, какие данные вам нужны.

Дэвид Феллс
источник
4
Внутреннее соединение приведет к другим результатам, чем EXISTS, если с родительской записью связано несколько дочерних записей - они не идентичны.
OMG Ponies
Я думаю, что меня смущает то, что я читал, что подзапрос с EXISTS возвращает true или false; но это не может быть единственным, что он возвращается, верно? Возвращает ли подзапрос также всех «поставщиков, у которых есть соответствующие строки в таблице заказов»? Но если это так, как оператор EXISTS возвращает логический результат? Все, что я читаю в учебниках, говорит о том, что он возвращает только логический результат, поэтому мне трудно согласовать результат кода с тем, что мне сказали, он возвращает.
Дэн
Читайте EXISTS как функцию ... EXISTS (набор результатов). Затем функция 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