Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Базы Данных - Сибилев, 2007

.pdf
Скачиваний:
290
Добавлен:
11.05.2015
Размер:
1.93 Mб
Скачать

 

 

201

 

 

 

 

S1

P2

 

1200

S3

P9

 

600

S6

P5

 

8000

S7

P3

 

1000

S7

P6

 

2200

S7

P8

 

2500

S8

P1

 

2600

Пример 2. Получить значения номеров деталей, суммарные объёмы поставок каждой из которых, выполненные поставщиком S7, превосходят

500 штук.

SELECT Pnum Деталь, SUM(Qt) Объём

FROM SPJ

WHERE Snum = 'S7'

GROUP BY Pnum

HAVING SUM(Qt) > 500;

Деталь

Объём

P3

1000

P6

2200

P8

2500

Другая, может быть, менее очевидная формула такова:

SELECT PNUM Деталь, SUM(QT) Объём

FROM SPJ

GROUP BY SNUM, PNUM

HAVING SNUM = 'S7' AND SUM(QT) > 500;

7.2.12 Вложение запросов

Оператор SELECT производит неименованную таблицу. На неё нельзя ссылаться из другого оператора. Однако какой-то механизм управ-

ления работой операторов в зависимости от результатов выборки необхо-

дим. В современном SQL это механизм вложения запросов.

Вложенным запросом или подзапросом называется оператор

SELECT, включённый в формулу другого (включающего) оператора.

202

Подзапросы обычно используются в предикатах. Это означает, что подзапрос может быть частью другого подзапроса.22 Стандарт SQL1 до-

пускает только такое применение. Стандартом же SQL2 разрешается, кро-

ме того, использование подзапросов в выражениях для вычисления значе-

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

ния запросов на примере оператора выборки данных.

Подзапрос в предложении FROM определяет часть источника дан-

ных для включающего оператора выборки. В процессе обработки вклю-

чающего запроса он исполняется один раз – при формировании F-таблицы.

Подзапрос может ссылаться только на столбцы таблиц, указанных в его собственном предложении FROM. Выходная таблица подзапроса отражает текущее состояние его источника данных. Она получает имя, действитель-

ное только внутри включающего запроса. На это имя можно ссылаться во всех предложениях включающего запроса как на имя таблицы. Один при-

мер использования подзапроса в предложении FROM мы уже видели (см.

п. 7.2.9, пример 1, вариант 3).

Пример 1. Получить все сведения о поставляемых деталях и общие объёмы поставок этих деталей.

Может показаться, что требуемый результат выдаст оператор

SELECT P.*, SUM(Qt)

FROM P JOIN SPJ

GROUP BY P.Pnum;

Однако он будет отвергнут системой, поскольку целевой список содержит недопустимые элементы.

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

22 Число уровней вложенности подзапросов стандартами не ограничивается, однако любая реальная SQLсистема такое ограничение имеет.

203

таблицей P. Нужную таблицу можно получить непосредственно при обра-

ботке предложения FROM.

SELECT P.*, SUMQ.SQT

FROM P JOIN (

SELECT Pnum, SUM(Qt) AS SQT

FROM SPJ

GROUP BY Pnum

) AS SUMQ;

Из нашей учебной БД будут извлечены следующие данные:

Pnum

Pnam

We

Co

Ci

SQT

P1

корпус

300

белый

Томск

2700

P7

кнопка

10

оранжевый

Асино

200

P4

корпус

800

синий

Лесото

280

P9

лампочка

1

красный

Томск

950

P5

панель

100

серый

Асино

8000

P6

кинескоп

1200

черный

Яя

2200

P2

разъём

3

чёрный

Яя

1200

P8

панель

450

розовый

Асино

2800

P3

кнопка

5

красный

Томск

2000

Попытайтесь записать другие варианты формул операторов выборки,

в частности, не используя подзапрос в предложении FROM.

Подзапросы в предикатах предложений WHERE и HAVING

используются для управления процессом выборки данных. Далее в настоящем разделе мы будем для определённости говорить о предикатах предложения WHERE. Однако всё нижесказанное в равной мере относится и к предикатам предложения HAVING.

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

Стандарт допускает использование подзапросов в любых предикатах. Однако существуют некоторые ограничения. Укажем

204

важнейшие из них, имея в виду приведённое нами определение предиката

SQL1.23

− В предикатах простого сравнения можно использовать только

скалярный подзапрос, возвращающий таблицу из одного столбца и единственной строки. Эта таблица трактуется системой как значение типа.

− В предикате квантифицированного сравнения и предикате IN

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

− В предикате EXISTS можно использовать табличный подзапрос,

возвращающий таблицу из любого количества столбцов и строк.

Подзапрос может ссылаться только на столбцы собственного источ-

ника данных, а может, кроме того, содержать ссылки на столбцы источни-

ка данных включающего запроса (внешние ссылки). В первом случае под-

запрос называется простым, во втором – коррелированным или связанным.

Существуют важные различия в порядке обработки простого и коррелиро-

ванного подзапросов.

Простой подзапрос обрабатывается один раз. Возвращённая подза-

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

дой строке F-таблицы.

Пример 2. Вернёмся к примеру 1 из п. 7.2.9 и запишем формулу оператора выборки, основанную на следующем словесном представлении:

«Получить значения атрибута Jnam из таких кортежей отношения J,

в которых значения атрибута J.Jnum принадлежат множеству значений ат-

рибута SPJ.Jnum из таких кортежей отношения SPJ, в которых SPJ.Snum = 'S1'».

В соответствии с этим представлением достаточно однажды постро-

ить нужное множество значений SPJ.Jnum, а затем для каждой строки таб-

23 Аналогичные ограничения есть и в SQL2. Их отличия обусловлены различиями в определениях предиката.

205

лицы J выполнить проверку принадлежности значения J.Jnum этому мно-

жеству. В переводе на SQL это выглядит так:

SELECT Jnam

FROM J

WHERE Jnum IN (

SELECT DISTINCT Jnum

FROM SPJ

WHERE Snum = 'S1'

);

Обрабатывая предложение WHERE включающего запроса, система прежде всего исполнит подзапрос в предикате IN. Он возвратит значения номеров изделий, для которых поставщик S1 выполнял поставки деталей:

Jnum

J3

J5

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

проса.

Замечание 1. Параметр DISTINCT в подзапросе можно было бы не использовать. Тогда результат содержал бы дубликат первой строки. Од-

нако это никак не повлияло бы на результаты проверки.

Замечание 2. Имя Jnum во включающем и вложенном запросах от-

носится к столбцам разных таблиц, но мы не использовали префиксы для уточнения имён. В этом нет необходимости, т.к. обрабатывая подзапрос,

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

Коррелированный подзапрос, в отличие от простого, исполняется повторно на каждой строке проверяемой таблицы. Это необходимо, т.к.

206

он содержит внешние ссылки и, следовательно, его результат зависит от текущих значений ссылочных столбцов.

Пример 3. Рассмотрим ещё одну возможную словесную формули-

ровку запроса о наименованиях изделий: «Получить значения атрибута

Jnam из таких кортежей отношения J, для которых в отношении SPJ суще-

ствуют кортежи со значениями SPJ.Jnum, совпадающими с текущим зна-

чением J.Jnum и значениями SPJ.Snum, равными S1».

Эта формулировка предполагает проверку факта существования со-

ответствующих строк в таблице SPJ для каждой строки таблицы J. Такую проверку может выполнить предикат EXISTS.

SELECT Jnam

FROM J

WHERE EXISTS (

SELECT *

FROM SPJ

WHERE SPJ.Jnum = J.Jnum

AND Snum = 'S1'

);

В этом случае префиксы имён столбцов необходимы, поскольку внешняя и внутренняя ссылки в подзапросе указывают на одноимённые столбцы разных таблиц.

Обычно в целевом списке подзапроса предиката EXISTS использу-

ется спецификатор *. Хотя стандарт не запрещает явное указание имён столбцов, это не имеет смысла, т.к. значения столбцов не используются при вычислении значения предиката. Оно зависит лишь от наличия или от-

сутствия строк в выходной таблице подзапроса.

На практике предикат EXISTS используется с коррелированными подзапросами. Формально его можно использовать и с простым подзапро-

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

207

Пример 4. Получить номера, наименования и города размещения де-

талей, суммарные объёмы поставок которых максимальны.24

Требуемые данные можно получить только из таблицы P, но для выбора нужных строк придётся использовать данные из таблицы SPJ. Вы-

полняя запрос средствами «бумажной» технологии, мы, скорее всего, ис-

пользовали бы такой план.

–Создать вспомогательную таблицу с заголовком (номер детали,

суммарный объём поставок).

–Для каждого значения номера детали из таблицы SPJ вычислить сум-

марный объём поставок и заполнить вспомогательную таблицу.

–Выбрать значения номеров деталей из тех строк вспомогательной таблицы, в которых значение суммарного объёма поставок макси-

мально.

–С выбранными значениями номеров деталей войти в таблицу P и по-

лучить значения столбцов Pnum, Pnam, Ci.

Приведённая ниже формула SQL точно описывает логику этой про-

цедуры.

SELECT P.Pnum AS Номер,

P.Pnam AS Наименование,

P.Ci AS Город

FROM P

WHERE P.Pnum IN

( SELECT Pnum

FROM ( SELECT Pnum, SUM(Qt) AS Sumqt

FROM SPJ

GROUP BY Pnum

) AS TQt

WHERE

NOT EXISTS

24 Ни одна из приведённых ниже формул этого запроса не удовлетворяет требованиям SQL1.

208

( SELECT *

FROM TQt AS Y

WHERE Y.Sumqt > TQt. Sumqt

)

);

Вспомогательная таблица будет построена подзапросом в предложе-

нии FROM подзапроса предиката IN. Ей будет присвоено локальное имя

TQt.

TQt

Pnum

Sumqt

P1

2700

P7

200

P2

1200

P9

950

P5

2800

P6

2800

P8

2800

P4

280

P3

2000

Затем на каждой строке этой таблицы будет обрабатываться подза-

прос в предикате NOT EXISTS предложения WHERE подзапроса преди-

ката IN. В выходную таблицу подзапроса первого уровня вложения попа-

дут значения Pnum из тех строк таблицы TQt, в которых значение суммар-

ного объёма поставок Sumqt максимально. Вот результат, произведённый этим подзапросом:

Pnum

P5

P8

P6

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

лицы P.

Результат этих манипуляций таков:

Номер Наименование Город

P5

панель

Асино

 

 

209

 

 

 

 

 

P8

панель

 

Асино

 

 

 

 

P6

кинескоп

 

Яя

Другая формула запроса приведена ниже.

SELECT P.Pnum AS Номер,

P.Pnam AS Наименование,

P.Ci AS Город

FROM P

WHERE (

SELECT SUM(Qt)

FROM SPJ AS X

WHERE P.Pnum = X.Pnum

)

>= ALL

(

SELECT SUM(Qt)

FROM SPJ AS Y

GROUP BY Y.Pnum

);

Простой подзапрос в правой части предиката квантифицированного сравнения исполняется один раз и производит множество значений сум-

марных объёмов поставок деталей. Оно используется для вычисления зна-

чений предиката в процессе дальнейшей обработки запроса.

Коррелированный подзапрос в левой части предиката для текущей строки таблицы P вычисляет значение суммарного объёма поставок соот-

ветствующей детали. Это значение сравнивается с каждым значением множества. Если оказывается, что оно не меньше любого из них, то данные из текущей строки таблицы P заносятся в выходную таблицу запроса. За-

тем выбирается следующая строка таблицы и вновь исполняется коррели-

рованный подзапрос.

210

Приведём ещё одну, возможно, наиболее наглядную формулу запро-

са:

SELECT P.Pnum AS Номер,

P.Pnam AS Наименование,

P.Ci AS Город

FROM P,

(

SELECT Pnum, SUM(Qt) AS Sumqt

FROM SPJ

GROUP BY Pnum

) AS TQt

WHERE P.Pnum = TQT.Pnum

AND Sumqt = (SELECT MAX(Sumqt) FROM TQT);

В логике этой формулы читатель разберётся самостоятельно. Заме-

тим, что она единственная может быть легко модифицирована для такого запроса: «Получить номера, наименования и города размещения деталей,

суммарные объёмы поставок которых максимальны, а также значение мак-

симального объёма». Как? Почему так же нельзя изменить предыдущие формулы?

Как видно из приведённых примеров, механизм вложения запросов – это мощное выразительное средство. С его помощью можно представить запрос любой сложности в виде одного оператора SELECT. Все запросы,

выразимые в терминах соединений, могут быть выражены и в терминах подзапросов. Обратное неверно. Например, только что обсуждавшийся за-

прос без использования подзапросов сформулировать невозможно. Поче-

му?