Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Сложные запросы ч6.doc
Скачиваний:
4
Добавлен:
09.09.2019
Размер:
70.66 Кб
Скачать

Работа со списком значений из одного столбца

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

  • Товар (ID_системы, Название, Описание, Цена);

  • Компонент (ID_компонента, Название, Описание);

  • Состав (ID_системы, ID_компонента).

Допустим, нас интересует список систем, в которых имеется некоторый компонент, скажем, микропроцессор. Для этой цели подойдет сложный запрос с предикатом IN (в):

SELECT ID_системы FROM Состав

WHERE ID_компонента IN

( SELECT ID_компонента FROM Компонент

WHERE Название = 'Микропроцессор' );

Сначала выполняется подзапрос, возвращающий список идентификаторов компонентов, имеющих название 'микропроцессор'. Далее, внешний запрос сравнивает значение идентификатора компонента из каждой записи таблицы Состав с полученным списком. Если сравнение успешно (сравниваемое значение имеется в списке), то идентификатор системы из той же записи добавляется в результатную таблицу.

Теперь сформулируем запрос, возвращающий список систем, в которых нет заданного компонента, например, микропроцессора. В этом запросе будет два подзапроса с использованием ключевых слов IN И NOT (не в):

SELECT ID_системы FROM Состав

WHERE ID_системы NOT IN

(SELECT ID_системы FROM Состав

WHERE ID_компонента IN

( SELECT ID_компонента FROM Компонент

WHERE Название='Микропроцессор' ));

Разумеется, это один из возможных вариантов запросов, возвращающих требуемые данные.

Работа с набором записей

В полнофункциональных базах данных подзапрос можно вставлять не только в операторы WHERE и HAVING но и в оператор FROM.

Трудно однозначно сказать, зачем это надо делать, но, тем нее, следующая конструкция работает:

SELECT T.cтолбец1, T.столбец2, ... , T.столбецN

FROM(SELECT………..)T

WHERE ...........;

Здесь таблице, возвращаемой подзапросом в операторе FROM,-- присваивается псевдоним T, а внешний запрос выделяет столбцы этой таблицы и, возможно, записи в соответствии с некоторым условием, которое указано в операторе WHERE.

Связанные подзапросы

Связанные (коррелированные) подзапросы с практической точки зрения наиболее интересны, поскольку позволяют выразить более сложные вопросы относительно сведений, хранящихся в базе данных. Однако для их применения важно понимать, что именно будет делать СУБД для предоставления ответа. Для начала достаточно уяснить, что при выполнении запросов, содержащих связанные подзапросы, нет такого четкого разделения во времени выполнения между подзапросом и запросом, как в случае простых подзапросов. Напомню, что в случае простых подзапросов сначала выполняется подзапрос, а затем содержащий его запрос. В случае связанных подзапросов порядок выполнения запроса в целом иной, и его желательно понимать, чтобы избежать недоразумений. Основной признак связанного подзапроса заключается в том, что он не может быть выполнен самостоятельно, вне всякой связи с основным запросом. Формально этот признак обнаруживается в выражении сложного запроса следующим образом: подзапрос ссылается на таблицу, которая упоминается а основном запросе. Следовательно, подзапрос должен быть выполнен в некоем контексте с текущим состоянием выполнения основного запроса. Рассмотрим это на следующих примерах.

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

SELECT A FROM Tl

WHERE В =

( SELECT В FROM T2 WHERE С = T1.С );

Данный запрос на выборку данных (поскольку он начинается с оператора SELECT) содержит подзапрос, сформулированный в выражении, размещенном в основном запросе после ключевого слова WHERE.

Очевидно, данный запрос в целом использует две таблицы: T1 и T2, в которых есть столбцы с одинаковыми именами B и C и одинаковыми типами. Подзапрос, расположенный в выражении после ключевого слова WHERE основного запроса (SELECT В FROM T2 WHERE C = T1.C), обращается к этим же таблицам. Поскольку одна из таблиц (T1) фигурирует как в подзапросе, так и во внешнем запросе, то подзапрос нельзя выполнить самостоятельно, вне связи с внешним запросом. Поэтому выполнение запроса в целом (т. е. внешнего запроса) происходит следующим образом:

1. Сначала выделяется первая запись из таблицы T1, указанной. в операторе FROM внешнего запроса (вся запись таблицы Tl, а не только значение столбца A). Эта запись называется текущей. Значения столбцов для этой записи доступны и могут быть использованы в подзапросе.

2. Затем выполняется подзапрос, который возвращает список значений столбца В таблицы T2 в тех записях, в которых значение столбца C равно значению столбца C из таблицы T1.

3. Запрос, сформулированный в рассматриваемом примере, предполагает, что его подзапрос возвращает единственное значение (список с единственным элементом), поскольку в операторе WHERE используется оператор сравнения (=). Если это не так, то потребуется использование, например, предикатов. Как бы то ни было, считаем, что в этом примере подзапрос возвращает единственное значение. Теперь выполняется оператор WHERE основного запроса. Если значение столбца В в текущей (выделенной) записи таблицы T1 равно значению, возвращенному подзапросом, то эта запись выделяется внешним запросом.

4. Оператор SELECT внешнего запроса выполняет проверку условия своего оператора WHERE. А именно он проверяет, равно ли текущее значение столбца в таблицы т1 значению, возвращенному подзапросом. Если да, то значение столбца А текущей записи таблицы т1 помещается в результатную таблицу, в противном случае запись игнорируется. Затем происходит переход к следующей записи таблицы т1. Теперь для нее выполняется подзапрос. Аналогичным образом все описанное происходит для каждой записи таблицы т1.

Примечание

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

SELECT Tl.A FROM Tl

WHERE Tl.в =

( SELECT Т2.в FROM T2 WHERE T2.С = T1.С ) t

Теперь обратимся к более или менее конкретным примерам

Нередко торгующие фирмы делают своим клиентам скидки, размер которых зависит от суммы покупки (заказа). Предположим, размеры скидок (коэффициенты) заданы в виде таблицы Скидки. В этой таблице содержатся границы диапазонов суммы покупки и соответствующие коэффициенты скидки. Понятно, что чем больше сумма покупки, тем больше: коэффициент скидки (размер скидки равен сумме покупки, умноженной на коэффициент скидки). Тогда, чтобы узнать, какова.скидка для клиента, скажем, Захарова, сведения о котором находятся в другой таблице, например, Клиенты, можно воспользоваться следующим запросом:

SELECT Скидка FROM Скидки WHERE Мин сумма <=

( SELECT Сумма заказа FROM Клиенты WHERE Имя = 'Захаров' )

AND

Макс сумма >=

( SELECT Сумма заказа FROM Клиенты WHERE Имя = 'Захаров' );

Здесь подзапрос состоит из двух аналогичных простых запросов, связанных логическим союзом AND (и). Таким образом, подзапрос является составным. Запрос в целом выполняется следующим образом. Сначала в таблице Скидки выделяется первая запись. Далее выполняются два идентичных подзапроса, возвращающие значение столбца Сумма заказа таблицы клиенты при условии, что столбец имя имеет значение 'Захаров'. Предполагается, что подзапрос возвращает единственное значение. Если это не так, то в операторе SELECT вместо имени столбца следовало бы написать функцию SUM(сумма заказа). Результаты подзапросов сравниваются с текущими границами диапазона скидок. Если оба сравнения успешны (истинны), то внешний запрос возвращает текущее значение коэффициента скидки. В противном случае в таблице Скидки происходит переход к следующей записи, и все повторяется, как описано ранее.

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

Пусть в базе данных имеются:

  • таблица продажи, содержащая данные о продажах товаров некоторой фирмы, в том числе столбец ID_клиента (идентификатор клиента);

  • таблица контакты, содержащая данные о покупателях (ID_клиента, Имя, Адрес, Телефон), но не содержащая сведений об их покупках.

Требуется получить сведения о клиентах, сделавших хотя бы одну покупку. Далее приведен запрос, выполняющий данное задание:

SELECT Имя, Адрес, Телефон FROM Контакты

WHERE EXlSTS

( SELECT DISTINCT ID_клиента FROM Продажи

WHERE Продажи.ID_клиента = Контакты.ID_клиента );

Здесь предикат EXlSTS (существует) принимает значение true, если подзапрос возвращает хотя бы одну запись, и тогда внешний запрос возвращает имя клиента и его данные для контакта. Поскольку в запросе требуется проверка существования записей, возвращаемых подзапросом, а не сами записи, то приведенное SQL-выражение можно несколько упростить:

SELECT Имя, Адрес, Телефон FROM Контакты

WHERE EXlSTS

( SELECT DISTINCT 1 FROM Продажи

WHERE Продажи.ID_клиента = Контакты.ID_клиента );

Здесь, чтобы выполнить подзапрос, содержимое результата которого для нас не важно, вместо имени столбца вставлена просто цифра 1. Разумеется, в данном случае можно было бы использовать и любое другое число. Это своего рода "фокус-покус".

Связанные подзапросы могут относиться к той же таблице, и внешний запрос. В этих случаях для одной и той же таблицы используются различные псевдонимы, чтобы показать, из каких записей следует выбирать требуемые значения. Кроме того, подзапросы могут содержаться не только в операторе WHERE, но и в операторе HAVING, который обычно (но не всегда) используется вместе с оператором группировки GROUP BY.

В следующем примере из таблицы продажи выбирается список тех клиентов, у которых максимальная сумма заказов как минимум в 1,5 раза больше среднего размера заказов остальных клиентов:

SELECT T1. ID_клиента FROM Продажи T1

GROUP BY T1.ID_клиента

HAVING MAX(T1.Сумма_заказа) > ALL

( SELECT 1.5*AVG(T2.Cумма_заказа) FROM Продажи T2

WHERE T1.ID_клиента <> Т2.ID_клиента );

Данный запрос работает следующим образом:

1. Записи таблицы продажи группируются по значениям столбца ID_клиента.

2. Получившиеся группы обрабатываются оператором HAVING: для каждой группы вычисляется максимальное значение столбца Сумма_заказа (MAX (T1. Сумма_заказа) ).

3. Подзапрос дважды проверяет среднее значение столбца Сумма заказа для всех тех записей, в которых значение ID_клиента отличается от значения этого же столбца в текущей группе внешнего запроса (WHERE T1.ID_клиента <> T2.ID_клиента). Поэтому для одной и той же таблицы использовались два различных псевдонима.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]