- •5. Сложные запросы
- •5.1. Теоретико-множественные операции
- •5.1.1. Декартово произведение наборов записей
- •5.1.2. Объединение наборов записей
- •5.1.3. Пересечение наборов записей
- •5.1.4. Вычитание наборов записей
- •5.2. Внешние соединения
- •5.2.1. Левое внешнее соединение
- •5.2.2. Правое внешнее соединение
- •5.2.3. Полное внешнее соединение
- •5.3. Подзапросы
- •5.3.1. Простые подзапросы
- •5.3.2. Связанные подзапросы
- •5.4. Представления
- •Лабораторная работа 5
5.3.2. Связанные подзапросы
Связанные (коррелированные) подзапросы позволяют выразить более сложные вопросы относительно сведений, хранящихся в базе данных. При выполнении запросов, содержащих связанные подзапросы, нет такого четкого разделения по времени выполнения между подзапросом и запросом, как в случае простых подзапросов. В случае простых подзапросов сначала выполняется подзапрос, а затем содержащий его запрос. При наличии связанного подзапроса порядок выполнения запроса в целом иной. Основной признак связанного подзапроса заключается в том, что он не может быть выполнен самостоятельно, вне всякой связи с основным запросом. Формально этот признак обнаруживается в выражении сложного запроса следующим образом: подзапрос ссылается на таблицу, которая упоминается в основном запросе.
Рассмотрим некоторый абстрактный и, в то же время, типичный запрос, содержащий связанный подзапрос:
SELECT T1.A
FROM T1
WHERE T1.B = (SELECT T2.B
FROM T2
WHERE T2.C = T1.C);
Данный запрос на выборку данных содержит подзапрос, сформулированный в выражении, размещенном в основном запросе после ключевого слова WHERE. Запрос в целом использует две таблицы: T1 и T2, в которых есть столбцы с одинаковыми именами B и C и одинаковыми типами. Подзапрос (SELECT T2.B FROM T2 WHERE T2.C = T1.C) обращается к этим же таблицам. Поскольку одна из таблиц (T1) фигурирует как в подзапросе, так и во внешнем запросе, то подзапрос нельзя выполнить самостоятельно, вне связи с внешним запросом. Поэтому выполнение запроса в целом (т.е. внешнего запроса) происходит следующим образом:
Сначала выделяется первая запись из таблицы T1, указанной в операторе FROM внешнего запроса (вся запись таблицы T1, а не только значение столбца A). Эта запись называется текущей. Значения столбцов для этой записи доступны и могут быть использованы в подзапросе.
Затем выполняется подзапрос, который возвращает список значений столбца B таблицы T2 в тех записях, в которых значение столбца C равно значению столбца C из таблицы T1.
Будем считать, что в этом примере подзапрос возвращает единственное значение. Если это не так, то потребуется использование, например, предикатов вместо оператора сравнения (=). Теперь выполняется оператор WHERE основного запроса. Если значение столбца B в текущей (выделенной) записи таблицы T1 равно значению, возвращенному подзапросом, то эта запись выделяется внешним запросом.
Оператор SELECT внешнего запроса выполняет проверку условия своего оператора WHERE. Если оно истинно, то значение столбца A текущей записи таблицы T1 помещается в результатную таблицу, в противном случае запись игнорируется. Затем происходит переход к следующей записи таблицы T1. Теперь для нее выполняется подзапрос. Аналогичным образом все описанное происходит для каждой записи таблицы T1.
Получим, например, список покупателей, когда-либо делавших покупки:
SELECT last_name, first_name
FROM customers
WHERE (SELECT count(isbn)
FROM shipments
WHERE customer_id = customers.id) >= 1;
Как известно, запрос возвращает одну или несколько записей либо не возвращает ничего. Рассмотрим пример, в котором требуется проверка существования записей. Так, иногда требуется выборка записей из одной таблицы при условии, что в другой таблице существует хотя бы одна соответствующая запись.
Итак, получим список покупателей, когда-либо делавших покупки:
SELECT last_name, first_name
FROM customers
WHERE EXISTS (SELECT isbn
FROM shipments
WHERE customer_id = customers.id);
Аналогично, чтобы получить список покупателей, никогда не делавших покупки, можно воспользоваться следующим запросом:
SELECT last_name, first_name
FROM customers
WHERE NOT EXISTS (SELECT isbn
FROM shipments
WHERE customer_id = customers.id);
Предикат UNIQUE имеет такой же смысл, как и EXISTS, но при этом для его истинности требуется, чтобы все записи в результатной таблице не только существовали, но и были уникальны (т.е. не повторялись).
Предикат DISTINCT почти такой же, как и UNIQUE. Отличие этих предикатов обнаруживается применительно к значениям NULL. Так, если в результатной таблице все записи уникальны (предикат UNIQUE истинен), то и предикат DISTINCT тоже истинен. С другой стороны, если в результатной таблице имеются хотя бы две неопределенные записи, то предикат DISTINCT ложен, хотя предикат UNIQUE истинен.