Количество выполнений вложенного запроса

Сколько раз будет выполняться этот вложенный подзапрос?

SELECT CID, CNAME 
  FROM CUSTOMER
 WHERE EXISTS ( SELECT CID 
                  FROM RENTALS 
                 WHERE CUSTOMER.CID = RENTALS.CID 
                   AND PICKUP = 'CARY' )

Это теоретический вопрос, то есть он найден в моей книге. Ответ был 6, но я не понимаю, почему это так.


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

3 ответа

Решение

Как отмечают другие, этот коррелированный подзапрос может быть переписан как объединение, но это не совсем полная история, потому что план выполнения для не преобразованного EXISTS все равно будет выглядеть как объединение. Так что на самом деле это не вопрос синтаксиса, а проблема оптимизации запросов.

EXISTS на самом деле является просто синтаксическим сокращением для "присоединения к этому набору данных, но только к одной строке в нем, даже если имеется 1 000 000 совпадений", или к так называемому полусоединению.

Таким образом, полусоединение, требуемое предикатом EXISTS для коррелированного или некоррелированного подзапроса, может быть реализовано несколькими способами, которые в значительной степени зависят от чисел или записей в этих двух таблицах.

Если вы предполагаете, что CUSTOMER оценивается в одну строку, а оптимизатор оценивает, что в RENTALS есть много тысяч строк, для которых PICKUP = 'CARY', то оптимизатор, скорее всего, прочитает строку из CUSTOMER TABLE и выполнит один поиск по таблице RENTALS.

Если в таблице RENTALS имеется приблизительно один миллион CUSTOMERS и только одна строка, тогда этот план выполнения будет сумасшедшим - вместо этого оптимизатор может инвертировать объединение, ведя с таблицей RENTALS и просматривая в таблице CUSTOMER одну строку, которая будет вернулся. В этом случае подзапрос, возможно, вообще не был выполнен.

Между этими крайностями существуют различные другие оптимизации. Например, создание уникальных значений столбца RENTAL.CID в хэш-таблице в памяти для строк, где PICKUP = 'CARY', и полное сканирование таблицы CUSTOMER для проверки этой хеш-таблицы для каждой строки, которая будет представлять собой HASH SEMIJOIN, Опять же, нет выполнения узнаваемого подзапроса. (И переписать запрос, который предлагает Бармер, вероятно, приведет к такому плану, но он также может помешать оптимизатору увидеть другие планы, подходящие для других распределений данных и количества элементов).

Итак, как говорят другие ответы, вопрос действительно спорный, потому что я думаю, что здесь есть два важных урока:

  1. Много разных операторов SQL могут привести к одному и тому же плану выполнения, и один оператор SQL также может привести к нескольким планам выполнения.
  2. Вы должны писать запросы, которые синтаксически выражают желаемый результат, а не в целом, чтобы выгрузить и ограничить выбор оптимизатора запросов.

Второй момент важен, так как он противоречит инстинкту некоторых разработчиков избегать написания коррелированных подзапросов (и, кажется, существует или не существует) в надежде обеспечить собственную оптимизацию. В частности, замена EXISTS на внешнее объединение может быть очень неоптимальной, учитывая правильное / неправильное распределение данных.

Чтобы получить прямой ответ на вопрос, я бы сказал:

  • 0
  • 1
  • Однако много строк в КЛИЕНТАХ
  • Возможно что-то еще.

Там нет правильного теоретического ответа на этот вопрос. Умный оптимизатор запросов может преобразовать запрос в JOIN:

SELECT CID, CNAME
FROM Customer
LEFT JOIN (SELECT DISTINCT CID FROM Rentals WHERE PICKUP = 'CARY') as Rentals
ON Customer.CID = Rentals.CID
WHERE Rentals.CID IS NOT NULL

Теперь подзапрос выполняется только один раз.

Запрос не будет выполнен таким образом. Планировщик запросов превратит это в нечто, где он будет выполнять один запрос по отношению к результату другого, не выполняя подзапрос снова и снова.

Скорее всего, план запроса для вашего запроса будет выглядеть так же, как и для запроса, в котором вы выполняете соединение слева и проверяете существующие записи:

select c.CID, c.CNAME
from CUSTOMER c
left join RENTALS r on c.CID = r.CID and r.PICKUP = 'CARY'
where r.CID is not null
Другие вопросы по тегам