Специальный оператор EXISTS используется для проверки результата работы подза­проса. Это позволяет выполнить внешний запрос, но только тогда, когда проверка EXISTS возвращает значение "истина".
Шесть одних и полдюжины других
Как и с операторами сравнения, при работе с операторами IN для инвертирования полученных результатов используется ключевое слово NOT, благодаря чему опреде­ляются записи, которые были получены подзапросом и не вошли в результирующий набор. Для того чтобы показать это, перепишем предыдущий запрос
SELECT cname FROM clients WHERE cid IN (SELECT cid FROM branches WHERE bid IN (SELECT bid FROM branches_services AS bs, services AS s WHERE bs.sid = s.sid GROUP BY bid HAVING SUM(sfee) >= 2000));
в следующий эквивалент, возвращающий аналогичный результирующий набор:
SELECT cname FROM clients WHERE cid IN (SELECT cid FROM branches WHERE bid NOT IN (SELECT bid FROM branches_services AS bs, service AS s WHERE bs.sid = s.sid GROUP BY bid HAVING SUM(sfee) < 2000));
Вот простенький пример: Листинг 11.25.
mysql> SELECT * FROM clients WHERE EXISTS (SELECT bid FROM branches_services GROUP BY bid HAVING COUNT(sid) >= 5);
Empty set (0.17 sec)
В этом случае из-за того, что подзапрос возвращает пустой результирующий набор - филиалы, которые пользуют пятью и более услугами, не зафиксированы - проверка EXISTS возвращает значение "ложь", и, следовательно, внешний запрос не выполняется.
А теперь посмотрим, что произойдет, если предыдущий подзапрос немного видоиз­менить, чтобы он возвратил хоть какой-то результат.
Подзапросы и оператор exists
Подзапросы и оператор exists
В этом случае из-за того, что некоторые филиалы пользуются четырьмя и более услу­гами, внутренний запрос возвратит результирующий набор, содержащий как минимум одну строку, тест EXISTS возвратит значение "1101", а это значит, что внешний запрос будет выполнен.
Необходимо заметить, что результирующий набор этого внешнего запроса не пред­ставляет собой перечень заказчиков, пользующихся не менее чем четырьмя услугами. Скорее всего, это можно назвать списком всех заказчиков, который возвращается только благодаря тому, что внутренний запрос сработал и выдал какой-то (неважно какой) ре­зультирующий набор. В предыдущем примере результирующий набор, полученный внутренним запросом, сам по себе ничего не означает. Совершенно аналогичный резуль­тат можно получить, выполнив следующий запрос.
Подзапросы и оператор exists
Оператор EXISTS чаще всего используется в конструкциях, которые SQL-гуру назы­вают внешними ссылками.
Внешние ссылки
Чаще всего, если подзапрос дает один и тот же результат при каждом запуске, имеет смысл запустить запрос один раз и использовать один и тот же результирующий набор при тестировании каждой записи, сгенерированной в главном запросе. Однако иногда возможны ситуации, когда подзапрос использует поле из главного запроса в своем пред­ложении. Такая ссылка из подзапроса на поле из внешнего запроса называется внешней ссылкой, и такой подзапрос называется связанным подзапросом, т.к. он связан с результи­рующим набором одного включающего его запросы (более).
Если в подзапросе появляется внешняя ссылка, MySQL запускает подзапрос для ка­ждой записи, полученной для внешнего запроса, а значит, подзапрос срабатывает столько раз, сколько записей будет получено в результирующем наборе внешнего запроса. В таком случае оператор EXISTS оказывается очень полезным для отброса определенных записей из окончательного результирующего набора.
Этот момент поможет прояснить пример. Рассмотрим предыдущий пример, но уже с использованием внешней ссылки для того, чтобы сделать результирующий набор более полезным.
Подзапросы и оператор exists
Себе дороже
Внешние ссылки помогают понять одну интересную проблему оптимизации за­просов. В случае применения внешней ссылки, MySQL должен запустить внутреннюю ссылку для каждой записи, полученной внешним запросом, т.к. во внешних ссылках используется результирующий набор внешнего запроса. Это приводит к увеличению нагрузки на сервер, а значит, к деградации производительности сервера. По этой причине следует избегать использования внешних ссылок по мере возможности, применяя другие способы объединения данных (например, это может быть самообъе­динение или объединение UNION), благодаря которым обычно уменьшаются наклад­ные расходы по времени выполнения и использованию ресурсов.
Рассмотрим связанный подзапрос из предыдущего примера:
SELECT bid, bdesc FROM branches WHERE EXISTS (SELECT bid FROM branches_services WHERE branches.bid = branches_services.bid
GROUP BY bid HAVING COUNT (sid) >= 4);
и вот такое объединение, которое дает совершенно аналогичный результат, но зна­чительно быстрее:
SELECT branches.bid, branches.bdesc FROM branches_services, branches WHERE branches.bid = branches_services.bid GROUP BY bid HAVING
COUNT (sid) >= 4;

И действительно, даже беглого взгляда достаточно, чтобы убедиться в том, что только один филиал воспользовался не менее чем четырьмя услугами (это филиал с идентифи­катором 1011).
В данном случае из-за того, что запрос имеет ссылку на внешний запрос, MySQL не может обработать внутренний запрос только один раз (как это обычно и происходит). Ему приходится запускать его вновь и вновь, по одному разу для каждой строки из внешней таблицы, заместить значение соответствующего поля из этой строки в подзапросе, а затем принять решение о включении этой внешней строки в окончательный результирующий набор исходя из того, возвращает ли соответствующий подзапрос результирующий набор. Очевидно, что это очень накладно с точки зрения производительности, поэтому применения внешних ссылок по мере возможности нужно избегать.