Вставка одного запроса внутрь другого
Как работает подзапрос?
С помощью SQL вы можете вкладывать запросы внутрь друг друга. Обычно, внутренний запрос генерирует значение которое проверяется в предикате внешнего запроса, определяющего верно оно или нет. Например, предположим что мы знаем имя продавца: Motika, но не знаем значение его поля snum, и хотим извлечь все Продаж из таблицы Продажи. Имеется один способ чтобы сделать это (вывод показывается в Рисунке 6):
SELECT *
FROM Orders
WHERE snum =
( SELECT snum
FROM Salespeople
WHERE sname = 'Motika');
Чтобы оценить внешний (основной ) запрос, SQL сначала должен оценить внутренний запрос (или подзапрос) внутри предложения WHERE. Он делает это так как и должен делать запрос имеющий единственную цель - отыскать через таблицу Продавцов все строки, где поле sname равно значению Motika, и затем извлечь значения поля snum этих строк.
Единственной найденной строкой естественно будет snum = 1004. Однако SQL, не просто выдает это значение, а помещает его в предикат основного запроса вместо самого подзапроса, так чтобы предиката прочитал что
WHERE snum = 1004
Номер чека | Сумма покупки | Дата покупки | Номер заказчика | Номер продавца |
1 900,10р. | 3 октября 1990 г. |
Рисунок 6 Использование подзапроса
Основной запрос затем выполняется как обычно с вышеупомянутыми результатами. Конечно же, подзапрос должен выбрать один и только один столбец, а тип данных этого столбца должен совпадать с тем значением с которым он будет сравниваться в предикате. Часто, как показано выше, выбранное поле и его значение будут иметь одинаковые имена( в этом случае, snum), но это необязательно. Конечно, если бы мы уже знали номер продавца Motika, мы могли бы просто напечатать WHERE snum = 1004и выполнять далее с подзапросом в целом, но это было бы не так универсально. Это будет продолжать работать даже если номер Motika изменился, а, с помощью простого изменения имени в подзапросе, вы можете использовать его для чего угодно.
Значения, которые могут выдавать подзапросы
Скорее всего было бы удобнее, чтобы наш подзапрос в предыдущем примере возвращал одно и только одно значение.
Имея выбранным поле snum WHERE city = ‘London’ вместо WHERE sname = ‘Motika’,можно получить несколько различных значений. Это может сделать уравнение в предикате основного запроса невозможным для оценки верности или неверности, и команда выдаст ошибку.
При использовании подзапросов в предикатах основанных на реляционных операторах (уравнениях или неравенствах), вы должны убедиться что использовали подзапрос который будет выдавать одну и только одну строку вывода. Если вы используете подзапрос который не выводит никаких значений вообще, команда не потерпит неудачи; но основной запрос не выведет никаких значений. Подзапросы которые не производят никакого вывода (или нулевой вывод) вынуждают рассматривать предикат ни как верный ни как неверный, а как неизвестный. Однако, неизвестный предикат имеет тот же самый эффект что и неверный: никакие строки не выбираются основным запросом.
Это плохая стратегия, чтобы делать что-нибудь подобное следующему:
SELECT *
FROM Orders
WHERE snum =
( SELECT snum
FROM Salespeople
WHERE city = ‘Barcelona’ );
Номер чека | Сумма покупки | Дата покупки | Номер заказчика | Номер продавца |
18,69р. | 3 октября 1990 г. | |||
1 098,16р. | 3 октября 1990 г. |
Поскольку мы имеем только одного продавца в Barcelona - Rifkin, то подзапрос будет выбирать одиночное значение snum и следовательно будет принят. Но это - только в данном случае. Большинство SQL баз данных имеют многочисленных пользователей, и если другой пользователь добавит нового продавца из Barcelona в таблицу, подзапрос выберет два значения, и ваша команда потерпит неудачу.
DISTINCT с подзапросами
Вы можете, в некоторых случаях, использовать DISTINCT чтобы вынудить подзапрос генерировать одиночное значение. Предположим что мы хотим найти все Продаж кредитований для тех продавцов которые обслуживают Hoffmanа (cnum = 2001).
Имеется один способ чтобы сделать это (вывод показывается в Рисунке 7):
SELECT *
FROM Orders
WHERE snum =
( SELECT DISTINCT snum
FROM Orders
WHERE cnum = 2001 );
Номер чека | Сумма покупки | Дата покупки | Номер заказчика | Номер продавца |
767,19р. | 3 октября 1990 г. | |||
4 723,00р. | 5 октября 1990 г. | |||
9 891,88р. | 6 октября 1990 г. |
Рисунок 7 Использование DISTINCT чтобы вынудить получение одного значения из подзапроса
Подзапрос установил что значение поля snum совпало с Hoffman - 1001, и затем основной запрос выделил все Продаж с этим значением snum из таблицы Продажи( не разбирая, относятся они к Hoffman или нет). Так как каждый заказчик назначен к одному и только этому продавцу, мы знаем что каждая строка в таблице Продажи с данным значением cnum должна иметь такое же значение snum. Однако так как там может быть любое число таких строк, подзапрос мог бы вывести много (хотя и идентичных ) значений snum для данного поля cnum. Аргумент DISTINCT предотвращает это. Если наш подзапрос возвратит более одного значения, это будет указывать на ошибку в наших данных - хорошая вещь для знающих об этом.
Альтернативный подход должен быть чтобы ссылаться к таблице Заказчиков а не к таблице Продажи в подзапросе. Так как поле cnum - это первичный ключ таблицы Заказчика, запрос выбирающий его должен произвести только одно значение. Это рационально только если вы как пользователь имеете доступ к таблице Продажи но не к таблице Заказчиков. В этом случае, вы можете использовать решение которое мы показали выше. Пожалуйста учтите, что методика используемая в предшествующем примере применима только когда вы знаете, что два различных поля в таблице должны всегда совпадать, как в нашем случае. Эта ситуация не является типичной в реляционных базах данных, она является исключением из правил.