
УП СУБД ч1
.pdf
STUDENT |
COURSE |
RESULT |
Bill |
C Programming |
5 |
Jane |
OS |
5 |
Рис. 11.3. Значение запроса 11.9
Подзапросы можно использовать и в операциях сравнения. Например, запрос 11.9 определяет, какие студенты посещают более одного курса:
SELECT S.name, S.class FROM Student S |
|
|||
WHERE ( SELECT COUNT(*) FROM Attends |
(11.10) |
|||
WHERE student = S.name ) > 1; |
||||
|
|
|
|
|
|
NAME |
CLASS |
|
|
|
Jane |
3 |
|
|
|
Bill |
3 |
|
|
Рис. 11.4. Значение запроса 11.10
Запрос 11.11 позволяет определить, какие студенты посещают более одного курса, читаемого преподавателем
‘G.Anderson’:
SELECT S.name, S.class FROM Student S
WHERE ( SELECT COUNT(*) FROM Attends A
WHERE A.student = S.name AND ‘G.Anderson’ IN
( SELECT staff FROM Course
WHERE A.title = title ) )> 1; (11.11)
NAME CLASS
Jane 3
Рис. 11.5. Значение запроса 11.11
На использование подзапросов в операциях сравнения накладываются некоторые дополнительные ограничения. В качестве примера рассмотрим два схожих запроса с некоррелированными подзапросами:
SELECT title, staff from Course WHERE staff = |
|
( SELECT name FROM Staff |
(11.12) |
WHERE position = ‘Senior Lector’ ); |
121

SELECT title, staff from Course WHERE staff = |
|
( SELECT name FROM Staff |
(11.13) |
WHERE position = ‘Full Professor’ ); |
Оба запроса являются синтаксически корректными, и запрос 11.12 действительно будет выполнен, сформировав следующую выборку:
TITLE |
STAFF |
DBMS |
G.Anderson |
Graphics |
G.Anderson |
OS |
G.Anderson |
Рис. 11.6. Значение запроса 11.13
Но при исполнении запроса 11.13 (при текущем состоянии данных в таблице Staff) возникнет следующая ошибка:
ORA-01427: single-row subquery returns more than one row.
Это означает, что выборка подзапроса содержит более одной строки; сравнение одиночных значений с множествами в языке SQL не предусмотрено.
Таким образом, при использовании подзапросов в операциях сравнения необходимо, чтобы: 1) значение подзапроса состояло не более чем из одной записи; 2) эта запись была совместима по типам полей со сравниваемым значением. Кстати, запись не обязана быть атомарным значением (т.е. состоять из одного поля); например, можно использовать следующий (несколько искусственный) вариант, использующий сравнение списочных выражений, одно из которых является подзапросом:
SELECT title, staff from Course |
|
WHERE (staff, ‘Senior Lector’ ) = |
|
( SELECT name, position FROM Staff |
(11.14) |
WHERE position = ‘Senior Lector’ ) |
Возвращаясь к запросам 8.12 и 8.13, следует отметить, что подобный тип запросов является не вполне удачным, поскольку его исполнение существенно зависит от конкретных значений используемых параметров (значения, сравниваемого с полем position) и текущего состояния таблицы Staff. Более безопасным является использование эквивалентного
122

варианта с предикатом IN (11.15) или с коррелированным подзапросом (11.16), которые для любых данных будут работать корректно:
SELECT title, staff from Course WHERE staff IN |
|
( SELECT name FROM Staff |
(11.15) |
WHERE position = ‘Full Professor’ ); |
SELECT title, staff from Course WHERE staff IN
( SELECT name FROM Staff WHERE Staff.name = staff
AND position = ‘Full Professor’ ); |
(11.16) |
Запрос 11.15 с предикатом IN является более предпочтительным, поскольку запросы с некоррелированными подзапросами выполняются более эффективно.
Еще одним полезным предикатом является предикат EXISTS, который в качестве параметра получает подзапрос и возвращает значение TRUE, если результирующее множество подзапроса содержит хотя бы одну запись.
Следующий запрос определяет, по каким курсам существуют «задолжники»:
SELECT staff, title FROM Course C WHERE EXISTS |
|
|||
( SELECT * FROM CourseResult |
|
|||
WHERE course = C.title AND |
(11.17) |
|||
( result IS NULL OR result < 3 ) ) |
||||
|
|
|
|
|
|
STAFF |
TITLE |
|
|
|
G.Anderson |
Graphics |
|
|
|
G.Anderson |
DBMS |
|
|
Рис. 11.7. Значение запроса 11.17
При использовании подзапросов совместно с предикатом EXISTS конкретный вид выражений после SELECT в подзапросе никакого значения не имеет; однако было бы ошибкой использовать, например, функцию COUNT(*), поскольку в этом случае подзапрос всегда содержал бы одну строку.
При помощи предиката EXISTS удобно находить «несвязанные» записи (т.е. выполнять неявное антисоединение). Ниже приведен подобный запрос, эквивалентный запросу 10.17:
123

SELECT C.title «Курс», staff «Преподавателя» |
|
FROM Course C |
|
WHERE NOT EXISTS |
|
( SELECT * FROM Attends A |
(11.18) |
WHERE C.title = A.title) |
В сравнении с запросом 10.17 текст данного запроса выглядит более очевидным.
С подзапросами связано использование двух предикатов ANY и ALL, которые употребляются в операторах сравнения и в качестве параметра, аналогично предикату IN, используют подзапрос или списочное выражение. Например, следующий запрос определяет студентов с оценкой, которая не является минимальной из существующих (по любому курсу):
SELECT student, course, result FROM CourseResult
WHERE result > ANY |
(11.19) |
||||
( SELECT result FROM CourseResult ) |
|||||
|
|
|
|
|
|
|
STUDENT |
COURSE |
|
RESULT |
|
|
|
|
|
|
|
|
Bill |
C Programming |
|
5 |
|
|
Jane |
OS |
|
5 |
|
Рис. 11.8. Значение запроса 10.19
При вычислении этого запроса сначала вычисляется подзапрос, который является некоррелированным, и формируется список значений, а затем для каждой строки проверяется предикат ANY; если значение поля result текущей записи больше хотя бы одного значения из списка, то значением операции сравнения является TRUE. Обратите внимание, что пустые значения, которые присутствуют в значении подзапроса SELECT result FROM CourseResult, игнорируются.
Может показаться, что запрос 11.19 определяет студентов с максимальной оценкой. Однако это верно только применительно к текущим данным в таблице CourseResult. В ней, за исключением игнорируемых пустых значений в поле result, встречаются только значения 4 и 5, хотя возможно и появление значения 3. В этом случае значение запроса 11.19 не будет совпадать со значением запроса 11.9, который всегда будет возвращать правильный результат. Данное наблюдение иллюстрирует тот факт, что «правильность» некоторого
124

SQL-запроса трудно оценить только методом тестирования, вычисляя его с некоторыми конкретными данными.
Предикат ALL, в отличие от предиката ANY, требует, чтобы условие сравнения выполнялось для всех значений из списка – значений подзапроса. В качестве примера построим запрос для нахождения студентов с максимальной оценкой аналогично запросу 11.9.
SELECT student, course, result FROM CourseResult CR WHERE result >= ALL
( SELECT result FROM CourseResult CR2
HERE CR2.result IS NOT NULL ) |
(11.20) |
|||
|
|
|
|
|
STUDENT |
COURSE |
|
RESULT |
|
Bill |
C Programming |
|
5 |
|
Jane |
OS |
|
5 |
|
Рис. 11.9. Значение запроса 11.20
Обратите внимание, что в запросе 11.20 подзапрос использует ту же самую таблицу, что и основной запрос, поэтому здесь необходимо использовать оператор сравнения >=, поскольку значение этого подзапроса в данном случае всегда включает значение result из левой части. Данный запрос можно переписать таким образом, чтобы для каждой записи внешней таблицы исключать из данных подзапроса эту запись; для этого будет необходимо использовать коррелированный подзапрос. Кроме того, этот запрос определяет максимальную оценку вне зависимости от курса; если необходимо получить максимальные оценки по каждому курсу в отдельности, запрос приобретет существенно более сложный вид:
SELECT student, course, result FROM CourseResult CR WHERE result > ALL
( SELECT result FROM CourseResult CR2 WHERE NOT ( CR.student = CR2.student AND CR.course =
CR2.course ) |
|
AND CR.course = CR2.course |
|
AND CR2.result IS NOT NULL ) |
(11.21) |
AND CR.result IS NOT NULL |
125

STUDENT |
COURSE |
RESULT |
|
|
|
Bill |
C Programming |
5 |
Jane |
OS |
5 |
Jane |
DBMS |
4 |
Рис. 11.10. Значение запроса 11.21
Прежде всего, необходимо обратить внимание на условие
CR.student = CR2.student AND CR.course = CR2.course, которое обычно используется как условие соединения двух таблиц по паре полей. Но в данном случае это условие используется с отрицанием, поскольку цель этого коррелированного подзапроса – построить неявное кросс-соединение таблицы с собой, удаляя для каждой записи внешней таблицы эту же запись из внутренней таблицы, чтобы избежать сравнения записи самой с собой. Проверка CR.result IS NOT NULL необходима, чтобы результирующее множество не содержало сведений о курсах, по которым не выставлено ни одной оценки. Наконец, условие CR.course = CR2.course (пятая строка текста запроса) необходимо, чтобы подзапрос каждый раз рассматривал только записи с одинаковыми названиями курса. Текст данного запроса можно переписать в более короткую форму, упростив логическое выражение (читателю рекомендуется самостоятельно выполнить шаги преобразования этого логического выражения):
SELECT student, course, result FROM CourseResult CR
WHERE result > ALL |
|
( SELECT result FROM CourseResult CR2 WHERE |
|
CR.student != CR2.student |
|
AND CR.course = CR2.course |
|
AND CR2.result IS NOT NULL ) |
(11.22) |
AND CR.result IS NOT NULL |
В конце подраздела рассмотрим еще несколько сложных примеров. Ниже приведены три запроса, одинаковые по значению, которые с содержательной точки зрения определяют, какие студенты посещают оба указанных курса, каждый из которых использует соответственно соединение, подзапросы с предикатом IN и подзапросы с предикатом
EXISTS.
126

SELECT DISTINCT S1.name, S1.class FROM
Student S1 JOIN Attends A1 ON A1.student = S1.name
JOIN Attends A2 ON A2.student = S1.name |
(11.23) |
WHERE A1.title = ‘OS’ AND A2.title = ‘DBMS’ |
|
SELECT name, class FROM Student |
(11.24) |
WHERE name in |
|
(SELECT student FROM Attends WHERE title = ‘OS’ ) AND name in
(SELECT student FROM Attends WHERE title = ‘DBMS’)
SELECT S.name, S.class FROM Student S WHERE EXISTS
(SELECT * FROM Attends WHERE student = S.name AND title = ‘OS’ )
AND EXISTS
(SELECT * FROM Attends WHERE student = S.name
AND title = ‘DBMS’ ) |
(11.25) |
|||
|
|
|
|
|
|
NAME |
CLASS |
|
|
|
Jane |
|
3 |
|
Рис. 11.11. Значение запроса 11.25
Во всех трех вариантах из таблицы Attends производятся две независимые выборки (для каждого курса в отдельности) – либо через повторное соединение, либо при помощи независимых подзапросов; в запросе 11.25 подзапросы коррелированные, поэтому этот вариант является менее предпочтительным.
11.2. Подзапросы после FROM
Подзапросы могут использоваться вместо имен таблиц в SELECT-запросах. При вычислении подобных запросов сначала вычисляется подзапрос, а затем значение подзапроса используется как исходная таблица при вычислении основного запроса. Рекомендуется в таком подзапросе явно указывать имена полей создаваемого результирующего множества, чтобы эти имена можно было далее использовать в выражениях основного запроса.
Следующий простейший пример формально иллюстрирует применение подобных подзапросов:
127

SELECT * FROM ( SELECT * FROM Staff ); |
(11.26) |
Конечно, использование запроса 11.26 вряд ли можно считать целесообразным, поскольку этот запрос эквивалентен обычному запросу SELECT * FROM Staff и здесь приводится исключительно для иллюстрации синтаксиса этого способа использования подзапросов. Следующий запрос 11.27 является более содержательным и демонстрирует применение подзапросов для дополнительного структурирования текста, однако и этот запрос может быть переписан без использования подзапроса.
SELECT «Имя», «Должность», «Количество» FROM
( SELECT S.name «Имя», S.position «Должность», COUNT(*) «Количество»
FROM Staff S, Course C
WHERE C.staff = S.name GROUP BY S.name, S.position )
WHERE «Имя» = ‘Won Kim’ ; |
(11.27) |
|||
|
|
|
|
|
|
Имя |
Должность |
Количество |
|
|
Won Kim |
Full Professor |
2 |
|
Рис. 11.12. Значение запроса 11.27
Следует обратить внимание, что приводимый ниже запрос 11.28 нельзя реализовать без использования подзапросов, поскольку результирующее множество каждого подзапроса группируется отдельно, а затем с ними выполняется соединение. С содержательной точки зрения этот запрос формирует список преподавателей и посещающих их курсы студентов, причем для каждого студента и преподавателя вычисляется количество посещаемых или читаемых ими всех курсов соответственно.
SELECT SC.«Имя», SC.«Должность», SC.«Кол.курсов», AC.«Студент», AC.«Кол.курсов» FROM
( SELECT S.name «Имя», S.position «Должность», COUNT(*) «Кол.курсов»
FROM Staff S JOIN Course C
ON C.staff = S.name
GROUP BY S.name, S.position ) SC JOIN
128

(SELECT A.student «Студент»,C.staff «Преподаватель»,
COUNT(*) «Кол.курсов» |
|
|
|
|
||
FROM Attends A JOIN Course C |
|
|||||
|
ON A.title = C.title |
|
|
|
||
GROUP BY A.student, C.staff |
) AC |
(11.28) |
||||
ON SC.«Имя» = AC.«Преподаватель»; |
|
|
||||
|
|
|
|
|
|
|
Имя |
Должность |
|
Кол. |
|
Студент |
Кол. |
|
курсов |
|
курсов |
|||
|
|
|
|
|
||
G.Anderson |
Senior Lector |
|
3 |
|
Polie |
1 |
G.Anderson |
Senior Lector |
|
3 |
|
Jane |
2 |
G.Anderson |
Senior Lector |
|
3 |
|
Bill |
1 |
Won Kim |
Full Professor |
|
2 |
|
Bill |
1 |
|
|
|
|
|
|
|
Рис. 11.13. Значение запроса 11.28
Выводы
•SQL-запросы могут содержать вложенные SELECTвыражения, называемые подзапросами;
•подзапросы используются в логических выражениях в конструкциях WHERE и HAVING и в качестве «динамических» таблиц в конструкции FROM;
•для многих (но не для всех) запросов с подзапросами можно написать эквивалентный запрос с соединением, и наоборот (но так же не для всех); использование подзапросов способствует повышению наглядности текста запросов;
•подзапросы, использующие данные записей таблиц основного запроса, называются коррелированными; коррелированные запросы вычисляются для каждой записи таблицы основного запроса;
•подзапросы, не использующие данные записей таблиц основного запроса, называются некоррелированными; коррелированные запросы вычисляются один раз для всех записей таблицы основного запроса;
•подзапросы могут использоваться в операциях сравнения, в этом случае подзапрос должен возвращать только одну запись с одним значением;
129
•подзапросы могут использоваться в предикате IN вместо списка значений;
•подзапросы могут использоваться в предикате EXISTS, который в качестве параметра получает подзапрос и возвращает значение TRUE, если результирующее множество подзапроса содержит хотя бы одну запись;
•подзапросы могут также использоваться вместо имен таблиц в SELECT-запросах; при вычислении подобных запросов сначала вычисляется подзапрос, а затем значение подзапроса используется как исходная таблица при вычислении основного запроса;
•множественные операции UNION (объединение), UNION ALL (объединение с сохранением дубликатов), INTERSECT (пересечение) и MINUS (разность) используются для слияния результирующих множеств результирующих множеств двух SELECT-запросов.
Вопросы для контроля
1.Опишите основные виды подзапросов, используемых
вSELECT-запросах.
2.Объясните различия между корректированными и некоррелированными подзапросами.
3.Перечислите ограничения на структуру результирующего множества подзапросов, используемых в операциях сравнения.
4.Опишите отличия между подзапросами, используемыми в предикатах IN и EXISTS.
5.Какие: коррелированные или некоррелированные – подзапросы потенциально являются более эффективными с точки зрения исполнения?
6.Объясните смысл множественных операций.
130