Перейти к оглавлению
16. ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С КОМАНДАМИ МОДИФИКАЦИИ
В ЭТОЙ ГЛАВЕ, ВЫ УЗНАЕТЕ КАК ИСПОЛЬЗОВАТЬ подзапросы в командах мо-
дификации. Вы найдете, что нечто подобное - вы уже видели при исполь-
зовании подзапросов в запросах. Понимание, как подзапросы используются
в командах SELECT, cделает их применение в командах модификации более
уверенным, хотя и останутся некоторые вопросы.
Завершением команды SELECT является подзапрос, но не предикат, и по-
этому его использование отличается от использования простых предикатов
с командами модификации, которые вы уже выполняли ранеее с командами
UPDATE и DELETE. Вы использовали простые запросы чтобы производить
значения для INSERT, а теперь мы можем расширить эти запросы чтобы
включять в них подзапросы.
Важный принцип который надо соблюдать при работе с командами модифи-
кации, состоит в том, что вы не можете в предложении FROM любого под-
запроса, модифицировать таблицу к которой ссылаетесь с помощью основ-
ной команды. Это относится ко всем трем командам модификации. Хотя
имеется большое количество ситуаций в которых будет полезно сделать
запрос той таблицы которую вы хотите модифицировать причем во время ее
модификации, это слишком усложняет операцию чтобы использовать ее на
практике.
Не делайте ссылки к текущей строке таблицы указанной в команде, ко-
торая является соотнесенным подзапросом.
==== ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С INSERT ===
INSERT - это самый простой случай. Вы уже видели как вставлять ре-
зультаты запроса в таблицу. Вы можете использовать подзапросы внутри
любого запроса, который генерирует значения для команды INSERT тем же
самым способом, которым вы делали это для других запросов - т.е. внут-
ри предиката или предложения HAVING.
Предположим, что мы имеем таблицу с именем SJpeople, столбцы которой
совпадают со столбцами нашей таблицы Продавцов. Вы уже видели как за-
полнять таблицу подобно этой, заказчиками в городе, например, в San
Jose:
INSERT INTO SJpeople
SELECT *
FROM Salespeople
WHERE city = 'San Jose';
Теперь мы можем использовать подзапрос чтобы добавить к таблице
SJpeople всех продавцов которые имеют заказчиков в San Jose, независи-
мо от того, находятся ли там продавцы или нет:
INSERT INTO SJpeople
SELECT *
FROM Salespeople
WHERE snum = ANY
( SELECT snum
FROM Customers
WHERE city = ' (San (Jose' );
Оба запроса в этой команде функционируют также как если бы они не
являлись частью выражения INSERT. Подзапрос находит все строки для за-
казчиков в San Jose и формирует набор значений snum. Внешний запрос
выбирает строки из таблицы Salespeople, где эти значения snum найдены.
В этом примере, строки для продавцов Rifkin и Serres, которые назначе-
ны заказчикам в San Jose - Liu и Cisneros, будут вставлены в таблицу
SJpeople.
НЕ ВСТАВЛЯЙТЕ ДУБЛИКАТЫ СТРОК
Последовательность команд в предшествующем разделе может быть проб-
лематичной. Продавец Serres находится в San Jose, и следовательно бу-
дет вставлен с помощью первой команды. Вторая команда попытается вста-
вить его снова, поскольку он имеет еще одного заказчика в San Jose.
Если имеются любые ограничения в таблице SJpeople которые вынуждают ее
значения быть уникальными, эта вторая вставка потерпит неудачу ( как
это и должно было быть). Двойные строки это плохо. ( См. Главу 18 для
подробностей об ограничениях. )
Было бы лучше если бы вы могли как-то выяснить, что эти значения уже
были вставлены в таблицу, прежде чем вы попытаетесь сделать это снова,
с помощью добавления другого подзапроса ( использующего операторы типа
EXISTS, IN, < > ALL, и так далее ) к предикату.
К сожалению, чтобы сделать эту работу, вы должны будете сослаться на
саму таблицу SJpeople в предложении FROM этого нового подзапроса, а,
как мы говорили ранее, вы не можете ссылаться на таблицу которая за-
действована ( целиком ) в любом подзапросе команды модификации. В слу-
чае INSERT, это будет также препятствовать соотнесенным подзапросам,
основанным на таблице в которую вы вставляете значения. Это имеет зна-
чение, потому что, с помощью INSERT, вы создаете новую строку в табли-
це. "Текущая строка" не будет существовать до тех пор, пока INSERT не
закончит ее обрабатывать.
ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ СОЗДАНЫХ
ВО ВНЕШНЕЙ ТАБЛИЦЕ ЗАПРОСА
Запрещение на ссылку к таблице которая модифицируется командой IN-
SERT не предохранит вас от использования подзапросов которые ссылаются
к таблице используемой в предложении FROM внешней команды SELECT. Таб-
лица из которой вы выбираете значения, чтобы произвести их для INSERT,
не будет задействована командой; и вы сможете ссылаться к этой таблице
любым способом которыми вы обычно это делали, но только если эта таб-
лица указана в автономном запросе. Предположим что мы имеем таблицу с
именем Samecity в которой мы запомним продавцов с заказчиками в их го-
родах.
Мы можем заполнить таблицу используя соотнесенный подзапрос:
INSERT INTO (Samecity
SELECT *
FROM (Salespeople outer
WHERE city IN
( SELECT city
FROM Customers inner
WHERE inner.snum = outer.snum );
Ни таблица Samecity, ни таблица Продавцов не должны быть использова-
ны во внешних или внутренних запросах INSERT. В качестве другого при-
мера, предположим, что вы имеете премию для продавца который имеет са-
мый большой порядок на каждый день. Вы следите за ним в таблице с име-
нем Bonus, которая содержит поле snum продавцов, поле odate и поле
amt. Вы должны заполнить эту таблицу информацией которая хранится в
таблице Порядков, используя следующую команду:
INSERT INTO Bonus
SELECT snum, odate, amt
FROM Orders a
WHERE amt =
( SELECT MAX (amt)
FROM Orders b
WHERE a.odate = b.odate );
Даже если эта команда имеет подзапрос который базируется на той же
самой таблице что и внешний запрос, он не ссылается к таблице Bonus,
на которую воздействует команда. Что для нас абсолютно приемлемо.
Логика запроса, естественно, должна просматривать таблицу Порядков,
и находить для каждой строки максимум порядка сумм приобретений для
этой даты. Если эта величина - такая же как у текущей строки, текущая
строка является наибольшим порядком для этой даты, и данные вставляют-
ся в таблицу Bonus.
==== ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С DELETE ====
Вы можете также использовать подзапросы в предикате команды DELETE.
Это даст вам возможность определять некоторые довольно сложные крите-
рии чтобы установить, какие строки будут удаляться, что важно, так как
вы конечно же не захотите по неосторожности удалить нужную строку.
Например, если мы закрыли наше ведомство в Лондоне, мы могли бы ис-
пользовать следующий запрос чтобы удалить всех заказчиков назначенных
к продавцам в Лондоне:
DELETE
FROM Customers
WHERE snum = ANY
( SELECT snum
FROM Salespeople
WHERE city = 'London' );
Эта команда удалит из таблицы Заказчиков строки Hoffman и Clemens
(назначенных для Peel), и Periera (назначенного к Motika).
Конечно, вы захотите удостовериться, правильно ли сформирована эта
операция, прежде чем удалитт или изменитт строки Peel и Motika.
Это важно. Обычно, когда мы делаем модификацию в базе данных, кото-
рая повлечет другие модификации, наше первое желание - сделать сначало
основное действие, а затем проследить другие, вторичные. Этот пример,
покажет, почему более эффективно делать наоборот, выполнив сначала
вторичные действия.
Если, например, вы решили изменить значение поля city ваших продав-
цов везде, где они переназначены, вы должны рассмотреть всех этих за-
казчиков более сложным способом.
Так как реальные базы данных имеют тенденцию развиваться до значи-
тельно больших размеров чем наши небольшие типовые таблицы, это может
стать серьезной проблемой. SQL может предоставить некоторую помощь в
этой области используя механизм справочной целостности ( обсужденной в
Главе 19 ), но это не всегда доступно и не всегда применимо.
Хотя вы не можете ссылаться к таблице из которой вы будете удалять
строки в предложении FROM подзапроса, вы можете в предикате, сослаться
на текущую строку-кандидат этой таблицы - которая является строкой ко-
торая в настоящее время проверяется в основном предикате. Другими сло-
вами, вы можете использовать соотнесенные подзапросы. Они отличаются
от тех соотнесенных подзапросов, которые вы могли использовать с IN-
SERT, в котором они фактически базировались на строках-кандидатах таб-
лицы задействованой в команде, а не на запросе другой таблицы.
DELETE FROM Salespeople
WHERE EXISTS
( SELECT *
FROM Customers
WHERE rating = 100
AND Salespeople.snum = Customers.snum );
Обратите внимание, что AND часть предиката внутреннего запроса ссы-
лается к таблице Продавцов. Это означает что весь подзапрос будет вы-
полняться отдельно для каждой строки таблицы Продавцов, также как это
выполнялось с другими соотнесенными подзапросами. Эта команда удалит
всех продавцов которые имели по меньшей мере одного заказчика с оцен-
кой 100 в таблице Продавцов.
Конечно же, имеется другой способ сделать то же:
DELETE FROM Salespeople
WHERE 100 IN
( SELECT rating
FROM Customers
WHERE Salespeople.snum = Customers.snum);
Эта команда находит все оценки для каждого заказчика продавцов и
удаляет тех продавцов заказчики которого имеют оценку = 100.
Обычно соотнесенные подзапросы - это подзапросы связанные с таблицей
к которой они ссылаются во внешнем запросе (а не в самом предложении
DELETE) - и также часто используемы. Вы можете найти наинизший порядок
на каждый день и удалить продавцов которые произвели его, с помощью
следующей команды:
DELETE FROM Salespeople
WHERE (snum IN
( SELECT snum
FROM Orders
WHERE amt =
( SELECT MIN (amt)
FROM Orders b
WHERE a.odate = b.odate ));
Подзапрос в предикате DELETE , берет соотнесенный подзапрос. Этот
внутренний запрос находит минимальный порядок суммы приобретеий для
даты каждой строки внешнего запроса. Если эта сумма такая же как сумма
текущей строки, предикат внешнего запроса верен, что означает, что те-
кущая строка имеет наименьший порядок для этой даты. Поле snum продав-
ца, ответственного за этот порядок, извлекается и передается в основ-
ной предикат команды DELETE, которая затем удаляет все строки с этим
значением поля snum из таблицы Продавцов( так как snum - это первичный
ключ таблицы Продавцов, то естественно там должна иметься только одна
удаляемая строка для значения поля snum выведенного с помощью подзап-
роса. Если имеется больше одной строки, все они будут удалены. ) Поле
snum = 1007 которое будет удалено, имеет наименьшее значение на 3 Ок-
тября; поле snum = 1002, наименьшее на 4 Октября; поле snum = 1001,
наименьшее в порядках на 5 Октября ( эта команда кажется довольно рез-
кой, особенно когда она удаляет Peel создавшего единственный порядок
на 5 Октября, но зато это хорошая иллюстрация). Если вы хотите сохра-
нить Peel, вы могли бы добавить другой подзапрос, который бы это де-
лал:
DELETE FROM Salespeople
WHERE (snum IN
( SELECT snum
FROM Orders a
WHERE amt =
( SELECT MIN (amt)
FROM Orders b
WHERE a.odate = b.odate )
AND 1 <
( SELECT COUNT onum
FROM Orders b
WHERE a.odate = b.odate ));
Теперь для дня в котором был создан только один порядок, будет про-
изведен счет = 1 во втором соотнесенном подзапросе. Это сделает преди-
кат внешнего запроса неправильным, и поля snum следовательно не будут
переданы в основной предикат.
==== ИСПОЛЬЗОВАНИЕ ПОДЗАПРОСОВ С UPDATE ====
UPDATE использует подзапросы тем же самым способом что и DELETE -
внутри этого необязательного предиката. Вы можете использовать соотне-
сенные подзапросы или в форме пригодной для использования с DELETE -
связаной или с модифицируемой таблицей или с таблицей вызываемой во
внешнем запросе. Например, с помощью соотнесенного подзапроса к табли-
це которая будет модифицироваться, вы можете увеличить комиссионные
всех продавцов которые были назначены по крайней мере двум заказчикам:
UPDATE Salespeople
SET comm = comm + .01
WHERE 2 < =
( SELECT COUNT (cnum)
FROM Customers
WHERE Customers.snum =
Salespeople.snum );
Теперь продавцы Peel и Serres, имеющие многочисленых заказчиков, по-
лучат повышение своих комиссионных.
Имеется разновидность последнего примера из предыдущего раздела с
DELETE. Он уменьшает комиссионные продавцов которые произвели наимень-
шие порядки, но не стирает их в таблице:
UPDATE Salespeople
SET comm = comm - .01
WHERE snum IN
( SELECT snum
FROM Orders a
WHERE amt =
( SELECT MIN (amt)
FROM Orders b
WHERE a.odate = b.odate ));
СТОЛКНОВЕНИЕ С ОГРАНИЧЕНИЯМИ ПОДЗАПРОСОВ КОМАНДЫ DML
Неспособность сослаться к таблице задействованой в любом подзапросе
из команды модификации (UPDATE), устраняет целые категории возможных
действий. Например, вы не можете просто выполнить такую операцию как
удаление всех заказчиков с оценками ниже средней. Вероятно лучше всего
вы могли бы сначала (Шаг 1.), выполнить запрос, получающий среднюю ве-
личину, а затем (Шаг 2.), удалить все строки с оценкой ниже этой вели-
чины:
Шаг 1.
SELECT AVG (rating)
FROM Customers;
Вывод = 200.
Шаг 2.
DELETE
FROM Customers
WHERE rating < 200;
=============== РЕЗЮМЕ ==================
Теперь вы овладели тремя командами которые управляют всем содержани-
ем вашей базы данных. Осталось только несколько общих вопросов относи-
тельно ввода и стирания значений таблицы, когда например эти команды
могут выполниться данным пользователем в данной таблице и когда дейс-
твия сделанные ими, становятся постоянными. Подведем итог: Вы исполь-
зуете команду INSERT чтобы добавлять строки в таблицу. Вы можете или
дать имена значениям этих строк в предложении VALUES ( когда только
одна строка может быть добавлена ), или вывести значения с помощью
запроса ( когда любое число строк можно добавить одной командой ). Ес-
ли используется запрос, он не может ссылаться к таблице в которую вы
делаете вставку,каким бы способом Вы ее ни делали, ни в предложении
FROM, ни с помощью внешней ссылки ( как это делается в соотнесенных
подзапросах ). Все это относится к любым подзапросам внутри этого зап-
роса.
Запрос, однако, оставляет вам свободу использования соотнесенных
подзапросов или подзапросов которые дают в предложении FROM имя табли-
це, которое уже было указано в предложении FROM внешнего запроса ( это
- общий случай для запросов ).
DELETE и UPDATE используются чтобы, соответственно удалить строки из
таблицы и изменить в них значения. Оба они применимы ко всем строкам
таблицы, если не используется предикат определяющий какие строки долж-
ны быть удалены или модифицированы. Этот предикат может содержать под-
запросы, которые могут быть связаны с таблицей, удаляемой, или модифи-
цированой, с помощью внешней ссылки. Эти подзапросы, однако, не могут
ссылать к таблице модифицируемой любым предложением FROM. Может пока-
заться, что мы прошли материал SQL который обладает не самым понятным
логическим порядком. Сначала мы сделали запрос таблицы которая уже за-
полнена данными. Потом мы показали как можно фактически помещать эти
значения изначально. Но, как вы видете, полное ознакомление с запроса-
ми здесь неоценимо. Теперь, когда мы показали вам как заполнять значе-
ниями таблицы которые уже были созданы (по определению) , мы покажем(
со следующей главы) откуда появились эти таблицы.
************** РАБОТА С SQL **************
1. Предположите, что имеется таблица называемая Multicust, с такими же
именами столбцов что и таблица Продавцов. Напишите команду, которая
бы вставила всех продавцов (из таблицы Продавцов)имеющих более чем
одного заказчика в эту таблицу.
2. Напишите команду которая бы удаляла всех заказчиков не имеющих те-
кущих порядков.
3. Напишите команду которая бы увеличила на двадцать процентов комис-
сионные всех продавцов имеющих общие текущие порядки выше чем
$3,000.
( См. Приложение A для ответов. )
Перейти к оглавлению