Запрос SELECT.

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

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

Часть SELECT – используется для описания того, что мы хотим получить. В части SELECT описываются результаты запроса.

Часть FROM – используется для указания источников данных. В первую очередь, там перечисляются таблицы, из которых должны извлекаться данные. Кроме того, в этой части описывается, как таблицы должны соединяться друг с другом.

Часть WHERE – содержит условия отбора. Мы уже упоминали о ней, когда обсуждали запросы UPDATE и DELETE.

Часть GROUP BY – используется для группировки записей, применяется в запросах, которые содержат агрегатные функции.

Часть HAVING – аналогична части WHERE, но производит отбор среди результатов группировки. Используется только совместно с GROUP BY.

Часть ORDER BY – определяет упорядочение результатов запроса. Применяется к результатам объединения запросов (если выполнялось) и позволяет отсортировать данные целиком.

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

 

Рис. 5.1 Схема БД для примеров.

Схема базы данных намеренно сделана простой, чтобы не отвлекать от деталей запросов.

ID Name Birthday GroupID City
Петров Сергей Васильевич 1990-03-01 Киров
Сергеев Анатолий Павлович 1991-07-28 Кирово-Чепецк
Иванова Мария Сергеевна 1991-04-23 Киров
Алексеев Виктор Михайлович 1990-09-14 Слободской
Иванов Вячеслав Игоревич 1988-01-21 Киров
Петрова Дарья Алексеевна 1991-02-01 Сыктывкар
Серёгин Василий Иванович 1991-04-12 Киров
Андреева Ирина Петровна 1987-05-10 Вятские Поляны
Васильев Антон Станиславович 1991-07-11 NULL Киров

Таблица 5.1 Данные таблицы Students.

ID Name NumberOfStudents
ПИ-21
БИ-42 NULL
С-22 NULL
ГМУ-11 NULL
ПИЭ-51
С-21
И-31 NULL
И-12 NULL
ПИ-41

Таблица 5.2 Данные таблицы Groups.

ID SubjectName SubjectCycle
История России Гуманитарный
Высшая математика Естественно-научный
Физика Естественно-научный
Вычислительные машины Общетехнический
Иностранный язык Гуманитарный
Экономическая теория Общеэкономический

Таблица 5.3 Данные таблицы Subjects.

ID StudentID SubjectID MarkDate Semester Mark
2011-01-11
2011-01-11
2011-01-11
2011-06-16
2011-06-16
2011-06-30
2011-06-20
2011-06-20

Таблица 5.4 Данные таблицы Marks.

В части SELECT мы определяем, что должно быть в результатах запроса. Для этого мы через запятую перечисляем выбираемые элементы. В качестве таких элементов можно использовать имена колонок в таблицах, арифметические выражения, в том числе, с участием колонок таблиц, литералы, агрегатные функции. Приведем пример на рис. 5.2.

 

Рис. 5.2 Простой запрос на выборку в SQL Server.

В приведенном примере используются имена колонок и строковые литералы. Легко видеть, что строковые литералы просто повторяются в каждой строке. Их можно использовать как подписи к данными при работе в текстовом режиме. Обратите внимание, что для колонок, содержащих строковые литералы, не назначены имена. Это естественно, так как взять для них имена просто негде. Так же точно происходит и с выражениями и агрегатными функциями. Если требуется обращаться к таким колонкам далее в тексте запроса, можно назначить им имя, используя конструкцию AS. Также с помощью этой конструкции можно задавать псевдонимы колонкам таблиц. Это полезно, если имена колонок длинные или непонятные. Приведем пример.

 

Рис. 5.3 Использование псевдонимов для полей.

Как мы видим на рисунке 5.3, полям назначены псевдонимы. Через них можно обращаться к полям далее в тексте запроса.

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

 

Рис. 5.4 Использование в запросе символа «*».

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

 

Рис. 5.5 Использование ключевого слова DISTINCT.

Наконец, мы можем указать, сколько записей требуется выбрать, использовав конструкцию TOP N, где N – требуемое количество записей. При использовании этой конструкции будут выбраны первые N записей, но нужно помнить, что то, какие именно записи окажутся первыми, определяется порядком их сортировки (задается в части ORDER BY и будет рассмотрен далее).

Как уже было сказано, в части SELECT могут также указываться агрегатные функции, но к ним мы обратимся немного позднее, а пока перейдем к части FROM. Часть FROM, наряду с частью SELECT, должна иметься в любом запросе на выборку данных из таблиц. Остальные части можно добавлять по мере необходимости.

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

 

Рис. 5.6 Результат декартова соединения таблиц.

Для экономии места мы привели лишь часть получившейся таблицы, но в строке состояния в правом нижнем углу видно, что всего в ней 81 строка (9 студентов и 9 групп дают 81 сочетание). Очевидно, что большая часть этих строк нам не нужна и вообще не имеет смысла и будет отброшена при проверке соответствия шифру группы у студента и группы. Тем не менее, на формирование этой таблицы будет потрачено время и оперативная память. Кроме того, мы привели лишь крохотный учебный пример, а что будет в ситуации с реальной БД, в которой имеются записи о тысячах студентах и десятках групп?

Из сказанного следует, что лучше использовать какой-то способ соединения таблиц, чтобы избежать таких накладных расходов. Наиболее широко применяемым является явное соединение таблиц, при котором мы указываем, как именно должны соединяться таблицы. Существует четыре вида явных соединений: внутреннее (INNER JOIN), левое внешнее (LEFT OUTER JOIN), правое внешнее (RIGHT OUTER JOIN) и полное внешнее соединение (FULL OUTER JOIN). При написании запросов слово OUTER можно не указывать. Рассмотрим каждый вид соединения на примере.

 

Рис. 5.7 Схема внутреннего соединения.

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

 

Рис. 5.8 Пример использования внутреннего соединения.

Обратите внимание на синтаксис, используемый для соединения. Мы указываем, какие таблицы хотим соединить, а далее – какие поля использовать для сопоставления записей. В нашем случае, сопоставление выполнено по внешнему ключу, но это не обязательно. Как и показано на схеме, выбираются только записи, для которых нашлось соответствие. Если посмотреть на результат выполнения запроса, легко увидеть, что выбраны только те студенты, которые входят в какую-либо группу. Аналогично, группы, которые не содержат студентов, в результаты запроса не попали.

 

Рис. 5.9 Схема левого внешнего соединения.

Из схемы видно, что в результаты запроса попадают все записи из левой таблицы и соответствующие записи из правой таблицы, если они нашлись. Посмотрим на пример запроса.

 

Рис. 5.10 Пример использования левого внешнего соединения.

Легко видеть, что в результаты запроса попали все студенты (то есть, записи из левой таблицы), а также группы, которые удалось сопоставить с ними. Для последнего студента группа не задана, поэтому запись из таблицы групп не выбрана и в результатах запроса стоит значение NULL.

 

Рис. 5.11 Схема правого внешнего соединения.

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

 

Рис. 5.12 Пример использования правого внешнего соединения.

Представляется, что все очевидно из рисунка.

 

Рис. 5.13 Схема полного внешнего соединения.

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

 

Рис. 5.14 Пример использования полного внешнего соединения.

Мы видим, что выбраны все записи из обеих таблиц, вне зависимости, найдены ли для них соответствия.

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

 

Рис. 5.15 Соединение трех таблиц.

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

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

Логические операторы, используемые в запросах SQL, привычны по другим языкам программирования. Это операторы AND, OR и NOT. Их значение в языке SQL не отличается от общепринятого. Приоритет операторов следующий: NOT, AND, OR. То есть, в оператор NOT выполняется в первую очередь, OR – в последнюю. Аналогично другим языкам программирования, порядком выполнения операций можно управлять при помощи круглых скобок.

Самые простые операции, которые используются в части WHERE – это операции сравнения: = (равно), <> (не равно), > (больше), >= (больше или равно), < (меньше), <= (меньше или равно). Их смысл такой же, как и везде. Внимательным следует быть при сравнении строк – результат зависит от используемого порядка сортировки (collation).

Сравнивать между собой можно и данные в таблицах, и данные с константой. Но необходимо отметить еще один момент. В базе данных, помимо определенных значений, может встречаться и значение NULL. Точнее говоря, NULL – это не значение, а отсутствие значения. Соответственно, результат сравнения какого-то конкретного значения с NULL дает в результате UNKNOWN, а не истину или ложь. Из-за этого логика становится не двузначной, а трехзначной. Записи, результат вычисления выражения в части WHERE для которых принимает значение UNKNOWN, не попадают в результаты запроса.

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

  TRUE FALSE UNKNOWN
AND UNKNOWN FALSE UNKNOWN
OR TRUE UNKNOWN UNKNOWN

Табл. 5.5 Результат логических операций с участием UNKNOWN.

Для небольшой иллюстрации приведем пример.

 

Рис. 5.16 Влияние NULL на результаты запроса.

Как видно по тексту запроса, мы пытаемся выбрать студентов из Кирова, которые не учатся в группе с ID = 1. Два студента выбраны совершенно ожидаемо, а вот студент Васильев, который тоже из Кирова, но группа для которого не указана, не выбран, хотя, казалось бы, он тоже не учится в группе с ID = 1. Здесь мы как раз имеем дело со значением NULL. Результат сравнения NULL и 1 дает UNKNOWN, результат сравнения поля City со значением «Киров» дает TRUE. Но выражение TRUE AND UNKNOWN дает в результате UNKNOWN, так что запись отбрасывается.

Как же работать со значениями NULL? Здесь мы переходим к использованию так называемых предикатов – специальных конструкций для формирования более сложных условий. Самый простой из них как раз предназначен для работы со значениями NULL и выглядит очень просто – IS [NOT] NULL. Рассмотрим его использование на примере.

 

Рис. 5.17 Использование предиката IS NULL.

Пример практически тот же самый, но дополнен предикатом. И мы видим, что студент Васильев тоже выбран.

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

 

Рис. 5.18 Использование предиката BETWEEN.

Как видно из примера, синтаксис предиката BETWEEN достаточно простой. Нужно только отметить, что если значение равно одной из границ диапазона, результат выполнения предиката тоже истинный. То есть, используются операции сравнения <= и >=. Пример с выбором фамилий приведен, чтобы показать, что проверка диапазона может выполняться не только для чисел, но и для других типов. Но в случае со строками следует иметь в виду установленный порядок сортировки для них. Хотя задачу, решаемую с помощью предиката BETWEEN, легко можно решить и комбинацией из <= и >=, предикат удобнее тем, что позволяет яснее выразить смысл условия.

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

 

Рис. 5.19 Использование предиката IN.

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

Последним рассмотрим предикат LIKE, который позволяет выполнять отбор строк по шаблону. Синтаксис его использования следующий: «Строка LIKE шаблон»

При этом для формирования шаблона можно использовать элементы «%» для обозначения любого количества любых символов и «_» для обозначения одного произвольного символа. При этом, элементу «%» соответствует, в том числе, и пустая строка, а элемент «_» требует обязательного наличия символа. Рассмотрим на примере. В таблице групп у нас имеются группы ПИ-21, ПИЭ-51 и ПИ-41. Сформируем первый запрос, который вернет нам все группы.

 

Рис. 5.20 Предикат LIKE.

Теперь немного усложним шаблон.

 

Рис. 5.21 Предикат LIKE с усложненным шаблоном.

Как мы видим, результат не изменился, то есть, элементу «%» действительно соответствует, в том числе, и пустая строка. Теперь сравним результат при использовании элемента «_».

 

Рис. 5.22 Предикат LIKE с элементом «_».

Как видно из результатов запроса, элемент «_» действительно требует наличия символа, и строки «ПИ-21» и «ПИ-41» не подошли.

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

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

· SUM (выражение) – вычисляет сумму значений;

· AVG (выражение) – вычисляет среднее значение;

· MIN (выражение) – находит минимальное значение;

· MAX (выражение) – находит максимальное значение;

· COUNT (выражение) – подсчитывает количество значений.

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

 

Рис. 5.23 Запрос с использованием агрегатных функций.

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

· COUNT(имя_поля) – подсчитывает количество значений в указанном поле. Строки, в которых в указанном поле находится NULL пропускаются.

· COUNT(DISTINCT имя_поля) – подсчитывает количество уникальных значений в указанном поле. Строки с NULL также пропускаются.

· COUNT(*) – подсчитывает количество строк в результатах запроса. Считаются все строки.

Приведем небольшой пример использования разных вариантов COUNT:

 

Рис. 5.24 Пример использования разных вариантов COUNT.

Поскольку поле CITY в таблице студентов заполнено во всех строках, то результаты COUNT(*) и COUNT(City) не отличаются.

Предложение GROUP BY используется для указания порядка группировки строк. В этом предложении должны быть перечислены все поля из части SELECT, которые не являются агрегатными функциями и константами. Если какое из полей не указать, то при выполнении запроса будет получена ошибка. Пожалуй, этим правила использования предложения GROUP BY и ограничиваются. Пример его использования можно посмотреть в запросе на рисунке 5.23.

Для работы с полученными значениями агрегатных функций используется часть HAVING. По своему смыслу и порядку использования она совершенно аналогична части WHERE, а разница состоит в том, что обрабатываются уже сгруппированные записи. То есть, к моменту, когда начинают проверяться условия в части HAVING, доступны только поля, указанные в части SELECT. Все остальные данные уже обработаны и отброшены. Соответственно, в этой части невозможно обращаться к полям таблиц, которые не попадают в результаты запроса, работать с данными отдельных строк и так далее. Аналогично, невозможно получить доступ к результатам вычисления агрегатных функций в части WHERE – на этот момент они еще не существуют. Для экономии места, мы не будем приводить примеры сообщений об ошибках при попытке выполнить подобные запросы, желающие могут проверить самостоятельно.

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

 

Рис. 5.25 Использование части HAVING.

Допустим, что мы хотим отобрать только дисциплины из естественнонаучного и общетехнического циклов. Для этого мы должны дополнить наш запрос условием SubjectCycle IN (‘Естественно-научный’, ‘Общетехнический’). При этом, разместить его можно и в части WHERE (которая пока в запросе отсутствует за ненадобностью), и в части HAVING. Результат будет одинаковым, но представляется, что эффективнее отбросить ненужные записи еще на этапе их отбора, до вычисления средних значений оценок. Тем самым, мы избавимся от лишних расчетов, результаты которых нам все равно не нужны.

В остальном, для части HAVING верно все, что сказано о части WHERE, поэтому ее рассмотрение на этом можно закончить.

Последняя часть, которую мы рассмотрим в нашем конспекте – это часть ORDER BY, отвечающая за упорядочение результатов запроса. Её применение достаточно просто – после ключевого слова GROUP BY через запятую указывается перечень полей, по значениям которых необходимо отсортировать результаты, а также порядок сортировки. Порядок задается при помощи ключевых слов ASC (сокращение от Ascending, возрастание по-английски) и DESC (Descending, соответственно, убывание). По умолчанию, сортировка производится по возрастанию, так что ключевое слово ASC можно и не указывать. Приведем пример.

 

Рис. 5.27 Пример сортировки результатов запроса.

Думается, что из приведенного примера порядок применения части ORDER BY для упорядочения результатов запроса вполне очевиден и дальнейшие пояснения не требуются.

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

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

Во вторую очередь, вложенные запросы можно разделить на те, которые формируют промежуточные выборки для дальнейшей обработки и те, которые используются при проверке различных условий. Запросы первого вида чаще всего размещаются в части FROM, запросы второго вида – в частях WHERE и HAVING.

Разберем все возможные случаи на примерах. Начнем с простых запросов, использующихся при проверке условий. Они как для получения одиночных значений, так и для формирования списков для предиката IN. Например, выберем студентов, которые получили максимальный балл по дисциплине «Вычислительные машины». Очевидно, что в общем случае мы не можем заранее знать, какой именно балл максимальный, это может быть и 5, и 4, и 3. Соответственно, вложенный запрос поможет нам это выяснить.

 

Рис. 5.28 Использование вложенного запроса в простом условии.

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

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

 

Рис. 5.29 Использование вложенного запроса с предикатом IN.

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

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

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

 

Рис. 5.30 Использование вложенного запроса для формирования временной таблицы.

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

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

Немного модифицируем предыдущий пример, и выберем студентов и оценки, если они выше, чем средний балл по данной дисциплине по группе данного студента.

 

Рис. 5.31 Использование коррелированного подзапроса.

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

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

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

Надо отметить, что уровней вложенности запросов может быть и больше, и внутри вложенного запроса может быть еще один, а внутри него – еще один. Максимальное количество уровней вложенности зависит от конкретной СУБД.

Последний аспект работы с вложенными запросами – применение специальных предикатов, предназначенных специально для них. Это предикаты EXISTS, ALL и ANY.

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

 

Рис. 5.32 Использование предиката EXISTS.

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

Предикаты ALL и ANY работают похоже, но механизм несколько иной. Лучше продемонстрировать это на примере. Выберем троечников с использованием этих предикатов.

 

Рис. 5.33 Использование предиката ALL.

 

Рис. 5.34 Использование предиката ANY.

Итак, поясним работу предикатов. Предикат ALL обращается в истину, если для всех значений, выбранных вложенным запросом, выполнится условие. В нашем случае он обращается в истину, если все оценки больше 3, а оператор NOT обращает это значение, так что отбираются студенты, для которых находятся удовлетворительные оценки.

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

Как видно, применение предикатов ALL и ANY достаточно просто. Но в их работе есть один важный нюанс – если вложенный запрос не вернет результатов вообще, то эти предикаты обратятся в истину. В результате мы можем получить несколько неожиданную выборку. Например, попытаемся выбрать отличников, то есть, студентов, у которых все оценки равны 5.

 

Рис. 5.35 «Лишние» результаты для предиката ALL.

Легко видеть, что выбраны не только отличники (а это только Сергеев), но все остальные студенты, которые вообще не имеют оценок. Логика здесь следующая – если запрос не вернул результатов, то значит, не найдется таких, которые нарушат условие (в нашем случае – оценок, отличных от 5). Таким образом, запись подходит. Аналогично можно рассуждать и для предиката ANY, пример приводить не будем для экономии места. Для получения нужных результатов, то есть, студентов, у которых есть оценки, и эти оценки – только отличные, необходимо дополнить запрос предикатом EXISTS, который проверяет, имеются ли вообще оценки.

 

Контрольные вопросы.

1. Укажите особенности SQL как языка программирования.

2. Перечислите основные составные части языка и их функции.

3. Опишите структуру запроса на создание таблицы.

4. Перечислите запросы, предназначенные для манипулирования данными.

5. Назовите основные части запроса на выборку.

6. Опишите виды явных соединений таблиц.

7. Поясните специфику работы с пустыми значениями.

8. Перечислите предикаты, которые можно использовать при формировании условий.

9. Опишите порядок использования агрегатных функций.

10. Перечислите разновидности вложенных запросов и их назначение.

11. Поясните особенности использования коррелированных подзапросов.

12. Назовите предикаты для работы с вложенными запросами. Укажите на особенности их использования.