Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Подзапросы: запросы внутри запросов

01.01.2007

Подзапросы: Запросы внутри запросов

 

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

В этой главе обсуждаются следующие темы:

 

·Что такое подзапросы;
·Типы подзапросов;
·Выражения с подзапросами;
·Квантифицированные предикаты с подзапросами;
·Коррелирующиеся подзапросы.
 

Что такое подзапросы
 

Подзапросы обычно содержатся в предложениях where или having SQL оператора или в списке выбора этого оператора. С помощью подзапросов можно проводить дальнейший отбор данных из результатов других запросов. Оператор, содержащий подзапрос, может обрабатывать строки некоторой таблицы, основываясь на результатах вычисления списка выбора подзапроса, который в свою очередь может ссылаться на эту же таблицу как внешний запрос, или обращаться к другой таблице. В языке Transact-SQL подзапросы допускаются почти везде, где допускаются выражения,  если подзапрос возвращает одно значение.

Операторы выбора, содержащие один или несколько подзапросов, называются также составными запросами или составными операторами выбора. Возможность включения одного оператора выбора внутрь другого является одной из причин, по которой язык SQL называется “структурированным” (Structured Query Language).

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

 

Примеры использования подзапросов
 

Если нужно найти все книги, имеющие ту же цену, что и книга Straight Talk About Computers, то это можно сделать за два шага. Во-первых найти цену этой книги:

 

select price

from titles

where title = "Straight Talk About Computers"

 

price

-------------

      $19.99

 

(Выбрана 1 строка)

 

Затем, используя этот результат во втором запросе, уже можно найти все книги, имеющие ту же стоимость, что и Straight Talk:

 

select title, price

from titles

where price = $19.99

 

title                                                               price

-------------------------------------------------------------        --------

The Busy Executive's Database Guide                          19.99

Straight Talk About Computers                                19.99

Silicon Valley Gastronomic Treats                            19.99

Prolonged Data Deprivation: Four Case Studies        19.99

 

(Выбрано 4 строки)

 

С помощью подзапроса эта задача решается одним оператором:

 

select title, price

from titles

where price =

  (select price

   from titles

   where title = "Straight Talk About Computers")

 

title                                                               price

--------------------------------------------------------             --------

The Busy Executive's Database Guide                  19.99

Straight Talk About Computers                        19.99

Silicon Valley Gastronomic Treats                            19.99

Prolonged Data Deprivation: Four Case Studies        19.99

 

(Выбрано 4 строки)

 

Общие правила написания и синтаксис подзапросов

 

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

 

(select [distinct] список_выбора_подзапроса

[from [[database.]owner.]{название_таблицы | название_вьювера}

[({index название_индекса | prefetch size |[lru|mru]})]}

       [holdlock | noholdlock] [shared]

    [,[[database.]owner.]{ название_таблицы | название_вьювера }

[({index название_индекса | prefetch size |[lru|mru]})]}

       [holdlock | noholdlock] [shared]]... ]

[where условия_отбора]

[group by выражение_без_агрегации [,

            выражение_без_агрегации]... ]

[having условия_отбора])

 

Подзапросы могут быть вложенными в конструкциях (предложениях) where или having внешних операторов выбора (select), вставки (insert), обновления (update) или удаления (delete), а также вложенными в другие подзапросы или помещены в список выбора.

В языке Transact-SQL подзапрос можно помещать почти везде, где допустимы выражения, если этот подзапрос возвращает единственное значение в качестве результата.

 

Ограничения на подзапросы
 

На подзапросы накладываются следующие ограничения:

 

·Подзапросы нельзя использовать в списках предложений order by, group by и compute by.
·Подзапрос не может содержать предложения for browse или union.
·Список выбора внутреннего подзапроса, которому предшествует операция сравнения, может содержать только одно выражение или название столбца, и подзапрос должен возвращать единственный результат. При этом тип данных столбца, указанного в конструкции where внешнего оператора, должен быть совместим c типом данных в столбце, указанным в списке выбора подзапроса (правила здесь такие же как и при соединении).
·В подзапросах не допускаются текстовые (text) и графические (image) данные.
·Подзапросы не могут обрабатывать свои результаты внутренним образом, т.е. подзапрос не может содержать конструкций order by, compute, или ключевого слова into.
·Коррелирующиеся (повторяющиеся) подзапросы не допускаются в конструкции select обновляемого курсора, определенного с помощью declare cursor (определить курсор).
·Количество вложенных уровней для подзапросов не должно превышать 16.
·Максимальное число подзапросов на каждой стороне объединения не больше 16.
 

Расширенные названия столбцов
 

В следующем примере столбец pub_id в конструкции where внешнего запроса неявно определяется таблицей publishers из конструкции from этого запроса. Обращение к столбцу pub_id в списке выбора подзапроса определяется конструкцией from подзапроса, т.е. таблицей titles:

 

select pub_name

from publishers

where pub_id in

  (select pub_id

   from titles

   where type = "business")

 

Общее правило таково: названия столбцов в операторе неявно определяются таблицей, которая указана в конструкции from этого уровня вложенности.

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

 

select pub_name

from publishers

where publishers.pub_id in

  (select titles.pub_id

   from titles

   where type = "business")

 

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

 

Подзапросы с коррелирующимися названиями 
 

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

Например, с помощью следующего подзапроса можно найти писателей, живущих в одном городе с Ливией Карсен:

 

select au1.au_lname, au1.au_fname, au1.city

from authors au1

where au1.city in

  (select au2.city

   from authors au2

   where au2.au_fname = "Livia"

   and au2.au_lname = "Karsen")

 

au_lname            au_fname            city

-----------        --------------     -----------

Green               Marjorie            Oakland

Straight            Dick                Oakland

Stringer            Dirk                Oakland

MacFeather          Stearns             Oakland

Karsen              Livia               Oakland

 

(Выбрано 5 строк)

 

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

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

 

select au_lname, au_fname, city

from authors

where city in

  (select city

   from authors

   where au_fname = "Livia"

   and au_lname = "Karsen")

 

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

 

select au1.au_lname, au1.au_fname, au1.city

from authors au1, authors au2

where au1.city = au2.city

and au2.au_lname = "Karsen"

and au2.au_fname = "Livia"

 

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

 

Несколько уровней вложенности
 

Подзапрос может содержать в себе один или несколько подзапросов следующего уровня. Оператор может содержать подзапросы 16 уровней вложенности.

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

 

select au_lname, au_fname

from authors

where au_id in

  (select au_id

   from titleauthor

   where title_id in

      (select title_id

       from titles

       where type = "popular_comp") )

 

au_lname                      au_fname

----------------------       ------------

Carson                        Cheryl

Dull                          Ann

Hunter                        Sheryl

Locksley                      Chastity

 

(Выбрано 4 строки)

 

Самый внешний запрос выбирает имена и фамилии всех писателей. Запрос следующего уровня находит идентификационные номера писателей, а самый внутренний запрос возвращает идентификационные номера книг PC1035, PC8888 и PC9999.

Этот запрос также можно выразить с помощью соединения:

 

select au_lname, au_fname

from authors, titles, titleauthor

where authors.au_id = titleauthor.au_id

and titles.title_id = titleauthor.title_id

and type = "popular_comp"

 

Подзапросы в операторах модификации удаления и вставки 
 

Подзапроcы могут быть вложенными в операторах  модификации (update), удаления (delete) и вставки (insert) так же, как и в операторе выбора.

 

Замечание: Выполнение следующих примеров изменит содержимое базы данных pubs2. Следует обратиться к системному администратору, чтобы получить исходную копию этой базы.

 

В следующем запросе удваиваются цены всех книг, изданных компанией New Age Books. Этот оператор модифицирует таблицу titles, а подзапрос обращается к таблице publishers.

 

update titles

set price = price * 2

where pub_id in

  (select pub_id

   from publishers

   where pub_name = "New Age Books")

 

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

 

update titles

set price = price * 2

from titles, publishers

where titles.pub_id = publishers.pub_id

and pub_name = "New Age Books"

 

Можно удалить все записи о продажах книг по бизнесу с помощью следующего вложенного оператора выбора:

 

delete salesdetail

where title_id in

  (select title_id

   from titles

   where type = "business")

 

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

 

delete salesdetail

from salesdetail, titles

where salesdetail.title_id = titles.title_id

and type = "business"

 

Подзапросы в условных операторах
 

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

 

if exists (select title_id

   from titles

   where type = "business")

begin

   delete salesdetail

   where title_id in

     (select title_id

      from titles

      where type = "business")

end

 

Использование подзапросов на месте выражений
 

В языке Transact-SQL подзапрос можно подставлять почти в любое место в операторах выбора, модификации, вставки и удаления, где может размещаться выражение. Подзапросы нельзя использовать в списках выбора предложения order by. Ниже приведены некоторые примеры, которые показывают, как правильно  использовать это расширение языка Transact-SQL.

В следующем запросе выбираются названия и типы книг, которые были написаны авторами, живущими в Калифорнии, и изданные там же:

 

select title, type

from titles

where title in

  (select title

   from titles, titleauthor, authors

   where titles.title_id = titleauthor.title_id

   and titleauthor.au_id = authors.au_id

   and authors.state = "CA")

and title in

  (select title

   from titles, publishers

   where titles.pub_id = publishers.pub_id

   and publishers.state = "CA")

 

title                                                        type

--------------------------------------------------         ------------------

The Busy Executive's Database Guide        business

Cooking with Computers:

                 Surreptitious Balance Sheets         business

Straight Talk About Computers                        business

But Is It User Friendly?                       popular_comp

Secrets of Silicon Valley                          popular_comp

Net Etiquette                                       popular_comp

 

(Выбрано 6 строк)

 

В следующем операторе выбираются  названия всех книг, которых было продано более 5000 экземпляров, их цены, и цена самой дорогой книги:

 

select title, price,

   (select max(price) from titles)

   from titles

   where total_sales > 5000

 

title                                                        price

-----------------------------------                     -------     -------

You Can Combat Computer Stress!               2.99        22.95

The Gourmet Microwave                           2.99         22.95

But Is It User Friendly?                            22.95    22.95

Fifty Years in Buckingham Palace        11.95    22.95

                  Kitchens

Типы подзапросов
 

Существуют два основных типа подзапросов:

 

·Подзапросы, которым предшествует немодифицированная операция сравнения и которые возвращают единственное значение, называются подзапросами- выражениями (скалярными подзапросами).
·Подзапросы, которые возвращают список значений и которым предшествует  ключевое слово in (принадлежит) или операция сравнения, модифицированная кванторами  any (некоторый) или all (все), а также подзапросы, проверяющие существование  с помощью квантора exists (существует), называются квантифицированными предикатными подзапросами.
 

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

 

·Некоррелированный подзапрос может вычисляться как независимый запрос. Иначе говоря, результаты подзапроса подставляются в основной оператор (или внешний запрос). Это не значит, что SQL-сервер именно так выполняет операторы с подзапросами. Некорреляционные подзапросы могут быть заменены соединением и будут выполняться как соединения SQL-сервером.
 

·Коррелированные подзапросы не могут выполняться как независимые запросы, поскольку они могут обращаться к данным, находящихся в столбцах таблицы, указанной в списке from внешнего запроса. Коррелированные подзапросы детально обсуждаются в конце этой главы.
 

В следующих разделах этой главы  рассматриваются различные типы подзапросов.

 

Подзапросы-выражения
 

Подзапросам-выражениям предшествует одна из операций сравнения =, !=, <>, >, >=, <, !< или <= и они имеют следующую общую форму:

 

[Начало оператора выбора, вставки, модификации, удаления или подзапроса]

where выражение операция_сравнения (подзапрос)

[Конец оператора выбора, вставки, модификации, удаления или подзапроса]

 

Подзапрос, которому предшествует немодифицированная операция сравнения, т.е. операция сравнения без квантора any или all, должен возвращать единственное значение. В противном случае SQL-сервер выдает сообщение об ошибке.  

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

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

 

select au_lname, au_fname

from authors

where city =

  (select city

   from publishers

   where pub_name = "Algodata Infosystems")

 

au_lname       au_fname

--------------     --------------

Carson         Cheryl

Bennet         Abraham

 

(Выбраны 2 строки)

 

Использование скалярных аггрегирующих функций для достижения единственности результата
 

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

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

 

select title

from titles

where price >

  (select min(price)

   from titles)

 

title

---------------------------------------------------

The Busy Executive's Database Guide

Cooking with Computers: Surreptitious Balance Sheets

Straight Talk About Computers

Silicon Valley Gastronomic Treats

But Is It User Friendly?

Secrets of Silicon Valley

Computer Phobic and Non-Phobic Individuals: Behavior Variations

Is Anger the Enemy?

Life Without Fear

Prolonged Data Deprivation: Four Case Studies

Emotional Security: A New Algorithm

Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean

Fifty Years in Buckingham Palace Kitchens

Sushi, Anyone?

 

(Выбрано 14 строк)

 

Предложения group by и having в подзапросах-выражениях
 

Поскольку подзапросы, которым предшествует немодифицированная операция сравнения, должны возвращать скалярную величину, то обычно они не могут включать предложений group by и having, за исключением случая, когда в результате группировки действительно получается одна группа.

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

 

select title

from titles

where price >

  (select min(price)

   from titles

   group by type

   having type = "trad_cook")

 

Использование опции distinct в подзапросе-выражении
 

Подзапросы, которым предшествует немодифицированная операция сравнения, часто содержат ключевое слово distinct (различные), чтобы в результате получалась скалярная величина.

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

 

select pub_name from publishers

   where pub_id =

       (select distinct pub_id

       from titles

       where pub_id  = publishers.pub_id)

 

Квантифицированные предикатные подзапросы
 

Квантифицированные подзапросы - это подзапросы, возвращающие несколько значений (или никаких значений), которым в предложениях where или having, предшествует один из кванторов any (некоторый), all (все), in (в) или exist (существует). Кванторы any и all модифицируют операции сравнения.

 

·Подзапросы, которым предшествует модифицированная операция сравнения, и которые могут  содержать предложения where или having, имеют следующий общий вид:
 
[ Начало оператора выбора, вставки, модификации, удаления или подзапроса]
       where выражение операция_сравнения [any | all] (подзапрос)
[ Конец оператора выбора, вставки, модификации, удаления или подзапроса]
 

·Подзапросы, которым предшествует ключевые слова in (принадлежит) и not in (не принадлежит) имеют следующий общий вид:
 
[ Начало оператора выбора, вставки, модификации, удаления или подзапроса]
       where выражение [not] in (подзапрос)
[ Конец оператора выбора, вставки, модификации, удаления или подзапроса]
 

·Подзапросы с кванторами exists (существует) и not exists (не существует), проверяющие существование некоторых значений, имеют следующий общий вид:
 
[ Начало оператора выбора, вставки, модификации, удаления или подзапроса]
       where [not] exists (подзапрос)
[ Конец оператора выбора, вставки, модификации, удаления или подзапроса]
 

Хотя ключевое слово distinct допускается в подзапросах с кванторами, тем не менее при исполнении оно игнорируется, т.е. выполнение происходит также, как и при его отсутствии.

 

Подзапросы с кванторами any и all
 

Ключевые слова all и any модифицируют операцию сравнения, которая формирует подзапрос.

Рассмотрим в качестве примера операцию > (больше):

 

·>all означает больше чем любое значение или, что равносильно, больше максимальной величины. Например, >all (1,2,3) означает больше чем 3.
·>any означает больше, по крайней мере, одного значения или, что равносильно, больше минимальной величины. Поэтому, >any (1,2,3) означает меньше 1.
 

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

Использование кванторов all и any требует внимательности, поскольку компьютер не допускает двусмысленности, которая свойственна этим словам в обычном языке. Например, можно задать вопрос: “По каким книгам выплачен аванс, больший чем по любой (any) книге, опубликованной издательством New Age Books?”.

Этот вопрос можно перефразировать в SQL в более точной форме: “По каким книгам выплачен аванс, больший чем максимальный аванс, выплаченный издательством New Age Books?”. В данном случае нужно использовать ключевое слово all (все), а не any:

 

select title

from titles

where advance > all

  (select advance

   from publishers, titles

   where titles.pub_id = publishers.pub_id

   and pub_name = "New Age Books")

 

title

----------------------------------------

The Gourmet Microwave

 

(Выбрана одна строка)

 

Для каждой книги внешний запрос выбирает название и аванс из таблицы titles и сравнивает его со всеми авансами, выплаченными издательством New Age Books, которые возвращаются подзапросом. Внешний запрос находит максимальное значение в этом списке и определяет является ли аванс по данной книге большим этого максимального значения.

 

>all означает больше чем все значения
 

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

Например, чтобы найти книги, которые стоят больше чем самая дорогая книга в группе mod_cook, нужно сделать следующий запрос: 

 

select title from titles where price > all

   (select price from titles

   where type =  "mod_cook")

 

title

---------------------------------------------------

But Is It User Friendly?

Secrets of Silicon Valley

Computer Phobic and Non-Phobic Individuals: Behavior Variations

Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean

 

(Выбрано 4 строки)

 

Однако, если множество значений, возвращаемое подзапросом, содержит неопределенное значение NULL, то запрос возвращает 0 строк (пустое множество), поскольку невозможно сравнить конкретную величину с неопределенным значением.

Например, можно попытаться найти книги, которые стоят больше чем самая дорогая книга в группе popular_comp:

 

select title from titles where price > all

   (select price from titles

   where title_id = "popular_comp")

 

title

---------------------------------------------------

(0 rows affected)

 

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

 

=all означает равно каждому значению
 

Квантор =all означает равенство каждому возвращаемому значению. Текущая строка будет удовлетворять условию, указанному во внешнем запросе, если значение в сравниваемом столбце будет равно каждому значению, которое возвращается подзапросом.

Например, в следующем запросе находятся авторы, живущие в одном городе, путем сравнения их почтовых индексов:

 

select au_fname, au_lname, city

from authors

where city = all

     (select city

     from authors

     where postalcode like "946%")

 

>any означает больше, по крайней мере, одного значения
 

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

В следующем запросе приводится пример подзапроса, в котором операция сравнения модифицируется квантором any. В нем ищутся все книги, по которым выплачен аванс, больший, чем некоторый аванс, выплаченный издательством New Age Books.

 

select title

from titles

where advance > any

  (select advance

   from titles, publishers

   where titles.pub_id = publishers.pub_id

   and pub_name = "New Age Books")

 

title

---------------------------------------------------

Sushi, Anyone?

Life Without Fear

Is Anger the Enemy?

The Gourmet Microwave

But Is It User Friendly?

Secrets of Silicon Valley

Straight Talk About Computers

You Can Combat Computer Stress!

Emotional Security: A New Algorithm

The Busy Executive's Database Guide

Fifty Years in Buckingham Palace Kitchens

Cooking with Computers: Surreptitious Balance Sheets

Computer Phobic and Non-Phobic Individuals: Behavior Variations

Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean

 

(Выбрано 14 строк)

 

Для каждой книги, выбираемой во внешнем запросе, внутренний запрос возвращает список выплаченных авансов издательством New Age Books. Во внешнем запросе определяется, существует ли в этом списке значение, меньшее аванса, выплаченного за рассматриваемую книгу. Другими словами, в этом примере ищутся книги с авансом большим, чем наименьший аванс, выплаченный издательством New Age Books.

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

 

=any означает равно некоторому значению
 

Квантор =any означает проверку существования, поэтому он эквивалентен условию in (в). Например, чтобы найти всех авторов, которые живут в одном городе с некоторым издателем, можно использовать как =any, так и in:

 

select au_lname, au_fname

from authors

where city = any

  (select city

   from publishers)

 

select au_lname, au_fname

from authors

where city in

  (select city

   from publishers)

 

au_lname       au_fname

--------------     --------------

Carson         Cheryl

Bennet         Abraham

 

(Выбраны 2 строки)

 

Однако, квантор !=any будет не равносилен условию not in (не в). Квантор !=any означает “не а или не в или не с”, в то время как условие not in означает “не а и не в и не с”.

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

 

select au_lname, au_fname

from authors

where city != any

  (select city

   from publishers)

 

В результате будут перечислены все 23 автора, поскольку каждый автор живет в городе, где нет некоторого издательства.

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

Посмотрим, что произойдет, если в тот же самый запрос вставить условие not in:

 

select au_lname, au_fname

from authors

where city not in

  (select city

   from publishers)

 

au_lname                      au_fname

--------------                           ------------

del Castillo                   Innes

Blotchet-Halls                 Reginald

Gringlesby                     Burt

DeFrance                       Michel

Smith                          Meander

White                          Johnson

Greene                         Morningstar

Green                          Marjorie

Straight                       Dick

Stringer                       Dirk

MacFeather                     Stearns

Karsen                         Livia

Dull                           Ann

Hunter                         Sheryl

Panteley                       Sylvia

Ringer                         Anne

Ringer                         Albert

Locksley                       Chastity

O'Leary                        Michael

McBadden                       Heather

Yokomoto                       Akiko

 

(Выбрана 21 строка )

 

Это как раз тот результат, который необходимо было получить. Он включает всех авторов, за исключением Cheryl Carson и Abraham Bennet, которые живут в Беркли, где расположено издательство Algodata Infosystem.

Тот же результат можно получить с помощью квантора !=all, который эквивалентен условию not in:

 

select au_lname, au_fname

from authors

where city != all

  (select city

   from publishers)

 

Подзапросы с условием in
 

Подзапросы с ключевым словом in (в) возвращают список значений, который может быть пустым. Например, в следующем запросе ищутся названия издательств, которые опубликовали книги по бизнесу:

 

select pub_name

from publishers

where pub_id in

  (select pub_id

   from titles

   where type = "business")

 

pub_name

------------------------------

New Age Books

Algodata Infosystems

 

(Выбраны 2 строки)

 

Этот оператор вычисляется за два шага. На первом шаге внутренний запрос возвращает список номеров издателей, которые печатают книги по бизнесу, а именно номера 1389 и 0736. На втором шаге эти величины подставляются во внешний запрос, чтобы найти названия этих издательств в таблице publishers. На этом шаге запрос выглядит следующим образом:

 

select pub_name

from publishers

where pub_id in ("1389", "0736")

 

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

 

select pub_name

from publishers

where "business" in

  (select type

   from titles

   where pub_id = publishers.pub_id)

 

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

 

select distinct pub_name

from publishers, titles

where publishers.pub_id = titles.pub_id

and type = "business"

 

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

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

 

select pub_name, title

from publishers, titles

where publishers.pub_id = titles.pub_id

and type = "business"

 

pub_name                  title

----------------------------   -----------------------------------------------------

Algodata Infosystems   The Busy Executive's Database Guide

Algodata Infosystems   Cooking with Computers: Surreptitious       Balance Sheets

New Age Books           You Can Combat Computer Stress!

Algodata Infosystems   Straight Talk About Computers

 

(Выбрано 4 строки)

 

Далее рассмотрим еще один пример, который можно сформулировать как через подзапрос, так и через соединение. На естественном языке этот запрос формулируется следующим образом: “Найти имена всех вторых авторов, кто живет в Калифорнии и получил менее 30 процентов гонорара за книгу”. С использованием подзапроса, оператор будет выглядеть следующим образом:

 

select au_lname, au_fname

from authors

where state = "CA"

and au_id in

  (select au_id

   from titleauthor

   where royaltyper < 30

   and au_ord = 2)

 

au_lname                  au_fname

------------------------      ------------

MacFeather               Stearns

 

(Выбрана 1 строка)

 

Внешний запрос формирует список из 15 авторов, живущих в Калифорнии. Затем выполняется внутренний запрос и формируется список авторов, которые удовлетворяют всем условиям.

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

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

 

select au_lname, au_fname

from authors, titleauthor

where state = "CA"

and authors.au_id = titleauthor.au_id

and royaltyper < 30

and au_ord = 2

 

Соединение всегда может быть выражено с помощью подзапроса. Подзапрос также часто может быть выражен как соединение.

 

Подзапросы с условием not in
 

Подзапросы, которым предшествует ключевая фраза not in (не в), также возвращают список из ни одного или  нескольких значений. Эта фраза означает “не а и не в и не с”.

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

 

select pub_name from publishers

where pub_id not in

  (select pub_id

   from titles

   where type = "business")

 

pub_name

----------------------------------------

Binnet & Hardley

 

(Выбрана 1 строка)

 

Этот запрос в точности совпадает с ранее рассмотренным за исключением фразы not in, которая подставлена вместо in. Однако, этот запрос нельзя заменить соединением. Соединение через “не равно” будет иметь другой смысл, а именно, оно будет искать названия издательств, которые опубликовали некоторую книгу не по бизнесу. Трудности, возникающие в интерпретации запросов на соединение, в которых используется неравенство, подробно обсуждались в предыдущей главе 5 “Соединения: Выбор данных из нескольких таблиц”.

 

Подзапросы с условием not in, содержащие NULL
 

Подзапрос, которому предшествует not in, возвращает список значений для каждой строки внешнего запроса. Если значение поля, указанного во внешнем запросе, не содержится в этом списке, то фраза not in интерпретируется как истинная (TRUE) и внешний запрос помещает рассмотреную запись в результирующий список.

Однако, если список значений, возвращаемый внутренним запросом (подзапросом), не содержит указанного значения, но содержит неопределенное значение NULL, то фраза not in интерпретируется как логически неопределенная (UNKNOWN), поскольку в этом случае невозможно точно опеределить принадлежность указанного значения к возвращаемому списку. В этом случае внешний запрос не включает рассмотренную строку (запись) в результат запроса.

Проиллюстрируем это на следующем примере, используя базу pubs2:

 

select pub_name

   from publishers

   where $100.00 not in

       (select price

        from titles

        where titles.pub_id = publishers.pub_id)

 

returns:

 

pub_name

----------------------

New Age Books

 

В результате указано только издательство New Age Books, которое не публиковало книг по цене 100 долларов. Издательства Binnet & Handley и Algodata Infosystems не были включены в результат, поскольку каждое из них публиковало книги с неустановленной ценой.

 

Подзапросы с квантором exists
 

Подзапросы, которым предшествует ключевое слово exists (существует), осуществляют проверку существования. Другими словами, в предложении where внешнего запроса проверяется существование, хотя бы одной строки, удовлетворяющей подзапросу. На самом деле подзапрос не возвращает никаких данных, а вместо этого возвращает логическое значение TRUE (истина) или FALSE (ложь).

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

 

select pub_name

from publishers

where exists

  (select *

   from titles

   where pub_id = publishers.pub_id

   and type = "business")

 

pub_name

----------------------------------------

New Age Books

Algodata Infosystems

 

(Выбрано 2 строки)

 

Чтобы понять выполнение этого запроса, рассмотрим по порядку название каждого издательства. Будет ли в результате подзапроса хотя бы одна строка с этим названием? Другими словами, будет ли проверка существования истинной (TRUE)?

В результате предыдущего запроса на втором месте указано издательство Algodata Inforsystems, которое имеет идентификационный номер 1389. Имеется ли хотя бы одна строка в таблице titles, в которой поле pub_id имеет значение 1389 и поле type значение “business”? Если так, то издательство “Algodata Inforsystems” должно попасть в результат. Подобная проверка осуществляется для каждого издательства.

Подзапрос, которому предшествует квантор существования exists, имеет по  сравнению с другими подзапросами следующие особенности:

 

·Перед ключевым словом exists не должно быть названий столбцов, констант или других выражений.
·Подзапрос с квантором существования возвращает значения TRUE или FALSE и не возвращает никаких данных из таблицы.
·Список выбора такого подзапроса часто состоит из одной звездочки (*). Здесь нет необходимости указывать названия столбцов, поскольку осуществляется просто проверка существования строк, удовлетворяющих условиям, указанным в подзапросе. Здесь можно и явно указать список выбора, следуя обычным правилам.
 

Ключевое слово exists является очень важным, поскольку часто не существует альтернативного способа выбора данных без использования подзапроса. Подзапросы, которым предшествует квантор exists всегда являются коррелирующимися подзапросами (см. раздел “Использование коррелирующихся подзапросов).

Как уже отмечалось, некоторые запросы с квантором существования нельзя выразить иным способом, но все запросы с условием in или с оператором сравнения, дополненным квантором all или any, можно выразить с помощью подзапроса с exists. Далее приводятся несколько примеров операторов с квантором exists и их эквивалентные альтернативные переформулировки.

Здесь показано два способа нахождения авторов, которые живут в одном городе с издателем:

 

select au_lname, au_fname

from authors

where city =any

  (select city

   from publishers)

 

select au_lname, au_fname

from authors

where exists

  (select *

   from publishers

   where authors.city = publishers.city)

 

au_lname          au_fname

--------------        --------------

Carson            Cheryl

Bennet            Abraham

 

           (Выбрано 2 строки)

 

Далее приводятся два запроса, которые находят книги, опубликованные издательством, расположенном в городе, название которого начинается на букву “В”:

 

select title

from titles

where exists

  (select *

   from publishers

   where pub_id = titles.pub_id

   and city like "B%")

 

select title

from titles

where pub_id in

  (select pub_id

   from publishers

   where city like "B%")

 

title

---------------------------------------------------

The Busy Executive's Database Guide

Cooking with Computers: Surreptitious Balance Sheets

You Can Combat Computer Stress!    

Straight Talk About Computers   

But Is It User Friendly?     

Secrets of Silicon Valley

Net Etiquette

Is Anger the Enemy?

Life Without Fear

Prolonged Data Deprivation: Four Case Studies

Emotional Security: A New Algorithm

 

(Выбрано 11 строк)

 

Подзапросы с not exists
 

Действие квантора not exists (не существует) аналогично действию квантора exists за исключением того, что предложение where, где он используется, считается истинным, когда ни одна строка не удовлетворяет подзапросу.

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

 

select pub_name

from publishers

where not exists

  (select *

   from titles

   where pub_id = publishers.pub_id

   and type = "business")

 

pub_name

-------------------------------

Binnet & Hardley

 

(Выбрана 1 строка)

 

Следующий запрос находит названия книг, которые не покупались:

 

select title

from titles

where not exists

  (select title_id

   from salesdetail

   where title_id = titles.title_id)

 

title

-----------------------------------------

The Psychology of Computer Cooking

Net Etiquette

 

(Выбрано 2 строки)

 

Нахождение пересечения и разности множеств с помощью exists
 

Подзапросы, которым предшествуют кванторы exists и not exists, можно использовать для выполнения двух операций над множествами: пересечения и разности. Пересечение двух множеств состоит из элементов, принадлежащих обеим множествам. Разность состоит из элементов, принадлежащих только первому множеству.

Пересечение таблиц authors и publishers по столбцу city состоит из множества городов, в которых есть и авторы и издательства:

 

select distinct city

from authors

where exists

(select *

  from publishers

  where authors.city = publishers.city)

 

city

--------------------

Berkeley

 

(Выбрана 1 строка)

 

Разность таблиц authors и publishers по столбцу city состоит из множества городов, где проживает автор, но нет издательств, т.е. всех городов за исключением Беркли:

 

select distinct city

from authors

where not exists

(select *

  from publishers

  where authors.city = publishers.city)

 

city

--------------------

Gary                 

Covelo               

Oakland              

Lawrence             

San Jose             

Ann Arbor            

Corvallis            

Nashville            

Palo Alto            

Rockville            

Vacaville            

Menlo Park           

Walnut Creek         

San Francisco        

Salt Lake City

 

(Выбрано 15 строк)

 

Использование коррелированных подзапросов
 

Многие из предыдущих запросов можно было бы выполнить путем однократного вычисления подзапроса и подстановки его результатов в предложение where внешнего запроса. Такие подзапросы называются некоррелированными (независимыми). В запросах, которые требуют повторного вычисления подзапроса, называемого в этом случае коррелированным (зависимым) подзапросом, результаты возвращаемые подзапросом зависят от значений, передаваемых внешним запросом. В этом случае подзапрос выполняется повторно для каждой стоки, которая выбирается во внешнем запросе.

С помощью следующего запроса можно найти всех авторов, получавших 100 процентов гонорара за свои книги.

 

select au_lname, au_fname

from authors

where 100 in

  (select royaltyper

   from titleauthor

   where au_id = authors.au_id)

 

au_lname        au_fname

--------------     --------------

Carson          Cheryl               

Ringer          Albert               

Straight        Dick                 

White           Johnson              

Green           Marjorie             

Panteley        Sylvia               

Locksley        Chastity             

del Castillo    Innes                

Blotchet-Hall   Reginald

 

(Выбрано 9 строк)

 

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

Рассмотрим подробнее как вычисляется предыдущий запрос. Сначала Transact-SQL просматривает каждую строку в таблице authors и, чтобы выяснить какую из них надо включить в результат, передает соответствующее значение во внутренний подзапрос. Например, предположим, что Transact-SQL просматривает строку, соответствующую Cheryl Carson. Ее идентификатор (authors.au_id) равен “238-95-7766”, поэтому это значение подставляется во внутренний запрос:

select royaltyper

from titleauthor

where au_id = "238-95-7766"

 

В результате получим число 100, поэтому внешний запрос в этом случае будет выглядеть следующим образом:

 

select au_lname, au_fname

from authors

where 100 in (100)

 

Поскольку предложение where является очевидно истинным, то строка Cheryl Carson включается в результат. Если эту же процедуру повторить для Абрахама Беннета (Abraham Bennet), то можно увидеть, почему этот автор не попал в окончательный результат.

 

Коррелированные подзапросы с коррелирующимися названиями
 

С помощью коррелированного подзапроса можно найти типы книг, которые публиковались несколькими издательствами:

 

select distinct t1.type

from titles t1

where t1.type in

  (select t2.type

   from titles t2

   where t1.pub_id != t2.pub_id)

 

type

--------------------

business

psychology

 

(Выбрано 2 строки)

 

Здесь необходимы коррелирующиеся (согласующиеся) названия, чтобы различить роли, в которых используется таблица titles. Этот многоуровневый запрос эквивалентен следующему запросу на самосоединение:

 

select distinct t1.type

from titles t1, titles t2

where t1.type = t2.type

and t1.pub_id != t2.pub_id

 

Коррелированные подзапросы с операциями сравнения
 

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

 

select s1.ord_num, s1.title_id, s1.qty

from salesdetail s1

where title_id like "PS%"

and s1.qty <

  (select avg(s2.qty)

   from salesdetail s2

   where s2.title_id = s1.title_id)

 

Далее приводятся результаты этого запроса:

 

ord_num                           title_id        qty    

------------------                    -----------      -----

91-A-7                            PS3333        90 

91-A-7                            PS2106        30 

55-V-7                            PS2106        31 

AX-532-FED-452-2Z7         PS7777       125

BA71224                           PS7777       200 

NB-3.142                          PS2091       200 

NB-3.142                          PS7777       250 

NB-3.142                          PS3333       345 

ZD-123-DFG-752-9G8        PS3333       750 

91-A-7                            PS7777       180 

356921                            PS3333       200

 

(Выбрано 11 строк)

 

Внешний запрос выбирает книги из таблицы sales (или “s1”) одну за другой. Подзапрос вычисляет среднее значение числа экземпляров этой книги продаваемых в одном заказе и если это значение превосходит число запрашиваемых экзепляров, то соответствующий заказ включается в результат.

В некоторых случаях коррелированный подзапрос имитирует действие оператора, содержащего предложение group by. Следующий запрос находит названия книг, имеющих цену, превосходящую среднюю цену книг этого типа:

 

select t1.type, t1.title

from titles t1

where t1.price >

  (select avg(t2.price)

   from titles t2

   where t1.type = t2.type)

 

type                          title

---------                  -------------------------------------------------------------

business             The Busy Executive's Database Guide

business             Straight Talk About Computers

mod_cook               Silicon Valley Gastronomic Treats

popular_comp       But Is It User Friendly?

psychology              Computer Phobic and Non-Phobic Individuals: Behavior Variations

psychology           Prolonged Data Deprivation: Four Case Studies

trad_cook            Onions, Leeks, and Garlic: Cooking Secrets of            the Mediterranean

 

(Выбрано 7 строк)

 

Для каждой строки из таблицы t1 Transact-SQL вычисляет подзапрос и включает строку в результат, если цена книги, указанная в этой строке, больше чем вычисленная средняя цена. Здесь нет необходимости явно группировать книги по типам, поскольку строки, по которым вычисляются средняя цена, отбираются в предложении where подзапроса.

 

Коррелированные подзапросы в предложении having
 

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

 

select t1.type

from titles t1

group by t1.type

having max(t1.advance) >=any

  (select 2 * avg(t2.advance)

  from titles t2

  where t1.type = t2.type)

 

type

-------------

mod_cook

 

(Выбрана 1 строка)

 

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