SQL1

       

КАК РАБОТАЕТ СООТНЕСЕННЫЙ ПОДЗАПРОС?


В вышеупомянутом примере, "внутренний" (inner) и "внешний" (outer) это псевдонимы, подробно обсуждённые в Главе 9. Мы выбрали эти имена для большей ясности; они отсылают к значениям внутренних и внешних запросов, соответственно. Так как значение в поле cnum внешнего запроса меняется, внутренний запрос должен выполняться отдельно для каждой строки внешнего запроса. Строка внешнего запроса, для которого внутренний запрос каждый раз будет выполнен, называется текущей строкой-кандидатом.

=============== SQL Execution Log ============ | | | SELECT * | | FROM Customers outer | | WHERE 10/03/1990 IN | | (SELECT odate | | FROM Orders inner | | WHERE outer.cnum = inner.cnum); | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ----- | | 2001 Hoffman London 100 1001 | | 2003 Liu San Jose 200 1002 | | 2008 Cisneros San Jose 300 1007 | | 2007 Pereira Rome 100 1004 | =============================================

Рисунок 11.1 Использование соотнесённого подзапроса

Следовательно, процедура оценки, выполняемой соотнесённым подзапросом:

  1. Выбрать строку из таблицы, именованной во внешнем запросе. Это будет текущая строка-кандидат.
  2. Сохранить значения из этой строки-кандидата в псевдониме с именем в предложении FROM внешнего запроса.
  3. Выполнить подзапрос. Везде, где псевдоним, заданный для внешнего запроса, найден (в данном случае "внешний"), использовать значение текущей строки-кандидата. Использование значения из строки-кандидата внешнего запроса в подзапросе называется внешней ссылкой.
  4. Оценить предикат внешнего запроса на основе результатов подзапроса, выполняемого в шаге 3. Он определяет, выбирается ли строка-кандидат для вывода.
  5. Повторить процедуру для следующей строки-кандидата таблицы, и так далее, пока все строки таблицы не будут проверены.
  6. В вышеупомянутом примере SQL осуществляет следующую процедуру:

    1. Выбирает строку Hoffman из таблицы Заказчиков.
    2. Сохраняет эту строку как текущую строку-кандидат под псевдонимом "внешний".

    3. Затем он выполняет подзапрос. Подзапрос просматривает всю таблицу Заказов, чтобы найти строки, где значение поля cnum - такое же, как значение outer.cnum, которое в настоящее время равно 2001 - поле cnum строки Hoffman.


    4. Затем он извлекает поле odate из каждой строки таблицы Заказов, для которой это верно, и формирует набор значений поля odate.

    5. Получив набор всех значений поля odate для поля cnum = 2001, он проверяет предикат основного запроса, чтобы увидеть, имеется ли значение на 3 октября в этом наборе.


    6. Если это так (а это так), то он выбирает строку Hoffman для вывода её из основного запроса.

    7. Он повторяет всю процедуру, используя строку Giovanni как строку-кандидат, а затем сохраняет повторно, пока каждая строка таблицы Заказчиков не будет проверена.


    8. Как видите, вычисления, которые SQL выполняет с помощью этих простых инструкций, довольно сложны. Конечно, вы могли бы решить ту же самую проблему, используя объединение следующего вида (вывод для этого запроса показан на Рисунке 11.2):

      SELECT * FROM Customers first, Orders second WHERE first.cnum = second.cnum AND second.odate = 10/03/1990;

      Обратите внимание, что Cisneros был выбран дважды, по одному разу для каждого заказа, который он имел для данной даты. Мы могли бы устранить это, используя SELECT DISTINCT вместо просто SELECT. Но это не обязательно в варианте подзапроса. Оператор IN, используемый в варианте подзапроса, не делает никакого различия между значениями, которые выбираются подзапросом один раз, и значениями, которые выбираются неоднократно. Следовательно, DISTINCT не обязателен.

      =============== SQL Execution Log ============ | | | SELECT * | | FROM Customers first, Orders second | | WHERE first.cnum = second.cnum | | (SELECT COUNT (*) | | FROM Customers | | WHERE snum = main.snum; | | ============================================= | | cnum cname | | ----- -------- | | 1001 Peel | | 1002 Serres | =============================================

      Рисунок 11.2 Использование объединения вместо соотнесенного подзапроса



      Предположим, что мы хотим видеть имена и номера всех продавцов, которые имеют более одного заказчика. Следующий запрос выполнит это для вас (вывод показан на Рисунке 11.3):

      SELECT snum, sname FROM Salespeople main WHERE 1 < ( SELECT COUNT (*) FROM Customers WHERE snum = main.snum );

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

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

      =============== SQL Execution Log ============ | | | SELECT snum sname | | FROM Salespeople main | | WHERE 1 < | | AND second.odate = 10/03/1990; | | ============================================= | | cnum cname city rating snum | | ----- -------- ---- ------ ----- | | 2001 Hoffman London 100 1001 | | 2003 Liu San Jose 200 1002 | | 2008 Cisneros San Jose 300 1007 | | 2007 Pereira Rome 100 1004 | =============================================

      Рисунок 11.3 Нахождение продавцов с несколькими заказчиками


      Содержание раздела