Книга
Книга
В. В. ДунаевВ. В. ДунаевСТУДЕНТАСТУДЕНТА
В. В. Дунаев
В. В. Дунаев
СТУДЕНТА
СТУДЕНТА
Вадим Дунаев
БАЗЫ ДАННЫХ
ЯЗЫК SQL
ДЛЯ СТУДЕНТА
Санкт-Петербург«БХВ-Петербург»2006
УДК 681.3.0684-800.92SQL
ББК 32.973.26-018.1
Д83
Дунаев В. В.
Д83 Базы данных. Язык SQL. — СПб.: БХВ-Петербург, 2006. - 288 с.: ил.
ISBN 5-94157-823-7
Рассмотрен язык структурированных запросов дня взаимодействия с базами данных — SQL— начиная с доступного изложения теории отношений (реляционной теории) и заканчивая вопросами администрирования СУБД с помощью запросов. На практических примерах подробно описаны основные конструкции языка, а также различные типы запросов: простые, сложные, рекурсивные. Показано, как осуществлять вычисления в запросах с помощью агрегатных функций и условных выражений. Рассмотрены операции над наборами записей, соединение таблиц, модификация данных, транзакции, курсоры, хранимые процедуры и функции. Уделено внимание администрированию СУБД с помощью запросов. Материал сопровождается задачами для самостоятельного решения.
Для студентов и программистов
УДК 681.3.0684-800.92SQL
ББК 32.973.26-018.1
Группа подготовки издания:
Главный редакторЗам. главного редактора Зав. редакцией Редактор Компьютерная верстка КорректорДизайн серии Оформление обложки Зав. производствомГлавный редакторЗам. главного редактора Зав. редакцией Редактор Компьютерная верстка КорректорДизайн серии Оформление обложки Зав. производствомЕкатерина Кондукова Игорь Шишигин Григорий Добин Нина СедыхНатальи Караваевой Виктория Пиотровская Игоря Цырульникова Елены Беляевой Николай ТверскихЕкатерина Кондукова Игорь Шишигин Григорий Добин Нина СедыхНатальи Караваевой Виктория Пиотровская Игоря Цырульникова Елены Беляевой Николай Тверских
Главный редактор
Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка Корректор
Дизайн серии Оформление обложки Зав. производством
Главный редактор
Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка Корректор
Дизайн серии Оформление обложки Зав. производством
Екатерина Кондукова Игорь Шишигин Григорий Добин Нина Седых
Натальи Караваевой Виктория Пиотровская Игоря Цырульникова Елены Беляевой Николай Тверских
Екатерина Кондукова Игорь Шишигин Григорий Добин Нина Седых
Натальи Караваевой Виктория Пиотровская Игоря Цырульникова Елены Беляевой Николай Тверских
Лицензия ИД No 02429 от 24.07.00. Подписано в печать 27.02.06.
Формат 60х901Ав. Печать офсетная. Усл. печ. л. 18.
Тираж 3000 экз. Заказ No 120
"БХВ-Петербург", 194354, Санкт-Петербург, ул. Есенина, 5Б.
Санитарно-эпидемиологическое заключение на продукцию
No 77.99.02.953.Д.006421.11.04 от 11.11.2004 г. выдано Федеральной службойпо надзору в сфере защиты прав потребителей и благополучия человека.
Отпечатано с готовых диапозитивовв ГУП "Типография "Наука"199034, Санкт-Петербург, 9 линия, 12
ISBN 5-94157-823-7ISBN 5-94157-823-7© Дунаев В. В., 2006
ISBN 5-94157-823-7
ISBN 5-94157-823-7
© Оформление, издательство "'БХВ-Петербург*, 2006
Оглавление
От автора1
Благодарности1
Введение 3
Как устроена эта книга 4
Как выполнять примеры SQL-выражений6
Работа с Microsoft SQL Server 20007
Работа с Microsoft Access 2003 11
Глава 1. Основы реляционных баз данных17
Множества17
Отношения29
Общие сведения29
Способы представления отношений32
Операции над отношениями,....35
Декомпозиция отношений42
Корректная декомпозиция43
Пример некорректной декомпозиции44
Зависимости между атрибутами45
Правила вывода зависимостей55
Ключи57
Ограничения целостности отношений60
Семантическая целостность60
Доменная целостность61
Ссылочная целостность61
Нормализация таблиц63
Первая нормальная форма65
Вторая нормальная форма66
Третья нормальная форма67
Доменно-ключевая нормальная форма67
Денормализация68
Глава 2. Основы SQL69
Что такое SQL69
Типы данных73
Строки76
Числа77
Логические данные80
Дата и время80
Интервалы83
Специальные типы данных84
Пользовательские типы данных86
Неопределенные значения90
Преобразование типов91
Глава 3. Простые выборки данных93
Основное SQL-выражение для выборки данных94
Уточнения запроса98
Оператор WHERE102
Оператор GROUP BY109
Оператор HAVING.Ill
Оператор ORDER BY........................................................ 112
Логические операторы113
Задачи114
Задача 3.1115
Задача 3.2115
Задача 3.3115
Глава 4. Вычисления117
Итоговые функции117
Функции обработки значений121
Строковые функции121
Числовые функции123
Функции даты-времени125
Вычисляемые выражения126
Условные выражения с оператором CASE129
Оператор CASE со значениями129
Оператор CASE с условиями поиска130
Функции NULLIFY COALESCE132
Глава 5. Сложные запросы135
Подзапросы136
Простые подзапросы136
Связанные подзапросы142
Теоретико-множественные операции147
Декартово произведение наборов записей147
Объединение наборов записей (UNION)149
Пересечение наборов записей (INTERSECT)152
Вычитание наборов записей (EXCEPT)153
Операции соединения154
Естественное соединение (NATURAL JOIN)155
Условное соединение (JOIN... ON)157
Соединение по именам столбцов (JOIN... USING)157
Внешние соединения159
Рекурсивные запросы164
Задачи167
Задача 5.1167
Задача 5.2170
Задача 5.3170
Задача 5.4171
Задача 5.5171
Глава 6. Добавление, удаление и изменение данных в таблицах173
Добавление новых записей173
Удаление записей176
Изменение данных 178
Проверка ссылочной целостности182
Глава 7. Создание и модификация таблиц185
Создание таблиц185
Ограничения для столбцов187
Ограничения для таблиц190
Внешние ключи192
Удаление таблиц195
Модификация таблиц196
Представления200
Что такое представление200
Создание представлений203
Изменение данных в представлениях206
Задачи 207
Задача 7.1207
Задача 7.2208
Задача 7.3208
Задача 7.4208
Глава 8. Транзакции209
Как устроена транзакция210
Определение параметров транзакции212
Уровни изоляции транзакций213
Неподтвержденное чтение213
Подтвержденное чтение214
Повторяющееся чтение...214
Последовательное выполнение215
Субтранзакции215
Ограничения в транзакциях216
Глава 9. Курсоры и применение SQL в приложениях221
Объявление курсора224
Чувствительность224
Перемещаемость225
Выражение запроса226
Сортировка226
Разрешение обновления227
Открытие и закрытие курсора228
Работа с отдельными записями229
SQL в приложениях232
Глава 10. Постоянно хранимые модули237
Составные команды238
Атомарность239
Переменные240
Обработка состояния241
Операторы условного перехода244
Оператор IF..................................................................... 244
Оператор CASE... END CASE.245
Операторы цикла247
Оператор LOOP...END LOOP247
Оператор WHILE... DO ... END WHILE248
Оператор REPEAT... UNTIL ... END REPEATE249
Оператор FOR ... DO ... END FOR................................. 249
Оператор ITERATE251
Хранимые процедуры и функции252
Хранимые модули255
Глава 11. Управление правами доступа257
Пользователи257
Администратор базы данных258
Владелец объектов базы данных258
Другие пользователи259
Создание пользователей259
Предоставление привилегий260
Роли и группы262
Право просмотра данных263
Право изменять данные263
Право удалять записи264
Право на использование ссылок264
Право на домены265
Право предоставлять права266
Отмена привилегий267
Приложение. Зарезервированные слова SQL271
Предметный указатель275
От автора
Занимаясь приложениями реляционных баз данных более десяти лет, после двухлетней паузы я вдруг неожиданно для себя обнаружил, что изрядно подзабыл язык SQL. В памяти остались лишь общие идеи. Пришлось обратиться к нескольким книгам. И тогда я подумал, что хорошо бы иметь под рукой небольшую книжку, в которой было бы все самое необходимое, чтобы легко и быстро все вспомнить. А тем, кто не знал SQL никогда, эта книжка могла бы помочь быстро его освоить в такой степени, чтобы получать практически полезные результаты. Воплощение этой идеи вы держите в руках. Надеюсь, что данная книга окажется полезной как для новичков, так и для успевших кое-что забыть.
Благодарности
Я искренне благодарен коллегам Дмитрию Лямасву и Игорю Слю- саренко за практические советы и консультации по техническим вопросам.
Введение
SQL (Structured Query Language) — это структурированный язык запросов к реляционным базам данных. На этом языке можно формулировать выражения (запросы), которые извлекают требуемые данные, модифицируют их, создают таблицы и изменяют их структуры, определяют права доступа к данным и многое другое.
Запросы выполняются системой управления базой данных (СУБД). Если вы не являетесь специалистом по разработке и администрированию баз данных, то вполне можете быть их пользователем, который просматривает или/и изменяет данные в уже имеющихся таблицах. Во многих случаях эти и другие операции с базой данных выполняются с помощью специальных приложений, предоставляющих пользователю удобный интерфейс. Обычно приложения пишутся на специальных языках программирования (С, Pascal, Visual Basic и т. п.) и чаще всего создаются с помощью интегрированных сред разработки, например, Delphi, C++ Builder и др. Однако доступ к базе данных можно получить и без них — с помощью только SQL. Замечу также, что и специализированные приложения обычно используют SQL-фрагменты кода при обращениях к базе данных.
Таким образом, SQL — широко распространенный стандартный язык работы с реляционными базами данных. Синтаксис этого языка достаточно прост, чтобы его могли использовать рядовые пользователи, а не только программисты. В настоящее время обычный пользователь компьютера должен владеть, по крайней мере, текстовым редактором (например, Microsoft Word) и электронными таблицами (например, Microsoft Excel). Неплохо, если
он также умеет пользоваться базами данных. Различных СУБД существует много, а универсальное средство работы с базами данных одно — SQL. Знание SQL, хотя бы его основ, и умение его применять для поиска и анализа данных является фундаментальной частью компьютерной грамотности даже рядовых пользователей.
Как устроена эта книга
Данная книга посвящена языку манипулирования реляционными базами данных, а не их проектированию и сопровождению. Однако успех изучения и применения SQL существенно зависит от понимания того, как устроена реляционная база данных. Теоретическим источником реляционных баз данных является теория отношений (реляционная теория). Основам этой теории посвящена глава 1. Несмотря на обилие символики, изложенный в ней материал достаточно прост и вполне доступен широкому кругу читателей. Однако те, кого интересуют непосредственно SQL и возможность быстрее получить практический результат, могут пропустить эту главу при первом чтении. Изложение последующих глав построено в основном на рассмотрении примеров.
Таблицы, из которых состоит любая реляционная база данных, представляют собой некоторые отношения, а отношения являются не чем иным, как множествами записей (строк). Все запросы к базе данных, направленные на извлечение из нее нужных сведений, интерпретируются как инструкции по выполнению тех или иных операций, являющихся в конечном счете операциями алгебры множеств. Более или менее серьезные базы данных состоят из нескольких таблиц, между которыми могут существовать связи. Рассмотрение в реляционной теории декомпозиции одной таблицы на несколько других позволит вам понять противоположный процесс — проектирование базы данных как композиции нескольких таблиц.
Главы 2—4 содержат минимальный набор сведений, позволяющий в большинстве случаев извлекать из базы данных необходимую информацию.
В главе 2 кратко рассматривается история и составные части SQL, а также типы данных. При первом прочтении желательно
получить хотя бы поверхностное представление о типах данных. Дело в том, что наборы типов данных SQL и типов столбцов таблиц, поддерживаемых той или иной СУБД, как правило, не совпадают. Однако между большинством из них есть соответствие. Кроме того, можно использовать функцию приведения к заданному типу.
В главе 3 описываются запросы на выборку данных, которые наиболее часто используются на практике. Это так называемые простые запросы, не содержащие вложенных запросов. Здесь же рассматриваются условия, которые могут применяться не только в запросах на выборку, но и при модификации данных.
Глава 4 посвящена вычислениям в запросах с помощью итоговых (агрегатных) функций и условных выражений.
В главе 5 рассматриваются так называемые сложные запросы, а также теоретико-множественные операции над наборами записей, соединения таблиц и рекурсивные запросы. Нередко для получения данных приходится вначале выполнить некий вспомогательный запрос, чтобы затем его результат использовать при формулировке условия выборки данных в основном запросе. Вспомогательный запрос включается в выражение основного запроса, который называют сложным (содержащим подзапрос). Теоретико-множественные операции позволяют из нескольких наборов записей получить другой набор записей — объединение, пересечение или разность исходных наборов. Операция соединения таблиц дает в результате таблицу, записи в которой получаются путем некоторой комбинации записей соединяемых таблиц.
Модификация (изменение, добавление и удаление) данных описывается в главе 6.
Рано или поздно возникает задача создания новых или изменения структуры уже существующих таблиц. Средства SQL, позволяющие это сделать, рассмотрены в главе 7. Здесь же описываются представления — виртуальные таблицы, доступные многим пользователям.
При выполнении многоэтапных операций с базами данных, особенно в многопользовательском режиме, последовательности SQL-выражений объединяются в транзакции. Если какое-либо SQL-выражение в транзакции по какой-то причине нс выполнилось, отменяется действие всех SQL-выражений в этой транзакции,
а база данных возвращается в исходное состояние, в котором она находилась до начала транзакции. Иначе говоря, выполняется принцип "все или ничего". Транзакции описываются в главе 8.
В главе 9 рассмотрены так называемые курсоры. Курсор позволяет сделать обработку одной или нескольких записей таблицы с помощью SQL-выражений и, таким образом, упрощает совместное использование SQL с другими языками программирования, на которых пишутся приложения баз данных. Выборка записей производится с помощью SQL-запроса, а проверка их содержимого — с помощью кода на процедурном языке.
Глава 10 посвящена постоянно хранимым модулям. На языке SQL можно написать процедуры и функции, которые будут содержать объявления переменных и составные команды SQL. Коды этих процедур и функций могут содержать управляющие структуры, а также обычные SQL-выражения. Функции и процедуры задаются в рамках так называемого модуля, который представляет собой контейнер для их размещения. Модуль создается специальной командой SQL и сохраняется в метаданных базы данных, т. с. становится ее компонентом, подобно таблицам, индексам и т. д. Таким образом, однажды создав модуль с процедурами и функциями, вы можете затем в частных SQL-запросах использовать их вызовы.
В главе 11 рассматриваются некоторые возможности SQL по администрированию базы данных, а именно, по предоставлению прав доступа к ее объектам. Разграничение прав доступа является важным средством защиты базы данных от неправильного использования содержащейся в ней информации различными категориями пользователей.
В конце некоторых глав приводятся задачи для самоконтроля усвоения прочитанного материала. Список зарезервированных слов SQL приведен в приложении.
Как выполнять
примеры SQL-выражений
Чтобы выполнить примеры и решить задачи, приведенные в книге, необходимо установить на компьютере какую-нибудь СУБД, которых существует множество. Наиболее простым вариантом является Microsoft Access. Однако не все возможности SQL-92
поддерживаются этой СУБД. Я рекомендую установить какой- нибудь SQL-сервер, например, Microsoft SQL Server 2000 или PostgreSQL. Далее мы кратко рассмотрим создание базы данных и SQL-выражений с помощью SQL Server 2000 и Access 2003.
Работа с Microsoft SQL Server 2000
Установка Microsoft SQL Server 2000
Процедура инсталляции SQL Server 2000 с установочного CD-диска довольно проста. Если инсталлятор не запустился автоматически, то следует запустить программу \x86\setup\setupsql.exe, расположенную на установочном CD. Далее мастер предоставит вам возможность указать параметры установки. Один из возможных вариантов установки, пригодный для большинства новичков, работающих на локальном компьютере, выглядит следующим образом:
Local Computer (Локальный компьютер).
Server and Client Tools (Инструменты сервера и клиента). При этом будет инсталлирован сам сервер и средства администрирования.
Настройка учетных записей служб:
Use the same account for each service. Auto start SQL Server Service (Использовать одну учетную запись для всех служб с их автоматическим запуском при загрузке операционной системы);
Use the Local System account (Использовать локальную учетную запись);
Auto Start Service (Автоматический запуск служб при загрузке операционной системы).
Typical (Установка всех компонентов).
После инсталляции сервера баз данных в меню Windows Пуск | Программы | Microsoft SQL Server запустите утилиту Enterprise Manager, которая предоставляет пользовательский интерфейс для настройки, управления и доступа к данным на сервере (рис. В1). В левой части окна утилиты отображается древовидный список, в котором щелчком левой кнопкой мыши следует
раскрыть узел Microsoft SQL Servers/SQL Server Group. Если установка прошла успешно, то в этом узле должен находиться узел, имя которого состоит из имени вашей учетной записи, за которым следует "(Windows NT)". На рис. В1 это DW (Windows NT). В правой части окна отображаются элементы того узла, который выделен в левой части окна (в дереве).
SQL Server Enterprise ManagerSQL Server Enterprise ManagermodelmodelmsdbmsdbmastermasterNorthwindNorthwindpubspubstempdbtempdbМоя база данныхМоя база данныхDatabases 7 ItemsDatabases 7 ItemsКонсоль Действие ВидКонсоль Действие ВидConsole Root Microsoft SQL ServersXSQL Server GroupVDW (Windows NT)\Databases [Console Root Microsoft SQL ServersXSQL Server GroupVDW (Windows NT)\Databases [J Console Root■ ^0 Microsoft SQL Servers- ^J SQL Server Group j) DW (Windows NT)-■ ’ V (‘.‘atabases: * U master♦ У model$tUmsdb♦ (J Northwind‘;tУpubs,tUtempdb•,♦.УМоя база данныхд . I Data Transformation Services ;+i-’£j Management■+: uJ Replication:+! Security•♦ , 1 Support Services:♦ I Meta Data ServicesJ Console Root■ ^0 Microsoft SQL Servers- ^J SQL Server Group j) DW (Windows NT)-■ ’ V (‘.‘atabases: * U master♦ У model$tUmsdb♦ (J Northwind‘;tУpubs,tUtempdb•,♦.УМоя база данныхд . I Data Transformation Services ;+i-’£j Management■+: uJ Replication:+! Security•♦ , 1 Support Services:♦ I Meta Data ServicesРис. B1. Окно утилиты Enterprise ManagerРис. B1. Окно утилиты Enterprise Manager
SQL Server Enterprise Manager
SQL Server Enterprise Manager
model
model
msdb
msdb
master
master
Northwind
Northwind
pubs
pubs
tempdb
tempdb
Моя база данных
Моя база данных
Databases 7 Items
Databases 7 Items
Консоль Действие Вид
Консоль Действие Вид
Console Root Microsoft SQL ServersXSQL Server GroupVDW (Windows NT)\Databases [
Console Root Microsoft SQL ServersXSQL Server GroupVDW (Windows NT)\Databases [
J Console Root
■ ^0 Microsoft SQL Servers
- ^J SQL Server Group j) DW (Windows NT)
-■ ’ V (‘.‘atabases
: * U master
♦ У model
$tUmsdb
♦ (J Northwind
‘;tУpubs
,tUtempdb
•,♦.УМоя база данных
д . I Data Transformation Services ;+i-’£j Management
■+: uJ Replication
:+! Security
•♦ , 1 Support Services
:♦ I Meta Data Services
J Console Root
■ ^0 Microsoft SQL Servers
- ^J SQL Server Group j) DW (Windows NT)
-■ ’ V (‘.‘atabases
: * U master
♦ У model
$tUmsdb
♦ (J Northwind
‘;tУpubs
,tUtempdb
•,♦.УМоя база данных
д . I Data Transformation Services ;+i-’£j Management
■+: uJ Replication
:+! Security
•♦ , 1 Support Services
:♦ I Meta Data Services
Рис. B1. Окно утилиты Enterprise Manager
Рис. B1. Окно утилиты Enterprise Manager
Создание базы данных в Enterprise Manager
Чтобы выполнить учебные примеры SQL, необходимо создать учебную базу данных, на которой вы будете экспериментировать. Для этого в древовидном списке окна утилиты Enterprise Manager раскройте узел, соответствующий вашей учетной записи (на рис. Bl DW (Windows NT)). Среди содержащихся в нем подузлов имеется Databases (Базы данных). Щелкните на нем правой кнопкой мыши и в раскрывшемся контекстном меню выберите New Database (Новая база данных). В результате откроется диалоговое окно Database Properties (Свойства базы данных).
В поле Name (Имя) введите имя базы данных (например, "Моя база данных") и щелкните на кнопке ОК. В результате в узле Databases появится узел с именем, совпадающим с именем базы данных.
Итак, создана пустая база данных, которая пока нс содержит ни одной таблицы с пользовательскими данными. Теперь создайте какую-нибудь таблицу, например, клиенты с символьными столбцами имя, Адрес, телефон. Для этого щелкните правой кнопкой мыши на имени вашей базы данных в древовидном списке и в контекстном меню выберите Создать | Table. В результате откроется окно New Table (Новая таблица), показанное на рис. В2. В этом окне следует ввести имена столбцов (Column Name), тип данных (Data Туре) и длину данных (Length). В рассматриваемом примере все столбцы являются символьными (строковыми, текстовыми), т. с. имеют тип char.
® New Table in 'Моя база данных* on *DW® New Table in 'Моя база данных* on *DWРис. В2. Окно New Table для определения столбцов таблицыРис. В2. Окно New Table для определения столбцов таблицы
® New Table in 'Моя база данных* on *DW
® New Table in 'Моя база данных* on *DW
Рис. В2. Окно New Table для определения столбцов таблицы
Рис. В2. Окно New Table для определения столбцов таблицы
После завершения определения столбцов (структуры) создаваемой таблицы закройте окно, после чего появится запрос о сохранении введенных данных. Подтвердите необходимость сохранения и в открывшемся диалоговом окне введите имя созданной таблицы (например, клиенты), а затем щелкните на кнопке ОК.
Если в древовидном списке раскрыть узел с именем вашей базы данных и щелкнуть на узле Tables (Таблицы), то в правой части
окна Enterprise Manager отобразятся все таблицы, входящие в эту базу данных. Среди множества служебных таблиц вы найдете и свою только что созданную таблицу.
Вы можете изменить структуру созданной таблицы или/и изменить содержащиеся в ней данные.
Для изменения структуры таблицы щелкните правой кнопкой мыши на ее имени и в контекстном меню выберите Design Table (Разработка таблицы). В результате откроется окно, аналогичное показанному на рис. В2. Теперь можно добавить новые или удалить имеющиеся столбцы, изменить их имена и/или другие параметры.
Для ввода, редактирования и удаления данных в таблице щелкните правой кнопкой мыши на ее имени и в контекстном меню выберите Open Table | Return all rows (Открыть таблицу | Вернуть все записи). В результате откроется окно, показанное на рис. ВЗ. В этом окне можно вводить и изменять значения столбцов, добавлять и удалять строки (записи). Поскольку новая таблица пуста, то начать следует с добавления записей.
’ ИМЯГ~ Иванов ИванI Петпов Пето’ ИМЯГ~ Иванов ИванI Петпов ПетоУЙЙ&И^^УЙЙ&И^^С-Петербург, ул. Апельсиновая 123-4567С-Петербург, ул. Апельсиновая 123-4567^УМ»^йоМАбА«№й<йй^^^УМ»^йоМАбА«№й<йй^^UUПетров Петр Москва,Красная площадьПетров Петр Москва,Красная площадь100-0000121-1212|100-0000121-1212|Извольский АхтарскИзвольский Ахтарск
’ ИМЯ
Г~ Иванов Иван
I Петпов Пето
’ ИМЯ
Г~ Иванов Иван
I Петпов Пето
УЙЙ&И^^
УЙЙ&И^^
С-Петербург, ул. Апельсиновая 123-4567
С-Петербург, ул. Апельсиновая 123-4567
^УМ»^йоМАбА«№й<йй^^
^УМ»^йоМАбА«№й<йй^^
U
U
Петров Петр Москва,Красная площадь
Петров Петр Москва,Красная площадь
100-0000
121-1212|
100-0000
121-1212|
Извольский Ахтарск
Извольский Ахтарск
Рис. ВЗ. Окно для модификации данных таблицы
Аналогичным образом можно создать и другие таблицы базы данных.
Создание и выполнение SQL-выражений в SQL Query Analyser
Для создания, редактирования и выполнения выражений на языке SQL рекомендуется использовать утилиту SQL Query Analyser. Эту утилиту можно вызвать из меню Tools (Инструмен-
ты), расположенного в верхней части окна Enterprise Manager. В результате откроется окно, показанное на рис. В4. Здесь можно ввести, как в обычном текстовом редакторе, SQL-выражение.
Рис. В4. Окно утилиты SQL Query Analyser
Прежде чем выполнить введенное SQL-выражение, рекомендуется его проверить, щелкнув на кнопке Parse Query (Анализировать запрос) с изображением галочки или нажав клавиши <Ctrl>+<F5>. Для выполнения выражения достаточно щелкнуть на кнопке с изображением стрелки вправо или нажать клавишу <F5>.
При желании созданное SQL-выражение можно сохранить в текстовом файле с расширением sql. Сохраненные в файлах SQL- выражения можно снова открыть в окне SQL Query Analyser. Соответствующие команды выбираются из меню File.
Работа с Microsoft Access 2003
Создание базы данных
При запуске программы Access открывается главное окно, в правой части которого находится панель Приступая к работе. На этой панели в разделе Открыть выберите команду Создать файл. В результате отобразится панель Создание файла. В разделе Создание на этой панели выберите команду Новая база данных. В открывшемся диалоговом окне Файл новой базы данных введите имя файла создаваемой базы данных (например, “Моя база
данных”) и сохраните его в какой-нибудь папке. Имя файла базы данных в Access имеет расширение mdb.
Итак, пустая база данных создана. В главном окне Access откроется окно Моя база данных: база данных. В этом окне выберите Создание таблицы в режиме конструктора. Откроется окно Таблица!: таблица, показанное на рис. В5. В этом окне определяется структура (свойства столбцов) таблицы — имена, типы, размеры и другие параметры столбцов.
Рис. В5. Окно Таблица 1: таблица для определения столбцов таблицы
По завершении определения столбцов (структуры) создаваемой таблицы закройте это окно, после чего появится запрос о сохранении введенных данных. Подтвердите сохранение и в открывшемся диалоговом окне введите имя созданной таблицы (например, клиенты) и щелкните на кнопке ОК.
В окне Моя база данных: база данных появится пиктограмма с именем созданной таблицы. Вы можете изменить структуру созданной таблицы или/и изменить содержащиеся в ней данные.
Для изменения структуры таблицы щелкните правой кнопкой мыши на ее имени и в контекстном меню выберите Конструктор. В результате откроется окно, аналогичное показанному на рис. В5. Теперь можно добавлять новые или удалять имеющиеся столбцы, изменять их имена и/или другие параметры.
Двойной щелчок на пиктограмме с именем таблицы откроет ее в режиме редактирования данных (рис. Вб). В этом окне можно вводить и изменять значения столбцов, добавлять и удалять строки (записи). Поскольку новая таблица пуста, то следует начать с добавления записей.
Аналогичным образом можно создать и другие таблицы базы данных.
Создание и выполнение SQL-выражений
В окне Моя база данных: база данных (см. рис. Вб) в списке объектов выберите Запросы, а в области задач, расположенной в правой части этого окна, выполните двойной щелчок на опции Создание запроса в режиме конструктора. В результате откроются два окна: Запрос!: запрос на выборку и Добавление таблицы. Окно Добавление таблицы закройте, после чего на экране останутся окна, показанные на рис. В7.
файл Главкафайл ГлавкаJ Открыть ^Конструктор J3 Создать j / |J Открыть ^Конструктор J3 Создать j / |333333ОбъектыОбъектыЗапросыЗапросыNUMNUMГотовоГотовоЗапрос! i запрос на выборкуЗапрос! i запрос на выборкуВставка Запрос Сервис Окно ОправкаВставка Запрос Сервис Окно ОправкаПоле: Имя таблиць: Сортировка: Вывод на экран; Условие отбора: ;'••‘или:Поле: Имя таблиць: Сортировка: Вывод на экран; Условие отбора: ;'••‘или:Создание запроса в режиме конструктора кЩ Создание запроса с помощью мастераСоздание запроса в режиме конструктора кЩ Создание запроса с помощью мастера
файл Главка
файл Главка
J Открыть ^Конструктор J3 Создать j / |
J Открыть ^Конструктор J3 Создать j / |
333
333
Объекты
Объекты
Запросы
Запросы
NUM
NUM
Готово
Готово
Запрос! i запрос на выборку
Запрос! i запрос на выборку
Вставка Запрос Сервис Окно Оправка
Вставка Запрос Сервис Окно Оправка
Поле: Имя таблиць: Сортировка: Вывод на экран; Условие отбора: ;'••‘или:
Поле: Имя таблиць: Сортировка: Вывод на экран; Условие отбора: ;'••‘или:
Создание запроса в режиме конструктора кЩ Создание запроса с помощью мастера
Создание запроса в режиме конструктора кЩ Создание запроса с помощью мастера
В меню Вид главного окна Access выберите команду Режим SQL или щелкните правой кнопкой мыши на верхней области окна Запрос!: запрос на выборку и в раскрывшемся контекстном меню выберите Режим SQL. В результате окно запросов примет вид, как показано на рис. В8. Это текстовый редактор, в котором можно вводить и редактировать выражения на языке SQL. При создании нового запроса в области ввода появляется строка "select;", т. е. ключевое слово запроса, на выборку данных. Если вам необходимо выбрать данные, то необходимо дописать недостающие элементы SQL-выражения. Для создания других запросов (например, модификации данных, создания таблиц и др.) следует удалить ключевое слово select и написать вместо него требуемый оператор.
Созданные запросы сохраняются в базе данных под именами, задаваемыми пользователем. При закрытии редактора запросов появляется предложение сохранить созданный запрос. В случае положительного ответа открывается диалоговое окно, в котором можно ввести имя запроса. Если выражение запроса было задано корректно, то его имя появится в списке запросов в окне Моя база данных: база данных. В противном случае появится сообщение об ошибке.
Чтобы отредактировать созданный ранее запрос, достаточно щелкнуть правой кнопкой мыши на его имени и в контекстном меню выбрать Конструктор.
Чтобы выполнить запрос, достаточно сделать двойной щелчок на его имени в списке запросов или в контекстном меню, раскрываемом щелчком правой кнопкой мыши на имени запроса, выбрать команду Открыть.
Глава 1
Основы реляционных баз данных
Программные средства работы с реляционными базами данных, в отличие от многих других, базируются на довольно строгой теории отношений, которая, в свою очередь, основана на теории множеств. Чтобы понять суть задачи создания реляционной базы данных, а также операций над данными, достаточно рассмотреть на теоретическом (абстрактном) уровне всего лишь несколько основных положений теории отношений. Это быстрее и, в конечном счете, лучше, как мне представляется, чем изучать большое количество конкретных примеров и ситуаций.
Таблицы, из которых состоит любая реляционная база данных, представляют собой некоторые отношения, а отношения являются не чем иным, как множествами. Все запросы к базе данных, направленные на извлечение из нее нужных записей, интерпретируются как инструкции по выполнению тех или иных операций, являющихся, в конечном счете, операциями алгебры множеств и исчисления предикатов.
В этой главе мы рассмотрим основные вопросы, относящиеся к реляционным базам данных, с более общей точки зрения. Зато все последующие главы будут посвящены конкретным приемам использования языка SQL.
.1. Множества
Множество является настолько общим понятием, что не имеет определения, которое можно было бы выразить в еще более общих и простых понятиях. Поэтому в математической теории множеств определение (в математическом смысле) понятия
множества отсутствует. Вместе с тем, в его основе лежит некий образ, который Георг Кантор (один из создателей теории множеств) описал как собрание определенных и различных между собой объектов интуиции или интеллекта, мыслимое как единое целое. Эти объекты называются элементами множества.
Примечание
В математике многое не так, как в других науках. Если мы обратим внимание на теорию, скажем, "блям-блямчиков", то с точки зрения математической культуры совсем не важно, что такое "блям- блямчики". Иначе говоря, строгое в математическом смысле определение "блям-блямчиков" не входит в теорию. "Блям- блямчики" — лишь наименование предмета теории, а ее содержание — выявление свойств и отношений "блям-блямчиков". Только изучив эту теорию, мы получим более или менее полное представление о том, что такое "блям-блямчики". Это представление возникает из понимания того, каким образом объект изучения и его компоненты связаны между собой и объектами окружающего мира. Так, например, К. Поппер (исследователь логических аспектов языка) удачно заметил, что смысл слов определяется через их использование в речи (текстах). Тем не менее, любая книга по теории, в том числе и математической, обычно начинается с пространных описаний предмета и стремится дать более четкое его "определение". Однако это по существу лишь стимуляция интуиции читателя, направленная на то, чтобы он настроился на предмет изучения и преодолел возможные психологические барьеры, возникающие при встрече с новым. По большому счету стимуляция интуиции не имеет непосредственного отношения к самой теории. Другими словами, интерпретация теории не является частью этой теории. Это задача ее применения к жизни.
Существенным для канторовского понимания множества является то, что собрание объектов само по себе рассматривается как один объект. На природу объектов, которые могут входить в множество, не накладывается никаких ограничений. Это могут быть числа, наборы символов, люди, атомы и т. п.
Множества могут быть конечными и бесконечными. Конечные множества содержат элементы, которые можно сосчитать или перечислить. Это означает, во-первых, что имеется принципиальная возможность сопоставить каждому элементу множества некоторое натуральное число (1, 2, 3, ...), и, во-вторых, этот пересчет когда-нибудь закончится. Так, например, бесконечное множество
всех целых чисел можно начать перечислять, но этот процесс никогда не закончится: для любого целого числа можно создать следующее, прибавив к нему 1. А множество всех действительных чисел, также являющееся бесконечным, даже начать перечислять невозможно. Интересно, что этот факт был установлен Кантором только лишь в конце XIX века и произвел на математиков ошеломляющее впечатление.
Существуют и конечные множества, перечислить элементы которых практически невозможно. Я хочу обратить ваше внимание на слово "практически". Представителем таких множеств может служить, например, множество всех погибших в Куликовской битве, а также множество всевозможных последовательностей нулей и единиц длиной 100. Мы уверены, что эти множества конечны. Количество погибших не может быть бесконечным, но мы либо не знаем, как их перечислить, либо решение этой задачи требует неимоверно больших затрат ресурсов. Количество последовательностей из нулей и единиц длиной 100 равно 2100. Это число больше количества атомов в видимой части вселенной, и, следовательно, не хватит жизни многих поколений людей, чтобы перечислить (или сгенерировать) такие последовательности даже с помощью самого быстродействующего компьютера. Однако в математике практическая и теоретическая невозможности чего-либо — это различные вещи. Практические трудности игнорируются математиками, а выявление принципиальной недостижимости (фундаментальных пределов) чего-либо является для них чрезвычайно ценным результатом. Математическая теория множеств рассматривается сейчас как фундамент всей математики, а также как пространство и средство изучения бесконечного. Это очень интересно, но данная книга не об этом.
В компьютерной практике и, в частности, в базах данных мы имеем дело с конечными множествами, хотя иногда и очень большими. Поэтому мы застрахованы от логических неопределенностей и тупиков, которые могут встретиться в области бесконечных множеств. Однако нам предстоит преодолевать практические трудности, связанные с очень большими конечными множествами, но это задача скорее технологии, чем математики. Теперь обратимся к формальным аспектам конечных множеств, чтобы кратко и ясно представить себе то, с чем в большом многообразии частностей мы имеем дело в жизни.
Итак, множество является тем, что имеет элементы, а элементы —- это то, что либо входит, либо не входит в данное множество. В множестве не может быть двух или более одинаковых элементов, а порядок расположения элементов в множестве не имеет никакого значения. Заметим, что этим множества отличаются от массивов — объектов языков программирования и компьютерных программ. Массивы в программах так или иначе упорядочены и могут содержать одинаковые элементы. Вместе с тем, ничто не мешает рассматривать массивы как модель (некоторое приближение) множеств. Важно лишь помнить, что массив — это некоторое программное воплощение идеи конечного множества, имеющее дополнительные свойства, которые не обязано иметь множество.
Обозначим некоторое множество символом Л, а все элементы, входящие в это множество, — символами av а2,..., aN. Тогда запись А = ^а15 а2,..., aN^ означает, что множество А содержит только те элементы а^ а2,..., aN , которые указаны в фигурных скобках. Напомню, что все элементы различны, а их порядок в фигурных скобках не имеет значения.
Какой-либо элемент х может принадлежать или не принадлежать множеству А. Чтобы указать, что элемент х принадлежит множеству А, пишут хе А, а если х не принадлежит А, то пишут х е А. Множество может и не содержать элементов, тогда его называют пустым и обозначают как 0.
Простейший способ для определения конкретного множества состоит в том, чтобы явно указать все элементы, принадлежащие этому множеству. Это так называемый экстенсиональный способ определения множества. Если количество элементов, входящих в множество А, невелико, то этот способ вполне применим на практике: достаточно написать выражение вида А = {аь а2,..., а^}. Однако при достаточно больших множествах этот способ не подходит. Тогда используют так называемый интенсиональный (неявный) способ определения множеств. Он основан на использовании некоторой функции (алгоритма), которая определяет для каждого элемента, принадлежит ли он данному множеству или нет. Пусть для некоторого множества А определена такая функ-
ция f^to- Если вместо переменной х подставить в ее выражение конкретный элемент а, то результатом вычисления Рл(а) будет либо ИСТИНА (true), либо ЛОЖЬ (false), в зависимости от того, принадлежит или нет элемент а множеству А. Таким образом, функция Р^х) принимает в качестве аргументов элементы из некоторой области (универсума) и возвращает одно из двух значений, которые мы здесь обозначаем как ИСТИНА и ЛОЖЬ (т. е. функция Рл(х) является двузначной). С другой стороны, мы можем сами определить какую- нибудь двузначную функцию Q(x), принимающую в качестве аргумента значения из некоторого универсума. Тогда эта функция будет определять некоторое множество, а именно множество всех тех элементов универсума, для которых эта функция возвращает значение ИСТИНА. Указанные двузначные функции в математической логике называются предикатами.
В качестве примера рассмотрим выражение х < 5. Это типичное выражение сравнения. Здесь через х обозначена переменная, значения которой берутся из множества всех действительных чисел. Тогда это выражение может принимать значения ИСТИНА или ЛОЖЬ в зависимости от того, какое значение будет подставлено вместо х. Вопреки сложившейся математической практике мы могли бы записать данное выражение и в таком виде: < 5(х). Здесь < 5 — обозначение предиката "быть меньше 5". Данный предикат определяет множество всех действительных чисел, которые меньше числа 5. Обратите внимание, что мы некоторым конечным образом определили бесконечное множество.
Аналогично мы можем определить с помощью предиката Красный^ множество всех красных элементов. Разумеется, мы должны иметь алгоритм вычисления данного предиката, т. е. алгоритм определения, является ли предъявленный элемент х красным или нет.
В явном виде (экстенсионально) пустое множество обозначается как { }. Интенсионально пустое множество определяется через некий предикат, который ложен для всех элементов универсума.
Примечание
Предикаты в математической логике являются двузначными функциями. Однако как в математике, так и на практике нередко встречаются ситуации, к которым больше подходит многозначная и, в частности, трехзначная логика. Так, для некоторого элемента х результатом вычисления значений некоторого предиката Р(х) может быть не только ИСТИНА или ЛОЖЬ, но и некоторое третье значение, например, null. Последнее интерпретируется как "не определено" или "не известно". Такая логика в том или ином виде принята при поддержке современных баз данных. Например, столбец таблицы базы данных может содержать помимо наборов каких-то символов особое значение null, которое понимается как "еще не введенное", "пока неизвестное" и т. п. Однако замечу, что понимание трехзначной логики лучше усваивается при понимании классической двузначной логики.
Между множествами определяется отношение включения. Так, множество А включается в множество В (это утверждение записывается как А с В), если каждый элемент множества А принадлежит и множеству В. В этом случае говорят, что множество А является подмножеством множества В. Два множества равны (А = В), если А с В и В с А , т. е. оба множества включаются друг в друга и, следовательно, состоят из одних и тех же элементов. Очевидно, что любое множество является своим подмножеством, т. е. для любого множества А выполняется отношение А с А . Однако в общем случае из того, что А с В, еще не следует, что В с. А.
Пустое множество 0 включается в любое множество, т. е. для любого множества А имеет место включение 0 с Л. Этот факт, хоть он и очевиден, можно доказать. Предположим, что 0с ^ ложно. Но это может быть только в том случае, когда существует некий элемент х, такой, что х g 0 и х £ А. Однако это не возможно, т. к. пустое множество 0 не имеет элементов по определению.
Не следует смешивать отношение принадлежности g между элементами и множествами и отношение включения с между множествами. Так, например, если х g А и A g В, то это еще не означает, что хе В. Другой пример: если Л = {В, а, с} и В = {я, d},
то Be А, но не верно, что Вс А. Верно лишь, что {б}сЛ, т. е. множество, состоящее из элемента В, является подмножеством множества А. Иначе говоря, элементы некоторого множества А сами могут быть множествами, но элементы последних не обязательно являются элементами множества А. Так, в рассматриваемом примере a е А , aeB, d е В, но d & А. Заметим также, что элемент и множество, содержащее единственный этот элемент (т. е. х и {х}), — разные понятия.
Над множествами можно выполнять различные операции: объединение (U), пересечение (А), вычитание (-) и декартово произведение (х).
Объединение множеств А и В есть множество, обозначаемое как A U В, которое содержит все элементы множества А и все элементы множества В. Пусть, например, А = {о, 6, с, х, у} , В = {я, 6, х, у, z}. Тогда A U 5 = {я, 6, с, х, у, z} . Напомню, что в любом множестве все элементы должны быть различны. Другими словами, множество AUB содержит только такие элементы, которые принадлежат множеству А или множеству В (возможно, и обоим одновременно). В частности, AU0 = А.
Пересечение множеств А и В есть множество, обозначаемое как A AS, которое содержит все элементы, принадлежащие как множеству А, так и множеству В. Если таких элементов нет, то пересечение множеств пусто (AQB = 0). Например, пусть А = ^а, Ь, с, х, у} , В = {о, Ь, х, у, z} . Тогда А А В = {а, 6, х, у}. В частности, А А 0 = 0 •
Вычитание из множества А множества В дает в результате множество, называемое разностью этих множеств, и обозначается как А-В . Это множество содержит все элементы множества А, которые не принадлежат множеству В. Например, пусть A = ^a,b,c)d,x)y^, В = {а, b, х, у, z] . Тогда А-В = ^с, d^ . В частности, А-0 = А. Если множества А и В не пересекаются (т. е. АПВ = 0), то А-В=А. Множество А-В называют также дополнением множества В до множества А.
Декартово произведение множеств будет подробно рассмотрено далее в разд. 1.2.
Обозначим через / множество всех элементов, которое называется универсумом. Предполагается, что любой элемент, с которым мы имеем дело, принадлежит универсуму. Тогда любое множество А является подмножеством универсума: А^1. Разность I - А называют дополнением множества А до универсума (или просто дополнением) и обозначают через А .
£ Примечание
В математической литературе для обозначения операции вычитания множеств часто используют обратный слэш (\).
Далее приведены основные равенства, используемые при преобразовании выражений с несколькими операциями над множествами:
А = А — закон двойного дополнения (отрицания);
A U А = I — закон исключения третьего;
А П А = 0 — закон противоречия;
,4U0 = ,4;
АП/=А;
законы Моргана:
(тв) = Я11В;
(яив)=лпв;
законы коммутативности (перестановки):
лиВ = В1)Л;
ЛПВ=ВПЛ;
законы ассоциативности (группировки):
v4U(BUC) = (^UB)UC;
ЯП(ВПС) = (ЛПВ)ПС;
законы дистрибутивности (сочетания операций):
^u(snc) = (^us)n(^uc);
лп(яис) = (лпя)и(лпс).
Чтобы доказать равенство двух множеств, обычно показывают, что первое множество включается во второе, а второе — в первое. Кроме того, в самом доказательстве относительно множеств используют предложения типа "пусть некоторый элемент х принадлежит такому-то множеству". Поскольку рассмотрение ситуации начинается с достаточно произвольного элемента, то в итоге мы получаем утверждение относительно не только этого элемента, но и всего множества, которому этот "произвольный" элемент принадлежит. Это общие правила доказательства равенств и включения множеств.
В качестве упражнения докажем одно из приведенных ранее равенств, а именно один из законов дистрибутивности:
ли(вПс)=(лия)П(лис).
Для этого достаточно доказать два включения: ди(вПс)с(лия)Л(лис)
И
(лия)Л(лис)сди(вПс).
Доказательство включения ^и(^ПС)с(JUS)D(^UC).
Выберем произвольный элемент хеЛД-йПС). Тогда хе А или х е В П С . Если х е А, то верно и то, что х 6 A U В и х е A U С, а следовательно, х есть элемент пересечения этих множеств. Если же х е В П С , то хе В и х е С. Следовательно, и в этом случае xeylU# и xeAUC. Таким образом, х е (A U В) П (A U С). Поскольку мы рассматривали произвольный элемент хе^и(5ПС), то требуемое включение множеств доказано.
2 Зак. 120
Доказательство включения (A U В) П (A U С) с A U (В П С).
Пусть хе(лиР)П(^ис). Тогда xeA\JB и xgJUC. Следовательно, хе А или же хе В и хе С, т. е. хе Л1(5ПС).
На этом доказательство равенства двух множеств заканчивается.
Итак, мы рассмотрели понятие множества и основные операции над множествами. Теперь кратко рассмотрим понятия высказывания и предиката. Высказывание, с точки зрения математической логики, не зависимо от его информационного содержания и смысла, либо истинно, либо ложно. Например, высказывание "дважды два равно четырем" считается истинным, а высказывание "все птицы могут летать" — ложным (поскольку есть, например, страус, который не летает). Однако мы можем составить выражение, в котором будут фигурировать некие изначально неопределенные объекты, вроде переменных в математических выражениях. Например, выражение х<10 может быть как истинным, так и ложным в зависимости от того, что имеется в виду под переменной х (т. е. каково ее значение). Выражение "нечто является красным" также не может принять конкретного значения истинности, пока мы не определим, что понимается под словом "нечто". Так, если в выражение х<10 подставить число 3, то оно примет вид 3 < 10 и будет истинным. Если же в это выражение подставить число 12345, то получится ложное высказывание. Выражения с переменными, возвращающие одно из двух возможных значений (ИСТИНА или ЛОЖЬ) после подстановки вместо переменных конкретных значений, называются предикатами. Пусть имеется некоторое выражение с переменными xl, х2,..., хп. Обозначим его как Р(х1, х2,..., хп), подчеркивая тем самым, что это просто некоторая функция от п аргументов. Особенностью этой функции является только то, что она двузначная — принимает одно из двух возможных значений (ИСТИНА или ЛОЖЬ).
Как в обычной речи, так и в математической логике часто используются так называемые общеутвердительные и частноутвердительные высказывания. Предложение "все люди млекопитающие" является примером общеутвердительного высказывания,
а предложение "некоторые люди — женщины" — пример частноутвердительного высказывания. Заметим, что оба эти высказывания являются истинными. Разумеется, мы могли бы придумать и какие-нибудь ложные высказывания. В этих высказываниях, как нетрудно заметить, используются слова "все" и "некоторые", играющие определенную функциональную роль. Они указывают, в насколько большой области значений переменных предикат является истинным. Поэтому эти слова называются кванторами. Слово "все" означает, что предикат истинен для всех (каждого, любого) элемента универсума, а слово "некоторые" — по крайней мере для какого-нибудь одного элемента. В математической логике эти кванторы обозначаются через V и 3 и называются кванторами всеобщности и существования соответственно.
Выражение ^хР(х) понимается как высказывание, что для всех элементов универсума предикат Р(х) истинен. Выражение ЭхР(х) означает, что предикат Р(х) истинен для некоторого элемента универсума. Высказывания Х/хР(х) и ВхР(х) уже не зависят от значения х и могут быть как истинными, так и ложными. Например, высказывание Vx(sin(x) = 1) ложно, а высказывание 3x(sin(x) = l) истинно.
К предикатам, как и к высказываниям, применимы операторы отрицания ( ), конъюнкции (&) и дизъюнкции (v). В результате применения этих операторов получается совсем другой предикат.
Оператор отрицания изменяет значение предиката на противоположное. Так, если предикат Р(х) истинен, то Р(х) ложен, и наоборот.
Оператор конъюнкции выполняет роль логического союза И, а дизъюнкции — логического союза ИЛИ. Так, для двух предикатов Р(х) и Q(x) предикат Р(х) & Q(x) истинен только тогда, когда истинны одновременно оба предиката; предикат P(x)vg(x) истинен только тогда, когда истинен хотя бы один из предикатов.
Из вышеизложенного следует, что выражение VxP(x) эквивалентно выражению Р(а1)&Р(а2)&...&Р(ап), где al, а2,..., ап — элементы универсума. Аналогично, выражение ЗхР(х) эквивалентно выражению Р(я1) v Р(а2) v... v P{ah). Нетрудно заме-
тить, что если истинно высказывание VxP(x), то истинно и высказывание ЗхР(х), хотя обратное в общем случае не верно (если верно частное утверждение, то соответствующее общее утверждение может быть и не верным). Например, утверждение "некоторые люди умны" истинно, но утверждение "все люди умны" скорее ложно, чем истинно.
До сих пор мы рассматривали предикаты только от одной переменной. Однако предикаты могут содержать и несколько переменных. Например, предикат х2 - у = у2 - х представляет собой равенство двух числовых выражений с двумя переменными — х и у. Следующее выражение является истинным высказыванием:
Х/хЗу^х2 - у = у2 - х\
Изменение типа квантора в выражении может изменить его значение истинности. Так, например, следующее высказывание ложно:
3xVy(*2 - У = у2 - х).
Однако для любого предиката Р(х,у) если истинно высказывание З^Х/х^х,^) , то истинно и высказывание Vx3yP(x,y). Иначе говоря, перестановка кванторов всеобщности и существования вместе с соответствующими им переменными не изменяет значения истинности высказывания.
Нетрудно заметить аналогию между операциями над множествами (-, U, П) и над предикатами ( , &, v). Эта аналогия обусловлена тем, что, во-первых, множества можно задать не только явным образом путем перечисления их элементов, но и через предикаты и, во-вторых, любой предикат задает множество всех тех элементов, для которых он истинен.
Пусть Р(х) — некоторый предикат. Тогда справедливы следующие равенства:
УхР(х) = ЗхРСО;
ЗхР(х) = VxP(x).
Пусть, например, Р обозначает свойство "быть красным". Тогда первое из двух указанных равенств означает, что высказывание
"не верно, что все х красные" эквивалентно высказыванию "некоторые х не красные". Второе равенство утверждает эквивалентность высказываний "не верно, что есть красный объект" и "все объекты не красные".
Отношения
Общие сведения
В предыдущем разделе мы рассмотрели понятия множества и предиката. Любой предикат определяет некоторое множество элементов из какого-то универсума, а именно множество тех элементов, для которых он истинен. Таким образом, предикат можно интерпретировать как двузначную функцию, определяющую, обладает ли элемент неким свойством, например, свойством принадлежности данному множеству.
Предикат Р(х) от одной переменной х определяет множество элементов, при подстановке которых вместо х этот предикат становится истинным высказыванием. Однако предикаты могут содержать и несколько переменных: Р^хХ, х2,..., хи). Такие предикаты определяют множество элементов вида (xl, х2,..., хп), т. е. последовательностей элементов универсума. Такие последовав тельности еще называют кортежами. Например, предикат х2 + у2 = 1 определяет множество точек (их координат), лежащих на окружности единичного радиуса с центром в начале системы координат.
В общем случае элементы xl, х2,..., хп кортежей могут выбираться не из одного множества-универсума, а из разных. Пусть переменные xl, х2, ..., хп в предикате Р(х1, х2,..., хт?) пробегают значения из множеств АХ, Л2,..., Ап соответственно. Тогда говорят, что этот предикат определяет отношение между элементами множеств АХ, А2,..., Ап или просто между множествами АХ, А2,..., Ап. Это отношение обозначим через Р(АХ, А2,..., Ап), т. е. так же, как и предикат.
Итак, отношение есть множество, элементами которого являются кортежи, которые, в свою очередь, состоят из элементов других множеств. При этом между любым кортежем отношения и самим отношением имеет место отношение принадлежности е , но не включения с. Кортеж представляет собой не множество, а упорядоченную последовательность элементов.
Рассмотрим в качестве примера отношение отцы_и_дети. Это отношение между множеством мужчины и множеством люди: отцы_и_дети (мужчины, люди). Мы не будем сейчас определять предикат, задающий это отношение. Алгоритм определения отцовства может быть различным. Например, можно ограничиться только опросом всех мужчин на предмет, каких детей они имеют, провести генетическую экспертизу и т. п. Как бы то ни было, в результате мы получим множество всех кортежей вида (xl, х2), в которых через xl и х2 обозначены люди, такие, что xl является отцом для х2. В качестве имен людей следует выбрать их уникальные идентификаторы. Фамилия, имя и отчество для этой цели вряд ли подойдут. Возможно, окажется достаточным использовать дополнительные паспортные данные, отпечатки пальцев или снимок радужной оболочки глаза. Очевидно, что множества отцов и женщин включаются в множество людей, но ни одна женщина не принадлежит множеству отцов. Поэтому в рассматриваемом отношении отцы_и_дети не будет ни одного кортежа, в котором на первом месте стояло бы имя женщины. Кроме того, ни один человек не может быть отцом для самого себя. Поэтому в отношении не будет ни одного кортежа, в котором первый и второй элемент совпадают. Эти и, возможно, другие особенности, характеризующие отношение, называются ограничениями его целостности.
Задавая отношение отцы и дети, мы можем, по крайней мере теоретически, поступить следующим образом. Сначала возьмем множество всевозможных кортежей (xl, х2), в которых xl е мужчины и х2елюди, а затем выберем из этого множества только те кортежи, в которых xl является отцом для х2. Полученное множество и будет представлять собой интересующее нас отношение отцы_и_дети.
Множество всех возможных кортежей (xl, х2), в которых xl е Л и х2 е Л2, называется декартовым произведением множеств А1 и А2 и обозначается как АА х А2 . Количество элементов
в декартовом произведении равно произведению количеств элементов в множествах Л1 и Л2. Например, если Al^^a, Ъ, с} и А2 = {х, у} , то:
Л1X ^2 = {(а, х), (а, у), (Ь, х), (6, у), (с, х), (с, у)} -
Рассмотрим еще один пример. Пусть X, Y— множества действительных чисел, составляющих отрезки числовых осей прямоугольной системы координат, тогда множество X х Y можно представить как множество точек прямоугольника с абсциссами из множества X и ординатами из К Кривая, нарисованная в этом прямоугольнике, задает некоторую зависимость (отношение) у = /(х) между X и Y, которую можно представить множеством пар координат точек, лежащих на кривой. Очевидно, это множество является подмножеством декартового произведения X х Y и может быть представлено в виде двухстолбцовой таблицы. В одном столбце этой таблицы записываются значения х, а в другом — соответствующие значения у функциональной зависимости у = /(х).
Декартово произведение для и множеств А1, Л2,..., Ап обозначается как А1хА2х...хАп и состоит из всевозможных кортежей вида (xl, х2,..., хп) , в которых xlevil, х2е^2,... , хпеАп. Например, пусть товары на складе имеют характеристики, выбираемые из множеств НАИМЕНОВАНИЕ, количество, цена и поставщик. Тогда таблица, содержащая сведения о всех товарах на складе, представляет отношение между указанными характеристиками, а все ее строки — кортежи, принадлежащие некоторому подмножеству декартового произведения:
НАИМЕНОВАНИЕ X КОЛИЧЕСТВО X ЦЕНА X ПОСТАВЩИК.
Итак, некоторое отношение Л(?11, Л2,..., Ап) есть подмножество декартового произведения Alx А2х...х Ап, т. е. имеет место включение /?(Л1, Л2,..., Ап)^ Alx А2х...х Ап . При п-\ jR(A1) представляет собой просто подмножество множества А1 и называется унарным. При п = 2 отношение называется бинарным. Так, отношения равенства, порядка и сходства являются примерами бинарных отношений. При и = 3 отношение называется тернарным. Нередко л-арные отношения называют также «-местными.
Способы представления отношений
К представлениям отношений мы приходим, когда требуется наглядность или определенное удобство для хранения данных и манипулирования ими. Рассмотрим несколько вариантов.
В вырожденном случае унарных отношений мы имеем дело с обычными множествами, которые естественным образом представляются списком своих элементов или же одностолбцовой таблицей, в каждой строке которой указан некоторый элемент множества. При этом нс следует забывать, что множество не предполагает никакого порядка элементов. Поэтому два списка, содержащие одни и те же элементы, но упорядоченные различным образом, являются эквивалентными с точки зрения теории отношений. Иначе говоря, они представляют одно и то же отношение.
Бинарные (двухместные) отношения можно представить в виде графов, а также двухстолбцовых таблиц. В графовом представлении каждой вершине соответствует некоторый элемент одного из двух множеств. От одной вершины к другой проводится стрелка, если первый элемент находится в рассматриваемом отношении со вторым. Например, отношение достижимости населенных пунктов из данного пункта с помощью авиарсйсов является бинарным отношением между множеством авиарейсы и множеством пункты_назначения. Мы проводим из вершины графа достижимости, соответствующей, например, рейсу Р123, стрелку в вершину ’’Москва", если этим рейсом можно добраться до данного пункта. Очевидно, информацию о достижимости пунктов авиарейсами можно представить и в виде двухстолбцовой таблицы. В первом столбце указываются авиарейсы, а во втором — пункты назначения. В каждой строке указывается, каким авиарейсом и куда можно долететь. Каждая строка таблицы представляет собой кортеж отношения.
Тернарные (трехместные) отношения можно представить в виде графов, а также трехстолбцовых таблиц. В графовом представлении элементами множеств помечаются не только вершины, но и стрелки. Пусть, например, отношение содержит сведения не только о том, каким авиарейсом в какие пункты можно попасть, но и за какое время. Время полета можно указывать непосредственно около стрелки, соединяющей номер авиарейса и пункт
назначения. Представление этого отношения с помощью таблицы очевидно.
Бинарные и тернарные отношения, атрибуты которых принимают значения из числовых множеств, представляются еще и в виде графиков — кривых и поверхностей в той или иной системе координат.
Отношения, арность которых больше 3, представить графически проблематично. Даже если как-то исхитриться это сделать, то результат вряд ли будет наглядным. Остается табличный способ, как достаточно универсальный для представления отношений.
При табличном представлении каждому кортежу отношения взаимооднозначно соответствует строка (запись) в таблице. Но не всякая таблица представляет некоторое отношение. Таблица — это прямоугольная сетка, в ячейках которой может находиться все, что угодно. При этом различные строки этой таблицы могут содержать в соответствующих столбцах одинаковые данные. Другими словами, таблицы могут содержать идентичные строки. В этом случае совокупность всех строк таблицы не является множеством, а значит, не представляет собой отношения. Напомню, что множество — это совокупность различных элементов. Таким образом, любое отношение (в определенном ранее смысле) можно представить в виде таблицы, но обратное утверждение, вообще говоря, не верно — не всякая таблица представляет какое-нибудь отношение.
При представлении отношения явным образом в виде таблицы, се нельзя рассматривать просто как вместилище данных, в которое можно добавлять новые записи или удалять старые. Добавление, удаление и изменение данных в такой таблице приводит либо к другому отношению, либо к никакому отношению (если в таблице окажутся одинаковые записи).
Табличное представление отношений — это чрезвычайно плодотворный технологический прием, обеспечивший создание и широкое практическое применение баз данных. То обстоятельство, что отношения — просто множества, позволило изучать проблемы реляционных баз данных на теоретическом уровне, в мйрс математики (алгебра множеств и исчисление предикатов), а нс только в мире технологии. В результате разработка СУБД, кон-
крстных баз данных, языков манипулирования данными, в том числе и SQL, водрузилась на твердый теоретический фундамент. Далее мы будем рассматривать отношения, представляя их в виде таблиц. Введем несколько терминов, которые обычно применяются в теории отношений: Так, говоря о некотором отношении 7?(>11, А2,..., Ап), мы имеем в виду следующее.
□ Отношение имеет имя R. Например, сведения о товарах, хранящихся на складе, образуют некоторое отношение, которому можно дать имя склад.
□ Множества Al, А2,..., Ап , для которых определено отношение R, имеют различные имена, называемые атрибутами отношения. Мы будем считать, что Al, А2,..., Ап — это атрибуты отношения. Совокупность всевозможных значений любого множества с именем Ai (j = \,2,..., п) называется доменом атрибута Ai. Заметим еще раз, что в отношении не может быть двух и более одинаковых атрибутов, хотя их домены могут быть и одинаковыми (в смысле равенства множеств). Например, отношение склад может быть определено для атрибутов НАИМЕНОВАНИЕ, количество, цена и поставщик. Структуру этого отношения можно записать так: СКЛАД (наименование, количество, цена, поставщик). Каждый атрибут имеет свой домен — множество возможных значений. Например, доменом атрибута количество может быть множество неотрицательных чисел. При табличном представлении отношения каждому атрибуту соответствует заголовок столбца, а домену — множество значений в этом столбце.
□ Порядок перечисления атрибутов в структуре отношения не имеет значения. Иначе говоря, перестановка атрибутов местами оставляет само отношение прежним, хотя вид таблицы, представляющей это отношение, меняется. Например, отношения СКЛАД (наименование, количество, цена, поставщик) и склад (поставщик, наименование, количество, цена) считаются эквивалентными.
□ Элементами отношения являются кортежи — последовательности значений атрибутов отношения. В отношении не может быть двух и более одинаковых кортежей, а порядок расположения кортежей не имеет значения. При табличном
представлении отношения каждому кортежу соответствует строка таблицы. Строки таблицы мы будем называть записями.
Операции над отношениями
Коль скоро отношение — это множество (а именно множество кортежей или, другими словами, записей), то к ним применимы все теоретико-множественные операции, рассмотренные ранее. Однако в реляционной теории особую роль играют специальные операции, лежащие в основе языка SQL: □ селекция;
□ проекция;
□ естественное соединение.
Эти операции выражаются некоторым образом через обычные операции над множествами, такие как объединение, пересечение, вычитание и декартово произведение. Рассмотрим их более подробно.
Селекция
Операция селекции (выборки, ограничения) отношения выделяет из него некоторое подмножество кортежей, которые удовлетворяют некоторому условию. При работе с базами данных выборка из всей имеющейся совокупности только требуемых данных — это наиболее часто применяемая операция. Конкретное определение этой операции зависит от вида условия. Одной из частных разновидностей операции селекции является операция сужения отношения, которую мы здесь и рассмотрим.
Пусть имеется некоторое отношение /?(^41, Л2,..., Ап). Обозначим домены атрибутов Al, А2,..., Ап этого отношения через DI, D2,..., Dn соответственно. Напомню, что домен атрибута — это множество его допустимых значений. Далее обозначим через бЛ, б/2,..., dn какие-нибудь подмножества доменов Z)l, D2,..., Dn соответственно (т. е. di cl Di, i = 1, 2,..., n). Тогда сужением отношения Л(т41, А2,..., Ап) на множество d\xd2x.,.xdn называется отношение 7?(Л1, Л2,..., An)C\dlxd2x...xdn. По существу, данная операция
просто выделяет из исходного отношения только те кортежи, в которых элементы (значения атрибутов) принадлежат указанным подмножествам соответствующих доменов. Заметим, что в частном случае, когда di = Di для всех / = 1, 2,..., и, сужение отношения /?(>41, Я2,..., Ап) на множество dYxd2x...xdn равно исходному отношению R(A\, А2,..., Ап).
На рис. 1.1 в качестве примера показано некоторое отношение R(A\, А2, АЗ), домены Z>1, D2 и D3 атрибутов >41, А2 и >43, некоторые подмножества dV d2 и d3 этих доменов, декартово произведение бЛхб/2хб/3 и результат сужения исходного отношения на множество кортежей d\ х d2 х d3.
А1А2АЗаXmаУпbсZXкПсXрА1А2АЗаXmаУпbсZXкПсXрR(A1 ,А2,АЗ): таблицаR(A1 ,А2,АЗ): таблица!Ш£^5О££2а4£21-ай-А1■тиаак...геивьек;—аXГПауmЬхmьУmаXnа ЬnЬ|хnьУn!Ш£^5О££2а4£21-ай-А1■тиаак...геивьек;—аXГПауmЬхmьУmаXnа ЬnЬ|хnьУnd1xd2xd3 : таблицаd1xd2xd3 : таблица£)1 D2 D3£)1 D2 D3d1 d2 d3d1 d2 d3
А1
А2
АЗ
а
X
m
а
У
п
b
с
Z
X
к
П
с
X
р
А1
А2
АЗ
а
X
m
а
У
п
b
с
Z
X
к
П
с
X
р
R(A1 ,А2,АЗ): таблица
R(A1 ,А2,АЗ): таблица
!Ш£^5О££2а4£21-ай-
А1
■тиаак...геивьек;—
а
X
ГП
ау
m
Ьх
m
ь
У
m
а
X
n
а Ь
n
Ь|х
n
ь
У
n
!Ш£^5О££2а4£21-ай-
А1
■тиаак...геивьек;—
а
X
ГП
ау
m
Ьх
m
ь
У
m
а
X
n
а Ь
n
Ь|х
n
ь
У
n
d1xd2xd3 : таблица
d1xd2xd3 : таблица
£)1 D2 D3
£)1 D2 D3
d1 d2 d3
d1 d2 d3
А1A2A3
a
lxm
A-.S... .»..»V*W.V»» *»•*••... «W. l^^.S W#VW. w #• .-.^...•..•v.-.>.V.4..We#.4r..-irr...r#*1 Av..•..,.■. ..A A'»..wAv^.-..-^»....1.WA-. a-. —A-.
a
Уn
Сужение R(A1 ,A2,A3)
на d1xd2xd3 : таблица
Рис. 1.1. Пример сужения отношения
Рассмотрим в качестве примера отношение зарплата (сотрудник, выплата), которое можно представить в виде двухстолбцовой таблицы. В этой таблице в столбце сотрудник указаны имена сотрудников некоторой фирмы или какого-то ее подразделения, а в столбце выплата — денежная сумма, причитающаяся соответствующему сотруднику. Допустим, нас интересуют выплаты только Иванову и Петрову — элементам домена атрибута сотрудники. Обозначим через Сотрудник множество всех значений столбца сотрудник. Это и есть домен атрибута сотрудник. Аналогично, обозначим через Выплата домен атрибута выплата. Тогда интересующее нас отношение можно определить следующим образом:
ЗАРПЛАТА (СОТРУДНИК, ВЫПЛАТА) П {Иванов, Петров} х Выплата
Здесь через {Иванов, Петров} обозначено требуемое подмножество домена атрибута сотрудник.
На естественном языке запрос на получение указанного множества кортежей выглядит очень просто: "выбрать кортежи, в которых Значение атрибута СОТРУДНИК равно "Иванов" ИЛИ "Петров”. Эквивалентный запрос можно сформулировать и так: "выбрать кортежи, в которых значение атрибута сотрудник равно "Иванов", и те кортежи, в которых значение атрибута сотрудник равно "Петров". Обратите внимание на использование союзов "или" и "и" в этих формулировках запросов. Если в первой формулировке "или" заменить на "и", то искомое множество кортежей будет заведомо пустым, поскольку атрибут сотрудник в одном и том же кортеже нс может иметь два и более различных значения. Вторая формулировка допускает замену "и" на "или", поскольку в ней речь идет о различных значениях атрибута в различных кортежах. Применяя в обычной речи союзы "и" и "или", всегда следует отдавать себе отчет в том, что имеется в виду — объединение или пересечение соответствующих множеств.
Примечание
На языке SQL данный запрос формулируется следующим образом: SELECT * FROM ЗАРПЛАТА WHERE СОТРУДНИК-’Иванов' OR СОТРУДНИК— ’Петров1;
По-русски это выглядит так: «выбрать кортежи (записи) из таблицы зарплата, в которых атрибут (столбец) сотрудник имеет значение ’Иванов’ ИЛИ ’Петров’».
Проекция
Операция проекции отношения заключается в удалении из него указанных атрибутов. Пусть дано некоторое отношение T^C^l, А2,..., Ап). Обозначим через А множество {Л1, А2...., Ап^ всех атрибутов отношения, а через X — некоторое его подмножество (т. е. ХсА). Тогда операция проекции отношения /?(А) на множество атрибутов X приводит к отношению /?[Х], кортежи которого получаются из кортежей исходного отношения путем удаления значений тех атрибутов, которые не принадлежат X. Для любого кортежа г из отношения /?(А) его проекция z[X] на множество атрибутов X определяется аналогично: в этом кортеже следует оставить значения только атрибутов из X. Обратите внимание, что квадратные скобки здесь указывают лишь на то, что рассматривается проекция отношения или кортежа на заключенные в эти скобки атрибуты.
На рис. 1.2 в качестве примера показано некоторое отношение /?(Л1, Л2, АЗ) и его проекция /?[Л1, АЗ] на множество атрибутов {Л1, лз}.
А1А2АЗаXmаУпbZксXпсXРА1А2АЗаXmаУпbZксXпсXРR(A1 ,А2ДЗ): таблицаR(A1 ,А2ДЗ): таблицаАТАЗаmапbJkспсРАТАЗаmапbJkспсРПроекция ЩА1Л2ЛЗ) на [А1 ,АЗ)Проекция ЩА1Л2ЛЗ) на [А1 ,АЗ)
А1
А2
АЗ
а
X
m
а
У
п
b
Z
к
с
X
п
с
X
Р
А1
А2
АЗ
а
X
m
а
У
п
b
Z
к
с
X
п
с
X
Р
R(A1 ,А2ДЗ): таблица
R(A1 ,А2ДЗ): таблица
АТ
АЗ
а
m
а
п
b
Jk
с
п
с
Р
АТ
АЗ
а
m
а
п
b
Jk
с
п
с
Р
Проекция ЩА1Л2ЛЗ) на [А1 ,АЗ)
Проекция ЩА1Л2ЛЗ) на [А1 ,АЗ)
Рис. 1.2. Пример проекции отношения
Примечание ^|
На языке SQL проекция отношения (таблицы) на заданное множество атрибутов (столбцов) производится с помощью оператора:
SELECT список_столбцов FROM имя_таблицы;
На практике часто операция проекции используется совместно с операцией селекции (выборки) кортежей. В этом случае применяется следующее выражение на языке SQL:
SELECT список_столбцов FROM имя_таблицы WHERE условие_выборки;
Естественное соединение
Предположим, имеются два отношения, содержащие кроме прочих и одинаковые атрибуты. Рассмотрим, например, две таблицы /?1 (преподаватель, дисциплина) и R2 (преподаватель, кафедра).
□ Отношение 7?1 содержит сведения об именах преподавателей и об учебных дисциплинах, занятия по которым они проводят. Вообще говоря, один и тот же преподаватель может заниматься несколькими дисциплинами, а по одной и той же дисциплине могут проводить занятия разные преподаватели.
□ Отношение KL содержит сведения о приписке преподавателей к кафедрам. Предполагается, что каждый преподаватель может быть приписан только к одной кафедре.
Естественно, может возникнуть задача сведения этих двух отношений в одно (соединить две таблицы в одну) — R3 (преподаватель, дисциплина, кафедра). На рис. 1.3 показаны примеры отношений Я1 и Я2, а также результат их соединения R3. Важным обстоятельством, позволившим выполнить соединение двух отношений, является наличие у них общего атрибута преподаватель. Обратите внимание, что проекция отношения R3 на атрибуты {преподаватель, дисциплина} в точности равна отношению /?1, а проекция на атрибуты {преподаватель, кафедра} — отношению R2. Это означает, что при соединении отношений /?1 и R2 в отношение R3 не было привнесено ничего нового и ничего не было потеряно. Другими словами, отношение R3 в точности представляет информацию, содержащуюся в отношениях /?1 и R2.
Рассмотренная операция соединения отношений называется операцией естественного соединения. Теперь уточним се определение.
Пусть даны два отношения 7?1(А1) и /?2(А2), где Al, А2 — множества атрибутов (а не одиночные атрибуты). Естественным соединением этих отношений /?1(А1) * 2?2(А2) называется отношение /?3(А1 U А2), содержащее те и только тс кортежи z, ДЛЯ которых выполняются одновременно следующие два условия: □ z[Al] е/?1(А1);
□ z[A2] е R2(А2) .
R2: таблицаR2: таблицаПРЕПОДАВАТЕЛЬДИСЦИПЛИНАИвановМатематический анализИвановГеометрияИванов^МихайловТеория вероятностей Органическая химияПРЕПОДАВАТЕЛЬДИСЦИПЛИНАИвановМатематический анализИвановГеометрияИванов^МихайловТеория вероятностей Органическая химияR1 : таблицаR1 : таблицаМихайлов J Неорганическая химияМихайлов J Неорганическая химияПРЕПОДАВАТЕЛЬ КАФЕДРАПРЕПОДАВАТЕЛЬ КАФЕДРАСергеевj Квантовая механика
R2: таблица
R2: таблица
ПРЕПОДАВАТЕЛЬ
ДИСЦИПЛИНА
Иванов
Математический анализ
Иванов
Геометрия
Иванов^
Михайлов
Теория вероятностей Органическая химия
ПРЕПОДАВАТЕЛЬ
ДИСЦИПЛИНА
Иванов
Математический анализ
Иванов
Геометрия
Иванов^
Михайлов
Теория вероятностей Органическая химия
R1 : таблица
R1 : таблица
Михайлов J Неорганическая химия
Михайлов J Неорганическая химия
ПРЕПОДАВАТЕЛЬ КАФЕДРА
ПРЕПОДАВАТЕЛЬ КАФЕДРА
СергеевАтомная физика
СергеевФизика твердого тела
СидоровАтомная физика
СидоровJ Квановая механика
СидоровJ Термодинамика _
Петров: Математический анализ
ПетровМатематическаялогика
ПетровТеория множеств
ФедоровТопология
Иванов101Михайлов103Петров101Сергеев102Сидоров102Федоров101Иванов101Михайлов103Петров101Сергеев102Сидоров102Федоров101Федоров Математическая логика
Иванов101
Михайлов103
Петров101
Сергеев102
Сидоров102
Федоров101
Иванов101
Михайлов103
Петров101
Сергеев102
Сидоров102
Федоров101
R3=R1*R2 : запрос на выборку
“преподаватель
ДИСЦИПЛИНА
КАФЕДРА
Иванов
Геометрия
101
Иванов .
Математический анализ
101
Иванов
Теория вероятностей
101
Михайлов ~
Неорганическая химия
103
Михайлов
Органическая химия
103
Петров
Теория множеств
101
Петров
Математическая логика
101
Петров
Математический анализ
101
Сергеев
Квантовая механика
102
Сергее^
Атомная физика
102
Сергеев
Физика твердого тела
102
Сидоров
Атомная физика
102
Сидоров
Квановая механика
102
Сидоров
Термодинамика
102
Федоров
ТополОГИ я_
101
Федоров
Математическая логика
101
Рис. 1.3. Пример соединения отношений
Напомню, квадратные скобки указывают на то, что рассматривается проекция на заключенные в них атрибуты. Операция естественного соединения здесь обозначена через символ (*).
Операция естественного соединения двух отношений может быть выражена через операции декартового произведения, проекции и пересечения. Рассмотрим три случая.
□ Множества атрибутов отношений 7?1(А1) и 7?2(А2) нс равны, но пересекаются (т. е. Al ^ А2 , А1П А2 ^ 0 ). В данном случае:
R4 А1) * 7?2(А2) = (Я1(А1) х Л2( А2 - А1)) А
П(Я1( Al - А2) х 7?2( А2)).
□ Множества атрибутов отношений нс пересекаются (т. с. А1ПА2 = 0). В данном случае естественное соединение равно дскартовому произведению исходных отношений:
/?1(А1) * Я2(А2) = Я1(А1) х /?2(А2).
□ Множества атрибутов отношений равны (т. с. А1-А2). Тогда естественное соединение равно пересечению исходных отношений:
7?1(А1) * /?2(А2) = 7?1(А1) П /?2(А2).
На рис. 1.4 показаны два исходных отношения, их декартово произведение и естественное соединение. В таблице для декартового произведения отмечены галочками тс кортежи, из которых получены кортежи естественного соединения.
Примечание
Язык SQL предоставляет специальные средства для различных типов соединения таблиц. Естественное соединение отношений RA (преподаватель, дисциплина) и R2 (преподаватель, кафедра), показанных на рис. 1.3, можно выполнить с помощью следующего SQL-выражения:
SELECT RI.*, R2.КАФЕДРА FROM RI, R2 WHERE R1.ПРЕПОДАВАТЕЛЬ= R2.ПРЕПОДАВАТЕЛЬ
По-русски это выглядит так: "выбрать все столбцы таблицы ri и столбец кафедра таблицы R2 из декартового произведения ri и R2 при условии, что преподаватели из этих таблиц одинаковы".
Здесь подразумевается именно декартово произведение, т. к. после ключевого слова from указаны две таблицы. В SQL также можно использовать оператор natural join для явного указания операции естественного соединения.
R1 : таблица R2 : таблица
А1А2а1Ыа1Ь2а2ЫА1А2а1Ыа1Ь2а2ЫА2АЗЫ Ы Ь2с1с2 с1А2АЗЫ Ы Ь2с1с2 с1
А1
А2
а1
Ы
а1
Ь2
а2
Ы
А1
А2
а1
Ы
а1
Ь2
а2
Ы
А2
АЗ
Ы Ы Ь2
с1
с2 с1
А2
АЗ
Ы Ы Ь2
с1
с2 с1
^^^й^-у- >:• • "йуйухй-<^$&£&Ш&£$
R1xR2 : запрос на выборку
А1
R1.A2
раяаяагз^^
R2.A2
АЗ
V
а1
ы
Ы
с1
а1
Ь2
Ы
с1
V
а2
Ы
Ы
с1
V
а1
Ы
Ы
с2
а1
Ь2
ы
с2
V
а2
Ы
ы
с2
а1
ы
Ь2
с1
V
а1
Ь2
Ь2
с1
а2
Ы
Ь2
с1
R3=R1*R2 : запрос на выборку
А1
А2
АЗ
а2
Ы
с1
а1
Ы
с1
а2
Ы
с2
а1
Ы
с2
al
Ь2
с1
Рис. 1.4. Естественное соединение отношений
При работе с базами данных кроме естественного соединения используются и другие способы комбинации таблиц. Однако в реляционной теории понятие естественного соединения играет исключительно важную роль, поскольку через него определяется понятие корректной декомпозиции одной таблицы на несколько других. Об этом будет рассказано в следующем разделе.
Декомпозиция отношений
Таблица базы данных, представляющая некоторое отношение, может иметь очень большие размеры по количеству записей (строк) и столбцов, а также по общему объему содержащихся в них данных. Эффективность работы с таблицами по мере роста
их объема уменьшается. Это обстоятельство вынуждает искать способы декомпозиции одной большой таблицы на несколько таблиц меньших размеров.
Так, в рассмотренном в предыдущем разделе отношении R3 (преподаватель, дисциплина, кафедра) (см. рис. 1.3) все атрибуты имеют повторяющиеся значения, а атрибут преподаватель однозначно определяет атрибут кафедра, поскольку каждый преподаватель приписан только к одной кафедре. Наличие такой зависимости между атрибутами преподаватель и дисциплина наталкивает на мысль, что декомпозиция возможна.
Отношения AI (преподаватель, дисциплина) и К2 (преподаватель, кафедра) можно рассматривать как результат декомпозиции отношения КЗ, т. с. как операцию, обратную естественному соединению. Обратите внимание, что общий объем данных в отношениях Кк и R2 меньше объема данных в отношении КЗ. Кроме того, в отношении К2 атрибут преподаватель не имеет повторяющихся значений.
Очевидно, что отношения, получающиеся в результате декомпозиции исходного отношения, являются проекциями последнего на некоторые подмножества атрибутов. Напомню, что проекция отношения на заданные атрибуты получается из этого отношения путем удаления из него всех атрибутов, кроме заданных. Заметим также, что для декомпозиции отношения на две проекции необходимо, чтобы оно было определено не менее чем для трех атрибутов. Декомпозиция может быть корректной или некорректной. Другими словами, она может быть обратимой или необратимой. Обратимость декомпозиции означает, что она является эквивалентным преобразованием исходной информации (форма изменяется, а содержание нет). Необратимость декомпозиции означает неэквивалентность преобразования: в содержание либо что-то вносится, либо что-то теряется. Далее мы дадим определение корректной декомпозиции и покажем пример некорректной декомпозиции.
Корректная декомпозиция
Декомпозиция отношения на некоторые свои проекции называется корректной, если исходное отношение можно восстановить по этим проекциям с помощью операции естественного соеди-
нения. Точнее, отношение 7?(А) корректно декомпозируется на свои проекции /?[Х] и /?[Y] (Хс А, Y с А), если выполняется равенство /?(А) = /?[Х] * /?[Y].
Например, декомпозиция отношения КЗ (преподаватель, дисциплина, кафедра) (см. рис. 1.3) на свои проекции:
КЗ [преподаватель, дисциплина] = /?1 (преподаватель, дисциплина)
и
КЗ [преподаватель, кафедра] = К2 (преподаватель, кафедра) является корректной.
Это утверждение просто следует из того, что отношение КЗ было получено из отношений 7?1 и К2 путем их естественного соединения (см. разд. 1.2.3).
Пример некорректной декомпозиции
На рис. 1.5 показано некоторое отношение 2?(Л1, Л 2, АЗ), три его проекции /?[Я1, А2], К[А2, АЗ] и /?[/11, АЗ], а также естественное
Проекция на А1 А2А1А210011о!оПроекция на А1 А2А1А210011о!оА2 ’АЗ001001|А2 ’АЗ001001|j®^^^|gSiMfi« '■ V"Проекция на А2 .АЗj®^^^|gSiMfi« '■ V"Проекция на А2 .АЗА1АЗ100001А1АЗ100001Проекция на А1 ,АЗПроекция на А1 ,АЗ
Проекция на А1 А2
А1А2
10
011
о!о
Проекция на А1 А2
А1А2
10
011
о!о
А2 ’
АЗ
0
0
1
0
0
1|
А2 ’
АЗ
0
0
1
0
0
1|
j®^^^|gSiMfi« '■ V"
Проекция на А2 .АЗ
j®^^^|gSiMfi« '■ V"
Проекция на А2 .АЗ
А1
АЗ
1
0
0
0
0
1
А1
АЗ
1
0
0
0
0
1
Проекция на А1 ,АЗ
Проекция на А1 ,АЗ
Й(А1 ,А2 АЗ): таблица
А1
А2
АЗ
1
0
0
0
1
0
0
0
1
Естественное соединение трех проекций
А1
А2
АЗ
1
0
0
0
0
1 0
0 '
0
0
1
0
Рис. 1.5. Пример некорректной декомпозиции отношения
соединение этих трех проекций A^l, А2]*В[Л2, АЗ]* ЯЦ1, АЗ]. Нетрудно заметить, что естественное соединение содержит кор- I еж (0,0,0), которого нет в исходном отношении /?(у41, А2. ЛЗ). Таким образом, данная декомпозиция некорректна. Можно показать, что это отношение вообще нельзя корректно декомпозировать, т. к. в естественном соединении всегда будет появляться кортеж (0, 0, 0) .
Далее, говоря о декомпозиции отношения, мы будем всегда иметь в виду корректную декомпозицию.
Зависимости между атрибутами
Итак, отношение не всегда можно декомпозировать на две своих проекции. Критерием возможности декомпозиции является на- личие в отношении некоторых зависимостей между его атрибу- шми. Это общий критерий. Если в чем-либо обнаруживаются какие-нибудь зависимости, то существует принципиальная возможность представить это нечто в более компактном виде.
Чтобы отношение можно было декомпозировать на две своих проекции, достаточно существования в нем так называемых функциональных зависимостей. Необходимым и достаточным условием декомпозиции отношения является наличие в нем многозначных зависимостей. Рассмотрим эти зависимости по порядку.
Функциональные зависимости
Функциональные зависимости просты для понимания и обычно югко обнаруживаются в отношении. Между атрибутами А и В существует функциональная зависимость, если любое значение .1 грибута А однозначно определяет значение атрибута В.
Рассмотрим несколько примеров отношений, показанных на рис. 1.3.
1 В отношении R2 (преподаватель, кафедра) между атрибутами преподаватель и кафедра имеется функциональная зависимость, поскольку каждый преподаватель может быть приписан лишь к одной кафедре (предполагается, что совместительство не допускается). Другими словами, по значению
атрибута преподаватель можно однозначно определить, на какой кафедре он работает. Однако между атрибутами кафедра и преподаватель в отношении R1 нет функциональной зависимости: на одной и той же кафедре могут работать несколько преподавателей.
В отношении А1 (преподаватель, дисциплина) нет ни одной функциональной зависимости: один и тот же преподаватель может заниматься несколькими дисциплинами, а одну и ту же дисциплину могут вести несколько преподавателей.
В отношении R3 (преподаватель, дисциплина, кафедра) существуют две функциональных зависимости:
между атрибутами преподаватель и кафедра;
между атрибутами дисциплина и кафедра.
Вообще говоря, функциональная зависимость может иметь место не только между отдельными атрибутами, но и между подмножествами атрибутов отношения. Для этого общего случая дадим определение функциональной зависимости.
Пусть дано некоторое отношение 7?(А) с атрибутами из множества А, т. е. А — это множество атрибутов, а не отдельный атрибут. Обозначим через X и Y некоторые подмножества атрибутов (ХсА, YcA). В отношении 7?(А) выполняется функциональная зависимость между атрибутами X и Y, обозначаемая как X->Y, если для любых кортежей <1 и <2 этого отношения из равенства их проекций на множество атрибутов X следует равенство их проекций на множество атрибутов Y. Иначе говоря, функциональная зависимость X->Y выполняется, если справедливо следующее высказывание: для любых zl и й из равенства zl[X] = z2[X] следует равенство zl[Y] = z2[Y].
Если выполняется функциональная зависимость X->Y, то говорят, что атрибуты X функционально (однозначно) определяют атрибуты Y.
Например, в отношении R3 (преподаватель, дисциплина, кафедра), показанном на рис. 1.3, имеются следующие функциональные зависимости:
□ {преподаватель} -> {кафедра} — преподаватель функционально определяет кафедру;
□ {дисциплина} -> {кафедра} — дисциплина функционально определяет кафедру;
□ {преподаватель, дисциплина} -> {кафедра} — преподаватель и дисциплина функционально определяют кафедру.
Рассмотрим еще один пример. Предположим, что отношение продажи (товар, количество, цена, стоимость) содержит сведения о проданных товарах. Стоимость товара однозначно определяется его количеством и ценой, поскольку существует простая формула: стоимость = количество х цена. Поэтому в данном отношении имеется функциональная зависимость {количество, цена} -> {стоимость}.
Если в отношении 7?(А) выполняется функциональная зависимость X->Y (X с А , Y с А), то это отношение декомпозируется на две своих проекции 2?[Х U Y] и ^[XU(A-Y)]. Иначе говоря, справедливо следующее равенство:
Я(А) = 7?[Х U Y] * Л[Х U (А - Y)].
Здесь через (*) обозначена операция естественного соединения. Таким образом, исходное отношение /?(А) можно восстановить с помощью операции естественного соединения двух своих проекций - /?[Х U Y] и 7?[Х U (А - Y)].
Например, в отношении КЗ (преподаватель, дисциплина, кафедра), показанном на рис. 1.3, имеется функциональная зависимость {преподаватель} -> {кафедра}. Следовательно, это отношение можно декомпозировать на две проекции:
КЗ [преподаватель, кафедра];
КЗ [преподаватель, дисциплина].
В данном примере можно принять такие обозначения:
А ={преподаватель, дисциплина, кафедра};
X ={преподаватель};
Y = {кафедра};
XUY = {преподаватель, кафедра};
А - Y = {преподаватель, дисциплина};
X U (А - Y) = {преподаватель} U {преподаватель, дисциплина} = {преподаватель, дисциплина}.
Отношение продажи (товар, количество, цена, стоимость), имеющее функциональную зависимость {количество, цена}-> {стоимость}, можно декомпозировать на такие проекции:
ПРОДАЖИ [количество, цена, стоимость];
ПРОДАЖИ [товар, количество, цена].
В любом отношении существует хотя бы одна функциональная зависимость. Например, для произвольного отношения /?(А) всегда выполняется зависимость А->А, поскольку по определению в любом отношении не может быть одинаковых кортежей и, следовательно, любой кортеж однозначно определяет сам себя. Такая функциональная зависимость является тривиальной. Точнее, функциональная зависимость X->Y называется тривиальной, если YcX.
Наличие в отношении нетривиальной функциональной зависимости является достаточным, но не необходимым условием корректной декомпозиции этого отношения на две проекции. Это означает, что если функциональная зависимость есть, то декомпозиция возможна. Однако корректная декомпозиция может быть выполнена и для некоторых отношений, в которых нет ни одной функциональной зависимости. Например, на рис. 1.4 было показано отношение R3 = Al * R2, в котором нет ни одной нетривиальной функциональной зависимости. Однако оно может быть корректно декомпозировано на две своих проекции RY и R2 (см. рис. 1.4).
Приведем некоторые свойства функциональных зависимостей.
Пусть Y = {v41,..., АЦ — некоторое подмножество атрибутов отношения. Если в этом отношении выполняется функциональная зависимость X->Y, то выполняется и множество функциональных зависимостей Х->{у4/} , где i = 1,..., к. Другими словами, коль скоро выполняется функциональная зависимость между множествами атрибутов X и Y, то выполняются и зависимости между X и одиночными атрибутами из Y.
Обратное утверждение тоже верно: если в отношении выполняются функциональные зависимости Х->{Л/} (i = 1,..., к\ то выполняется и функциональная зависимость Х->¥, где ¥ = {А ...,А}.
П В отношении /?(А) функциональная зависимость X->Y выполняется тогда и только тогда, когда она выполняется в проекции 7?[Z], где X (J Y с Z.
Многозначные зависимости
Обобщением понятия функциональной (однозначной) зависимости является понятие многозначной зависимости. Наличие в отношении многозначной зависимости является необходимым и достаточным условием его декомпозиции на две проекции. Напомню, что выполнение функциональной зависимости обеспечивает лишь достаточное условие декомпозиции. Вот почему некоторые отношения без функциональных зависимостей все- шки могут быть декомпозированы.
Многозначная зависимость имеет смысл только для отношений с тремя и более атрибутами. Проще всего понятие многозначной зависимости объяснить для отношения с тремя атрибутами. Пусть дано некоторое отношение /^(^l, Л 2, ЛЗ), для которого выполняются следующие условия:
□ атрибут Л1 не определяет однозначно атрибут Л2, т. е. нет функциональной зависимости {Л1}->{Л2};
П атрибуты Л2 и ЛЗ нс зависят друг от друга, т. е. в отношении /?(Л1, Л2, ЛЗ) любое значение Л2 может сочетаться в кортежах с любым значением ЛЗ.
Гогда в отношении 7?(Л1, Л2, ЛЗ) выполняется многозначная зависимость между атрибутами Л1 и Л2, которая обозначается как {Л1}-»{Л2}. При этом говорят, что атрибут Л1 многозначно определяет атрибут Л2.
Это частное определение многозначной зависимости, возможно, и не очень понятное. Поэтому поясним его на примере.
Пусть дано отношение R (преподаватель, дисциплина, увлечение), которое содержит сведения о преподавателях, дисциплинах, занятия по которым они проводят, и личных увлечениях (хобби). Предполагается, что один преподаватель может проводить занятия по нескольким дисциплинам, т. е. атрибут преподаватель не определяет однозначно атрибут дисциплина. Далее, мы считаем, что нет никакой связи между дисциплинами и увлечениями преподавателей. Иначе говоря, если известно, что преподаватель читает студентам, например, математический анализ, то из этого нельзя однозначно определить, чем конкретно он увлекается. И наоборот, если мы знаем о конкретном увлечении преподавателя классической музыкой, то не можем однозначно сказать, какой именно дисциплиной он занимается. Все это означает, что в рассматриваемом отношении значения атрибутов дисциплина и увлечение могут сочетаться произвольным образом. Итак, атрибуты дисциплина и увлечение не зависят друг от друга, а атрибут преподаватель не однозначно определяет атрибут дисциплина. Пример рассмотренного отношения показан на рис. 1.6.
R6 таблица " """' " "
ПРЕПОДАВАТЕЛЬ']
‘' vV. .^,ЙУ%2М!й<<£ФЯ22Й&И,У|й>ЬИМб')№!£й210ййд>5ййй,ТО1ФМ1?К^5й2СйХг>>Й1ИС^^
ДИСЦИПЛИНА 1 УВЛЕЧЕНИЕ
Иванов
Математический анализ
Классическая музыка -
Иванов
Математический анализ
Футбол
Иванов
Геометрия
Классическая музыка
Иванов
Геометрия
Футбол
Михайлов
Органическая химия
Поп-музыка
Михайлов
Неорганическая химия
Поп-музыка
Рис. 1.6. Отношение с многозначной зависимостью
ПРЕПОДАВАТЕЛЬ -» ДИСЦИПЛИНА
Поскольку в отношении R постулируется независимость атрибутов дисциплина и увлечение, то в этом отношении каждое сочетание значений атрибутов преподаватель и дисциплина должно сочетаться с каждым значением атрибута увлечение. В примере отношения, показанном на рис. 1.6, преподаватель Иванов проводит занятия по двум дисциплинам и имеет два хобби. Поэтому ему в таблице посвящено четыре записи (кортежа). Михайлов занят двумя дисциплинами и имеет одно увлечение, поэтому
информация о нем представлена в отношении двумя записями. В данном отношении имеется многозначная зависимость преподаватель—» дисциплина или, другими словами, атрибут преподаватель многозначно определяет атрибут дисциплина. Заметим, что мы бы не говорили о многозначной зависимости преподаватель—» дисциплина, если бы между атрибутами дисциплина и увлечение существовала какая-нибудь зависимость.
Очевидно, что с точки зрения хранения информации такое представление отношения в виде одной таблицы избыточно. И действительно, его можно декомпозировать так, как показано на рис. 1.7.
X<<<<<<<v>W«<»0««<«0»X*WGWdOC4«y00tiXiWZi^ПРЕПОДАВАТЕЛЬ | ДИСЦИПЛИНАИвановМатематический анализИвановГеометрияМихайлов? Органическая химияМихайлов; Неорганическая химияX<<<<<<<v>W«<»0««<«0»X*WGWdOC4«y00tiXiWZi^ПРЕПОДАВАТЕЛЬ | ДИСЦИПЛИНАИвановМатематический анализИвановГеометрияМихайлов? Органическая химияМихайлов; Неорганическая химияПроекция на ПРЕПОДАВАТЕЛЬ, ДИСЦИПЛИНАПроекция на ПРЕПОДАВАТЕЛЬ, ДИСЦИПЛИНАCWOOWOO<*X<4v>X4«<^ПреподавательУВЛЕЧЕНИЕ :ИвановКлассическая музыкаИвановФутболМихайловПоп-музыкаCWOOWOO<*X<4v>X4«<^ПреподавательУВЛЕЧЕНИЕ :ИвановКлассическая музыкаИвановФутболМихайловПоп-музыкаПроекция на ПРЕПОДАВАТЕЛЬ, УВЛЕЧЕНИЕПроекция на ПРЕПОДАВАТЕЛЬ, УВЛЕЧЕНИЕ
X<<<<<<<v>W«<»0««<«0»X*WGWdOC4«y00tiXiWZi^
ПРЕПОДАВАТЕЛЬ | ДИСЦИПЛИНА
Иванов
Математический анализ
Иванов
Геометрия
Михайлов
? Органическая химия
Михайлов
; Неорганическая химия
X<<<<<<<v>W«<»0««<«0»X*WGWdOC4«y00tiXiWZi^
ПРЕПОДАВАТЕЛЬ | ДИСЦИПЛИНА
Иванов
Математический анализ
Иванов
Геометрия
Михайлов
? Органическая химия
Михайлов
; Неорганическая химия
Проекция на ПРЕПОДАВАТЕЛЬ, ДИСЦИПЛИНА
Проекция на ПРЕПОДАВАТЕЛЬ, ДИСЦИПЛИНА
CWOOWOO<*X<4v>X4«<^
Преподаватель
УВЛЕЧЕНИЕ :
Иванов
Классическая музыка
Иванов
Футбол
Михайлов
Поп-музыка
CWOOWOO<*X<4v>X4«<^
Преподаватель
УВЛЕЧЕНИЕ :
Иванов
Классическая музыка
Иванов
Футбол
Михайлов
Поп-музыка
Проекция на ПРЕПОДАВАТЕЛЬ, УВЛЕЧЕНИЕ
Проекция на ПРЕПОДАВАТЕЛЬ, УВЛЕЧЕНИЕ
В: таблица
ПРЕПОДАВАТЕЛЬ
ДИСЦИПЛИНА
УВЛЕЧЕНИЕ
Иванов
Математический анализ
Классическая музыкаi
Иванов
Математический анализ
Футбол
Иванов
Геометрия
Классическая музыка
Иванов
Геометрия
Футбол
Михайлов
Органическая химия
Поп-музыка
Михайлов
Неорганическая химия
Поп-музыка
Рис. 1.7. Декомпозиция отношения, содержащего многозначнуюзависимость преподаватель -» ДИСЦИПЛИНА
Поскольку выполняется многозначная зависимость ПРЕПОДАВАТЕЛЬ—» ДИСЦИПЛИНА, атрибуты ДИСЦИПЛИНА И УВЛЕЧЕНИЕ не зависят друг от друга. Поэтому имеется возможность представить СВЯЗИ ПРЕПОДАВАТЕЛЬ—ДИСЦИПЛИНА И ПРЕПОДАВАТЕЛЬ—УВЛЕЧЕНИЕ в виде отдельных отношений, причем так, чтобы исходное отношение R (преподаватель, дисциплина, увлечение) полностью восстанавливалось с помощью операции естественного соединения.
()бобщим вышесказанное. Отношение А(т41, Л2, ЛЗ) , в котором выполняется многозначная зависимость {у41}-»{^42} , можно
декомпозировать на две проекции: А[Л 1Э Л2] и 7?[^1, ЛЗ]. Это означает, что выполняется следующее равенство: ДЛ1, А2, АЗ) = 7?[Л1, А2] * R\A\, ЛЗ].
Следующий пример отличается от рассмотренного ранее лишь уровнем абстракции. Пусть дано некоторое отношение /?(Л1, А2. АЗ), которое представлено в виде таблицы, показанной на рис. 1.8. Это представление отношения. Вся информация о зависимостях между его атрибутами не постулируется какими- либо предложениями, а заключена в самом представлении отношения. Иначе говоря, зависимости, если они существуют, могут быть выявлены из анализа самого содержимого отношения, хотя это и не всегда простая задача.
R(Af 'ДОЗ): таблица
‘ А1
А2 | АЗ
а2
Ы
с1
а2
ы
с2
а1
Ь2
с2
а1
Ы
с1
а1
Ы
с2
а1
Ь2
с1
Рис. 1.8. Отношение с многозначной зависимостью А1 -»Д2
В данном отношении выполняется многозначная зависимость ^1-»Л2. Поэтому его можно декомпозировать на две проекции — /?[Л1, А2] и ДЛ1, АЗ], как показано на рис. 1.9.
В рассмотренном отношении (см. рис. 1.8) кроме многозначной зависимости А1-» А2 выполняется еще и многозначная зависимость Л1—»ЛЗ. Следовательно, исходное отношение можно декомпозировать на проекции 2?[Л1, АЗ] и ??[ Л1, А2], т. е. на те же самые, что позволяет получить зависимость Л1 —»Л2.
В общем случае многозначная зависимость определяется нс только между одноэлементными множествами атрибутов.
Ранее уже отмечалось, что наличие многозначной зависимости в отношении является необходимым и достаточным условием декомпозиции отношения на две его проекции. Если в отноше-
нии /?(А) выполняется многозначная зависимость X-»Y, то это отношение можно декомпозировать на две проекции — /?[X,Y] и /?[X,Z]. Это утверждение о том, что многозначная зависимость необходима для декомпозиции.
а1Ыа1Ь2а2Ыа1Ыа1Ь2а2ЫПроекция на А1, АХПроекция на А1, АХHWttXXWZZ^XtiW^^А1 ' А2HWttXXWZZ^XtiW^^А1 ' А2itow^66-55w^А1 :•йх^^8^йа4$й»>^^АЗ ?а1с1а1с2а2с1а2с2itow^66-55w^А1 :•йх^^8^йа4$й»>^^АЗ ?а1с1а1с2а2с1а2с2Проекция на АТ, АЗПроекция на АТ, АЗ
а1
Ы
а1
Ь2
а2
Ы
а1
Ы
а1
Ь2
а2
Ы
Проекция на А1, АХ
Проекция на А1, АХ
HWttXXWZZ^XtiW^^
А1 ' А2
HWttXXWZZ^XtiW^^
А1 ' А2
itow^66-55w^
А1 :
•йх^^8^йа4$й»>^^
АЗ ?
а1
с1
а1
с2
а2
с1
а2
с2
itow^66-55w^
А1 :
•йх^^8^йа4$й»>^^
АЗ ?
а1
с1
а1
с2
а2
с1
а2
с2
Проекция на АТ, АЗ
Проекция на АТ, АЗ
R(A1.A2.A3): таблица
А1
. X ^<r/Z.ywiy24WiM
А2
•w>:<««w23^5ctixrxw
АЗ :
а2
Ы
с1
а2
Ы
с2
а1
Ь2
с2
а1
Ы
с1
а1
Ы
с2
а1
Ь2
с1
Рис. 1.9. Декомпозиция отношения Я(А1, А2, АЗ)с многозначной зависимостью А1 -»А2
То, что наличие многозначной зависимости является еще и достаточным условием декомпозиции, вытекает из следующего. Пусть отношение R(A) декомпозируется на две проекции — /?[Х] и A[Y] (это означает, что Л(А) = Л[Х] */?[¥]). Тогда в отношении R(A) выполняются две многозначные зависимо
сти: XAY-»X-Y и XAY-»Y-X. Иначе говоря, если
нам удалось декомпозировать отношение на две проекции, то это означает, что в отношении выполняются, по крайней мере, две многозначные зависимости.
На рис. 1.10 представлено некоторое отношение /?(^1, А2, ЛЗ) и две его проекции — /?[^41, А2] и R\A2, ^3]. Нетрудно проверить, что выполняется равенство А(^П, ^12, ЛЗ) = 7?[^41, Л2] * R[A2, АЗ].
Следовательно, отношение декомпозируется на эти проекции.
Но тогда в отношении 7?(Л, А2, АЗ) выполняются две многозначные зависимости:
Л2-»Л, поскольку {Л, А2^ П ^А2, ЛЗ} = {Л2} и {Л, А2)-^А2, ЛЗ} = {Л};
А2-» АЗ , поскольку ^А2, АЗ^ - {Л, А2\ = {ЛЗ}.
С учетом первой зависимости исходное отношение можно декомпозировать на проекции R\A2, Al] и R[A2, АЗ], показанные на рис. 1.10. С учетом второй зависимости это отношение можно декомпозировать на такие же проекции.
а1 |Ыа1 |Ыalj_b2а2Tbialj_b2а2TbiПроекция на А1, А2Проекция на А1, А2
а1 |Ы
а1 |Ы
alj_b2
а2Tbi
alj_b2
а2Tbi
Проекция на А1, А2
Проекция на А1, А2
R(A1 ,А2ЛЗ): таблица
A2
а1
Ы
d
а1
Ы
c2
а1
b2
d
a2
Ы
d
a2
,ы
c2
Рис. 1.10. Из данной декомпозиции отношения следует наличиев нем зависимостей Д2-»Д1 и Д2-»ДЗ
Нетрудно заметить, что в определении многозначной зависимости множества атрибутов Y и Z играют симметричные роли: если что-либо справедливо для Y, то это же справедливо и для Z, и наоборот. Поэтому можно сформулировать следующее утверждение: многозначная зависимость X-»Y выполняется в от
ношении /?(А) тогда и только тогда, когда в 7?(А) выполняется многозначная зависимость X-»Z.
Многозначные (а также и функциональные) зависимости сохраняются в проекциях отношения, которые не затрагивают их левой части. Другими словами, если в отношении 7?(А) выполня-
ется многозначная зависимость X-»Y, то в любой проекции /?[U], для которой Хс U, выполняется и многозначная зависимость X-»YDU. Для функциональных зависимостей верно и обратное утверждение: если в проекции выполняется функциональная зависимость, то эта же зависимость выполняется и во всем отношении. Для многозначных зависимостей это утверждение не верно: из выполнимости многозначной зависимости в некоторой проекции отношения еще не следует ее выполнимость во всем отношении.
1.3.4. Правила вывода зависимостей
Все утверждения, сформулированные для многозначных зависимостей, справедливы и для функциональных зависимостей. Поэтому далее мы будем рассматривать только многозначные зависимости.
Если в отношении было каким-либо образом замечено выполнение одних зависимостей, то другие могут быть выведены логически из тех, что обнаружены. В основе декомпозиции, как известно, находится та или иная зависимость. Таким образом, имея несколько зависимостей, мы имеем возможность выбора декомпозиции. Не всякая декомпозиция может оказаться удачной с точки зрения экономного представления данных. Не стоит также забывать и о том, что результаты декомпозиции (проекции исходного отношения) могут использоваться как исходный материал для дальнейшей декомпозиции: то, что хорошо на первом этапе декомпозиции, может плохо сказаться на всей многоэтапной декомпозиции. Иначе говоря, оптимизация декомпозиции в целом не означает, что каждый отдельный ее этап должен быть наилучшим.
Данный раздел посвящен выводам одних зависимостей из факта наличия других и адресуется тем, кто хочет либо глубже познать реляционную теорию, либо поупражняться в логике и алгебре множеств. Этот материал не из легких и может быть пропущен при первом чтении данной главы. Так или иначе, я привожу здесь правила вывода зависимостей.
В систему правил вывода многозначных зависимостей входят четыре основных правила. Прежде чем их перечислить, догово-
римся об обозначениях. Через А будем обозначать множество всех атрибутов отношения, а через X, Y и Z — подмножества атрибутов (ХсА, YcA, ZcA). Разумеется, каждое из них может содержать и только единственный элемент — какой- нибудь один атрибут отношения. Для обозначения многозначной зависимости используется, как и раньше, символ (-»). Однако все приведенные далее утверждения справедливы не только для многозначных зависимостей, но и для функциональных зависимостей.
Итак, для зависимостей между атрибутами отношения выполняются следующие правила вывода:
Правило дополнения. Если выполняется зависимость X-»Y, то выполняется и зависимость X-»Z для всех Z, таких, что YAZ = X и XUYUZ-A.
Правило рефлексивности. Если Хс Y, то X—» Y.
Правило пополнения. Если выполняется зависимость X-»Y и для некоторых подмножеств атрибутов V и W справедливо включение V с W, то выполняется зависимость X U W -» Y U V.
Правило транзитивности. Если выполняются две зависимости X-»Y и Y-»Z, то выполняется и зависимость X»Z-Y.
Из этих четырех основных правил можно логически вывести еще три дополнительных правила:
зависимости зависимостьзависимости зависимостьзависимости зависимостьзависимости зависимостьзависимости зависимостьзависимости зависимостьПравило объединения. Если выполняются две X-»Y и X-»Z, то выполняется и
зависимости зависимость
зависимости зависимость
зависимости зависимость
зависимости зависимость
зависимости зависимость
зависимости зависимость
X-»YUZ.
X-»Z, то выполняется иX-»Z, то выполняется иПравило разности. Если выполняются две
X-»Z, то выполняется и
X-»Z, то выполняется и
X-»Y-Z.
Правило пересечения. Если выполняются две
X-»Y и X-»Z, то выполняется и
X-»YAZ.
Дополнительные правила удобно использовать для сокращения длины вывода зависимостей.
В качестве примера, а также для упражнения в обращении с зависимостями рассмотрим вывод из четырех основных правил: правила разности и правила пересечения.
Пусть даны две зависимости X-»Y и X-»Z, тогда вывод правила разности состоит из следующих четырех шагов:
По правилу пополнения из X-»Z следует XUX-»XUZ,T. с. X-»XUZ.
По правилуПо правилупополнения изпополнения изследуетследуетПо правилуПо правилутранзитивности изтранзитивности изX-»XUZ иX-»XUZ и
По правилу
По правилу
пополнения из
пополнения из
следует
следует
По правилу
По правилу
транзитивности из
транзитивности из
X-»XUZ и
X-»XUZ и
XUZ-»YUZXUZ-»YUZследует Х-» (Y и Z) - (X и Z) . Однако
XUZ-»YUZ
XUZ-»YUZ
(Y U Z) - (X U Z) = Y - Z - X, поэтому выполняется зависимость X—» Y-Z-X.
Так как (Y - Z) А X с X, то по правилу пополнения из X-»Y-Z-X следует XUX-»(Y-Z-X)U(Y-Z)АХ. Но XUX=X, a (Y —Z-X)U(Y-Z)AX = Y-Z, поэтому справедлива зависимость X-»Y-Z.
Теперь выведем правило пересечения. Поскольку
Y A Z = Y - (Y - Z), то правило пересечения получается из пра
вила разности: из X-»Y и X-»Y-Z следует
X-»Y-Y-Z или X-»YAZ. Правило объединения можно
обосновать аналогичным образом.
3.5. Ключи
При синтезе и анализе отношений (таблиц базы данных) важную роль играет понятие ключа отношения (таблицы). Это понятие производное от понятия функциональной зависимости, которое было рассмотрено в предыдущем разделе. Можно считать, что ключ является аргументом (левой, частью) функциональной зависимости между множествами атрибутов отношения.
Ключ (key) — это множество из одного или нескольких атрибутов, которое однозначно определяет (идентифицирует) всю запись
3 Зак. 120
в отношении. Таким образом, множество атрибутов X является ключом отношения 7?(А), если в этом отношении есть функциональная зависимость Х-> А. И наоборот, если в отношении /?(А) имеется функциональная зависимость Х->А, то множество атрибутов X является ключом этого отношения. Очевидно, что главным признаком ключа отношения является уникальность (неповторяемость) его значений.
Из самого определения отношения следует, что в любом отношении всегда найдется ключ. По крайней мере, ключом может быть множество всех атрибутов А, т. к. в любом отношении имеется тривиальная функциональная зависимость А->А, поскольку каждая запись встречается в отношении всего один раз и, следовательно, однозначно идентифицирует сама себя. Однако при разработке таблиц базы данных обычно интересуются не тривиальными ключами, а состоящими из одного или небольшого количества атрибутов. Действительно, тривиальные зависимости не дают возможность получить декомпозицию отношения на две проекции.
Рассмотрим в качестве примера таблицу, представляющую отношение Студенты (Фамилия, Имя, Отчество, Учебная_группа). Какие атрибуты в этом отношении могут составить ключ? Предполагается, что каждый студент может находиться только в одной учебной группе. Казалось бы, что множество атрибутов {Фамилия, имя, Отчество} должно однозначно идентифицировать студента, а следовательно, и группу. Это было бы верно, если бы мы исключили возможность существования в одной группе полных тезок (однофамильцев с одинаковыми именами и отчествами). Хотя это и маловероятно, но все же возможно. Таким образом, множество атрибутов {фамилия, Имя, Отчество} НС ПОДХОДИТ на роль нетривиального ключа. В подобных ситуациях создают специальный атрибут (столбец), предназначенный играть роль ключа. В нашем примере это может быть ю студента (идентификатор студента). Значения этого атрибута могут быть какими угодно, но обязательно уникальными. В результате получается Таблица Студенты (lD—Студента, Фамилия, Имя, Отчество, УчебнаЯ—Группа). А если Ю_студента является КЛЮЧОМ, ТО в данном отношении имеется функциональная зависимость {Ю_студента}—> {10__студента, Фамилия, Имя, Отчество,
Учебная_группа}, Я ТЗКЖС зависимость {lD—Студента} — > (Фамилия, Имя, Отчество, Учебная_группа}. Последнюю зависимость, очевидно, можно использовать для декомпозиции исходного отношения на следующие два: Студентысписок (lD_студента, Фамилия, Имя, Отчество) И Группы (10_студента, Учебная группа). На рис. 1.11 показано исходное отношение студенты и две его проекции.
Ю^студента, Фамилия :ИмяОтчество001ИвановИванИванович002ПетровПетрПетрович003СидоровСидорСидорович004ФедоровФедорФедорович005ИвановИванИванович006ИвановИванИванович007НиколаевНиколайНиколаевич008СтепановСтепанСтепанович009МихайловМихаилМихайлович010ИвановИванИвановичЮ^студента, Фамилия :ИмяОтчество001ИвановИванИванович002ПетровПетрПетрович003СидоровСидорСидорович004ФедоровФедорФедорович005ИвановИванИванович006ИвановИванИванович007НиколаевНиколайНиколаевич008СтепановСтепанСтепанович009МихайловМихаилМихайлович010ИвановИванИвановичСтудентьСсписок: таблицаСтудентьСсписок: таблицаЮ^студентаУчебная_группа001201002201003201004202005202006201007202008203009203010202Ю^студентаУчебная_группа001201002201003201004202005202006201007202008203009203010202Группы : таблицаГруппы : таблица
Ю^студента
, Фамилия :
Имя
Отчество
001
Иванов
Иван
Иванович
002
Петров
Петр
Петрович
003
Сидоров
Сидор
Сидорович
004
Федоров
Федор
Федорович
005
Иванов
Иван
Иванович
006
Иванов
Иван
Иванович
007
Николаев
Николай
Николаевич
008
Степанов
Степан
Степанович
009
Михайлов
Михаил
Михайлович
010
Иванов
Иван
Иванович
Ю^студента
, Фамилия :
Имя
Отчество
001
Иванов
Иван
Иванович
002
Петров
Петр
Петрович
003
Сидоров
Сидор
Сидорович
004
Федоров
Федор
Федорович
005
Иванов
Иван
Иванович
006
Иванов
Иван
Иванович
007
Николаев
Николай
Николаевич
008
Степанов
Степан
Степанович
009
Михайлов
Михаил
Михайлович
010
Иванов
Иван
Иванович
СтудентьСсписок: таблица
СтудентьСсписок: таблица
Ю^студента
Учебная_группа
001
201
002
201
003
201
004
202
005
202
006
201
007
202
008
203
009
203
010
202
Ю^студента
Учебная_группа
001
201
002
201
003
201
004
202
005
202
006
201
007
202
008
203
009
203
010
202
Группы : таблица
Группы : таблица
Студенты : таблица
Ю^студента
фамилия -
Имя "
Отчество
Учебная^группа
001
Иванов
Иван
Иванович
201
002
Петров
Петр
Петрович
201
003
Сидоров
Сидор
Сидорович
201
004
Федоров
Федор
Федорович
202
005
Иванов
Иван
Иванович
202
006
Иванов
Иван
Иванович
201
007
Николаев
Николай
Николаевич
202
008
Степанов
Степан
Степанович
203
009
Михайлов
Михаил
Михайлович
203
010
Иванов
Иван
Иванович
202
Рис. 1.11. Декомпозиция отношения Студентыс ключом Ю_студента на две проекции
Ключ, состоящий из одного атрибута, называют простым, а из нескольких — составным. Ключи, рассмотренные в данном разделе, также называют первичными (primary key).
Ограничения целостности отношений
До сих пор мы рассматривали отношения, представленные в виде таблиц своими кортежами (записями). Зависимости между атрибутами выражали некоторую целостность отношений, а корректная декомпозиция отношений на основе зависимостей рассматривалась как сохраняющая их целостность. Таким образом, мы изучали реляционные базы данных главным образом как теорию декомпозиции отношений, заданных экстенсионально, т. е. посредством наборов записей. На практике приходится проектировать базу данных как набор из нескольких таблиц, которые изначально пусты и только со временем заполняются конкретными данными. Иначе говоря, довольно сложные базы данных проектируются путем разработки множества отдельных таблиц с последующей установкой связей между ними, т. с. путем композиции частей в некое целое. При этом нс исключается и декомпозиция таблиц на свои проекции.
Однако каждая таблица в отдельности, а также совокупность таблиц обычно являются не случайными комбинациями атрибутов, произвольно распределенными между различными таблицами. Как отдельные таблицы, так и вся их совокупность, называемая базой данных, обладают некоторой целостностью, которая выражается через различного рода ограничения, накладываемые на значения столбцов и связи между ними. Таким образом, при проектировании базы данных мы заняты не обнаружением зависимостей для использования их при декомпозиции, а наоборот, заданием их с целью объединения таблиц в целостную систему связанных таблиц, содержащих непротиворечивые данные.
Далее мы рассмотрим несколько видов целостности и способы их задания.
Семантическая целостность
При проектировании базы данных стремятся к тому, чтобы каждая таблица соответствовала некоторому объекту внешнего мира. Существование такого объекта, разумеется, нс зависит от базы данных. Если таблица полностью соответствует некоторому объекту, то говорят, что она обладает семантической целостностью
и моделирует (представляет) этот объект. При этом каждая запись таблицы моделирует некий элемент объекта.
В таблице, обладающей семантической целостностью, должен быть первичный ключ. Первичный ключ (см. разд. 1.3.5) — это один столбец или группа столбцов, значения которых должны быть уникальными и определенными (не равными null). В языке SQL это ограничение целостности, выражающееся в виде ограничения на значения столбца или группы столбцов, задается ключевыми словами primary key (см. разд. 7.1.1. 7.1.2).
Доменная целостность
С каждым атрибутом отношения связан домен — множество допустимых значений. При создании таблицы для каждого столбца кроме имени указывается и тип данных, которые он может содержать. Тип данных (см. разд. 2.2) ограничивает множество допустимых значений столбца, однако во многих случаях такого ограничения оказывается недостаточно. Например, если в числовом столбце возраст таблицы сотрудники указано значение 1000, то мы не усомнимся, что это ошибочное значение. В той же таблице символьный столбец должность может принимать значения из определенного списка, предусмотренного штатным расписанием организации, а не произвольную комбинацию символов или наименования должностей с орфографическими ошибками.
Ограничения на допустимые значения для столбца таблицы предназначены для поддержания доменной целостности. В языке SQL риск нарушить доменную целостность возникает при добавлении и обновлении записей с помощью операторов insert и update соответственно. Ограничения доменной целостности можно задать при создании таблицы с помощью оператора create table, а также предварительно, путем создания домена, применив оператор create domain.
Ссылочная целостность
В хорошо спроектированной базе из нескольких таблиц они связаны друг с другом. Так столбец в одной таблице может ссылаться на столбец другой таблицы этой же базы данных. Подобные
ссылки представляют собой ограничения ссылочной целостности базы данных и играют важную роль при поддержке се общей целостности. Вместе с тем, наличие ссылок порождает так называемую проблему аномалий модификации данных.
Связи между таблицами обычно несимметричны: одна таблица зависит от другой. Допустим, в базе данных имеются две таблицы:
Клиенты (Имя_клиента, Адрес, Телефон);
Продажи (ID, Товар, Количество, Цена, Стоимость, Имя_клиента).
В таблице клиенты столбец Имя клиента является ключом (primary key), т. е. имеет уникальные и определенные значения. В таблице продажи одноименный столбец нс является ключом, его значения могут повторяться, т. к. один и тот же клиент может приобрести несколько товаров. Эта таблица связана с таблицей Клиенты ПО столбцу Имя_клиента, Т. е. столбец Имя_клиента первой таблицы (продажи) ссылается на одноименный столбец второй таблицы (клиенты). Другими словами, данные таблицы находятся в родительско-дочернем отношении: таблица клиенты родительская, продажи дочерняя. Данная связь между таблицами организуется путем объявления столбца Имя_клиента таблицы Продажи ВНешНИМ КЛЮЧОМ (FOREIGN KEY), ссылающимся на первичный ключ в таблице клиенты (см. разд. 7.1.3).
Аномалии модификации могут возникнуть различными способами и при различных обстоятельствах, вызывая трудности. Предположим, какой-то клиент перестал вас интересовать (например, он перестал делать у вас покупки). Если запись о нем удалить из таблицы клиенты, то в дочерней таблице продажи останутся записи, ссылающиеся на отсутствующую запись в родительской таблице. Аналогичная ситуация возникает при попытке добавить в дочернюю таблицу запись, когда в родительскую таблицу еще не было сделано соответствующего добавления. О способах поддержки ссылочной целостности и поведении в случае возникновения аномалии модификации будет рассказано в разд. 7.1.3.
Нормализация таблиц
База данных может быть спроектирована хорошо или плохо. Сразу создать хороший проект довольно сложно, и на практике процесс проектирования обычно является итерационным: состав таблиц и их структура модифицируются в несколько этапов, пока не будет получен приемлемый результат.
В плохом проекте часто возникают аномалии модификации данных, устранить которые довольно трудно. Такие аномалии могут возникнуть даже в однотабличной базе данных.
Рассмотрим в качестве примера таблицу продажи (клиент, товар, количество, цена), показанную на рис. 1.12.
Продажи : таблица
Клиент
Товар : ;
Количество
: Цена.
Иванов
Хлеб
2
24,50р
Петров
Молоко
3
ЗО.ООр
ОАО "Рога и копыта"
Хвосты
25
120,00р.
ЗАО "111"
Молоко
1
ЗО.ООр.
Сидоров
Хлеб
3
24,50р
Рис. 1.12. Пример таблицы, в которой могут возникнуть аномалии модификации
В таблице, показанной на рис. 1.12, могут возникнуть аномалии модификации данных. Предположим, было решено удалить из нес запись о клиенте "ОАО "Рога и копыта", поскольку теперь он ничего не приобретает в вашей фирме. Но тогда вы потеряете информацию и цене на "Хвосты". Если же вам потребуется добавить запись о каком-нибудь новом товаре, то необходимо добавить и сведения о покупателе и количестве этого товара. А если пока такого покупателя нет?
Аномалия модификации, возникшая в рассмотренном примере, обусловлена тем, что данная таблица содержит информацию, относящуюся к различным темам. В ней есть сведения и о том, что приобрели покупатели, и о цене товаров. Лучше разбить ее на две таблицы, посвященные двум различным темам: продажи_ клиенты и прайс лист, показанные на рис. 1.13.
Клиент
Товар:
Количество
Иванов^
Петров
ОАО "Рога и копыта"
ЗАО "111"
Сидоров
Хлеб Молоко Хвосты Молоко Хлеб
2;
3
25;
1;
3|
Продажи_клменты: таблица
Рис. 1.13. Результат декомпозиции таблицы Продажи
ТоварШ
Цена
Хлеб
24,50р.
Молоко
30,00р. :
Хвосты
120,00р. ;
ПраЙс_лист: таблица
Вообще говоря, любую таблицу, относящуюся к двум или более темам, следует разбить на две или более таблицы. При определенных условиях, о которых говорилось ранее, можно декомпозировать и таблицу, посвященную одной теме. В этом случае каждая из результатных таблиц будет соответствовать какой-то части одной темы. Этот процесс декомпозиции и составляет суть нормализации. Обнаружив в таблице аномалию модификации, мы устраняем ее путем декомпозиции на две или более таблицы так, чтобы они были свободны от аномалий. Однако, производя декомпозицию, мы вынуждены задавать ограничения ссылочной целостности (см. разд. 1.4.3).
Таблицы классифицируются по тем видам аномалий модификации, которым они подвержены. Это так называемые нормальные формы таблиц (отношений). В своей статье 1970 г. И. Кодд определил три источника аномалий и три формы таблиц, свободных от них. В последующие годы он и другие исследователи обнаружили другие виды аномалий и предложили формы таблиц, которые им не подвержены. Далее приводится список всех специальных нормальных форм:
Первая нормальная форма (1НФ).
Вторая нормальная форма (2НФ).
Третья нормальная форма (ЗНФ).
Нормальная форма Бойса—Кодда (НФБК).
Четвертая нормальная форма (4НФ).
Пятая нормальная форма (5НФ).
Все нормальные формы вложены друг друга в следующем смысле: таблица в 2НФ является также и таблицей в 1НФ; таблица в ЗНФ является таблицей и в 2НФ, и в 1НФ, и т. д.
Каждая из перечисленных нормальных форм могла устранить определенные виды аномалий, и нс было гарантии, что с их помощью можно устранить всевозможные аномалии, о которых пока просто не было известно. В 1981 г. Р. Фагин ввел новую нормальную форму, названную доменно-ключевой (ДКНФ), и доказал, что таблица в ДКНФ свободна от всех аномалий модификации и наоборот: таблица, свободная от любых аномалий модификации, находится в ДКНФ. До появления этой важной теоремы теоретики реляционных баз данных должны были продолжать поиск не выявленных видов аномалий и соответствующих им нормальных форм. Теперь же было доказано, что для того, чтобы получить уверенность в отсутствии всех видов аномалий модификации, следует привести таблицу к ДКНФ.
Далее мы кратко рассмотрим первые три наиболее важные для практики нормальные формы, а также доменно-ключевую нормальную форму.
Первая нормальная форма
Любая таблица, удовлетворяющая определению отношения, находится в 1НФ. Вот основные характеристики таблицы в 1НФ:
в каждой строке таблицы должны содержаться данные, соответствующие некоторому объекту или его части;
в каждом столбце должны находиться данные, соответствующие одному из атрибутов отношения;
в каждой ячейке таблицы должно находиться только единственное значение;
у каждого столбца должно быть уникальное имя;
все строки (записи) в таблице должны быть различными;
порядок расположения столбцов и строк в таблице не имеет значения.
Таблица (отношение) в 1НФ свободна от некоторых аномалий, но все же подвержена многим другим. Например, таблица, показанная на рис. 1.12, находится в 1НФ, но, как уже было отмечено, подвержена аномалиям удаления и добавления записей.
Вторая нормальная форма
Каждая таблица в 1НФ должна иметь первичный ключ. Он может состоять из одного или более столбцов (атрибутов). В последнем случае ключ называется составным. Чтобы таблица была в 2НФ, все ее неключевые столбцы должны однозначно определяться всем ключом, т. е. всеми его компонентами, а нс некоторыми из них.
Рассмотрим пример отношения Секции (Имя, Секция, Плата), показанный на рис. 1.14.
Секции: таблица
Имя
Секция
Плата
Иванов
Футбол
100
Иванов
Волейбол
120
Петров
Лыжи
170
Сидоров
Шахматы
200
Сидоров
Лыжи
170
Федоров
Лыжи
170
Федоров
Волейбол
120
Рис. 1.14. Отношение Секции (Имя, Секция, Плата)
Ключом В данном отношении является {Имя, секция}, но оно содержит функциональную зависимость Секция-> Плата. Аргумент (левая часть) этой зависимости является лишь частью составного ключа. Отношение секции имеет аномалии удаления и добавления. Так, если мы захотим удалить записи с именем "Иванов", то потеряем стоимость шахматной секции. Мы не сможем добавить запись о новой секции, пока в нес кто-нибудь нс запишется. Данных аномалий можно было бы избежать, если бы атрибут плата зависел от всего ключа (однозначно определялся всем ключом).
Отношение Секции (Имя, Секция, Плата) В 1НФ МОЖНО разбить на два отношения во 2НФ:
Секция_члены (Имя, Секция);
Секция плата (Секция, Плата).
Третья нормальная форма
В отношениях могут быть так называемые транзитивные зависимости, являющиеся источником аномалий модификации данных, против которых 2НФ бессильна. Транзитивная зависимость имеет место тогда, когда один атрибут однозначно определяет второй, второй однозначно определяет третий и т. д.
Рассмотрим в качестве примера отношение гости (ш_гостя, Тип ыомера. Плата), представляющее сведения о проживающих в гостинице. Ключом в этом отношении является ю_гостя, плата однозначно определяется атрибутом Тип_номера (например, люкс, полулюкс и т. д.), т. е. имеется функциональная зависимость тип_номера-> Плата. Поскольку каждый гость проживает только в одном номере определенного типа, в отношении есть и функциональная зависимость ю_гостя-> тип номера. Таким образом, возникает транзитивная (опосредованная) зависимость тп__гостя-> плата. Так как ключ состоит из единственного атрибута тп_гостя, то отношение находится в 2НФ.
В рассматриваемом отношении существует аномалия удаления. Удалив запись, мы потеряем не только информацию о каком-то госте (где он проживает), но и сведения о том, сколько стоит номер соответствующего типа.
Чтобы устранить указанную аномалию, следует декомпозировать исходное отношение гости (ю_гостя, тип_номера, Плата) на два:
Проживание (ю_гостя, Тип_номера);
Тип_плата (Тип_номера, Плата).
Эти отношения будут находиться в 2НФ и не содержать транзитивных зависимостей.
Таким образом, отношение находится в ЗНФ, если оно находится в 2НФ и не содержит транзитивных зависимостей.
Доменно-ключевая нормальная форма
Если таблица находится в ЗНФ, то остается довольно мало шансов для возникновения аномалий модификации данных, но они все равно есть. Чтобы исключить все виды возможных аномалий,
таблица должна находиться в доменно-ключевой нормальной форме (ДКНФ).
Понятие ДКНФ довольно просто: отношение находится в ДКНФ, если каждое ограничение, накладываемое на него, является логическим следствием определения доменов и ключей. Термин ограничение (constraint) здесь намеренно трактуется широко. Р. Фагин определяет ограничение как любое правило, регулирующее возможные статические значения атрибутов, достаточно точное, чтобы можно было проверить его выполнимость. Правила редактирования, ограничения взаимосвязей и структуры отношений, функциональные и многозначные зависимости являются примерами таких ограничений. Отсюда исключаются ограничения, связанные с изменением данных (ограничения, зависящие от времени). Другими словами, отношение находится в ДКНФ, если выполнение ограничений на домены и ключи влечет за собой выполнение всех ограничений.
Однако в настоящее время не известен алгоритм преобразования отношения в ДКНФ. Неизвестно также, какие отношения в принципе могут быть приведены к ДКНФ. Поиск и создание отношений в ДКНФ сейчас является искусством, а не наукой. В литературе обычно приводятся только примеры отношений в ДКНФ, которые мы здесь рассматривать нс будем.
Денормализация
Чтобы исключить как можно больше аномалий модификации данных, старайтесь как можно больше нормализовать таблицы базы данных. Лучше, если вы доведете их до ДКНФ, хотя на практике это редко происходит. Чаще ограничиваются второй или третьей нормальными формами. Занимаясь нормализацией, вы увеличиваете количество таблиц в базе данных, и при определенном их количестве эффективность работы может оказаться слишком низкой. Кроме того, формулировать SQL-запросы к базе данных тем легче, чем меньше в ней таблиц. Так что, на любом этапе своего развития база данных может быть в какой-то степени денормализованной.
Глава 2
Основы SQL
Что такое SQL
Первые разработки систем управления реляционными базами данных (реляционных СУБД) были выполнены в компании IBM в начале 1970-х годов. Тогда же был создан язык данных, предназначенный для работы в этих системах. Экспериментальная версия этого языка называлась SEQUEL — от англ. Structured English QUEry Language (структурированный английский язык запросов). Однако официальная версия была названа короче — SQL (Structured Query Language). Точнее говоря, SQL — это подъязык данных, поскольку СУБД содержит и другие языковые средства.
В 1981 году IBM выпускает реляционную СУБД SQL/DS. К этому времени компания Relation Software Inc. (сегодня это Oracle Corporation) уже выпустила свою реляционную СУБД. Эти продукты сразу же стали стандартом систем, предназначенных для управления базами данных. В состав этих продуктов вошел и SQL, который фактически стал стандартом для подъязыков данных. Производители других СУБД выпустили свои версии SQL. В них имелись не только основные возможности продуктов IBM. Чтобы получить некоторое преимущество для "своей” СУБД, производители вводили некоторые расширения SQL. Вместе с тем, начались работы по созданию общепризнанного стандарта SQL.
В 1986 году Американский национальный институт стандартов (American National Standards Institute, ANSI) выпустил официальный стандарт SQL-86, который в 1989 году был обновлен
и получил новое название SQL-89. В 1992 году этот стандарт был назван SQL-92 (ISO/IEC 9075:1992). Последней к моменту написания этой книги версией стандарта SQL является SQL:2003 (ISO/IEC 9075Х:2003).
Любая реализация SQL в конкретной СУБД несколько отличается от стандарта, соответствие которому объявлено производителем. Так, многие СУБД (например, Microsoft Access 2003, PostgreSQL 7.3) поддерживают SQL-92 не в полной мере, а лишь с некоторым уровнем соответствия. Кроме того, они поддерживают и элементы, которые не входят в стандарт. Однако разработчики СУБД стремятся к тому, чтобы новые версии их продуктов как можно в большей степени соответствовали стандарту SQL.
Внимание
В данной книге описаны элементы SQL:2003, не все из которых поддерживаются существующими СУБД. Прежде чем применять их на практике, следует убедиться, что они будут работать в вашей СУБД. Об этом можно узнать из технической документации.
Большинство описанных в книге элементов соответствуют и более ранним версиям SQL, в частности, широко распространенному SQL-92.
SQL задумывался как простой язык запросов к реляционной базе данных, близкий к естественному (точнее, к английскому) языку. Предполагалось, что близость по форме к естественному языку сделает SQL средством, доступным для широкого применения обычными пользователями баз данных, а не только программистами. Первоначально SQL не содержал никаких управляющих структур, свойственных обычным языкам программирования. Запросы, синтаксис которых довольно прост, вводились прямо с консоли последовательно один за другим и в этой же последовательности выполнялись. Однако SQL так и не стал инструментом банковских служащих, продавцов авиа- и железнодорожных билетов, экономистов и других служащих различных фирм, использующих информацию, хранимую в базах данных. Для них простой SQL оказался слишком сложным и неудобным, несмотря на свою близость к естественному языку вопросов.
На практике с базой данных обычно работают посредством приложений, написанных программистами на процедурных языках, например, на С, Visual Basic, Pascal, Java и др. Часто приложения создаются в специальных средах визуальной разработки, таких как Delphi, Microsoft Access, Visual dBase и т. п. При этом разработчику приложения практически не приходится писать коды программ, поскольку за него это делает система разработки. Во всяком случае, работа с программным кодом оказывается минимальной. Эти приложения имеют удобный графический интерфейс, не вынуждающий пользователя непосредственно вводить запросы на языке SQL. Вместо него это делает приложение. Впрочем, приложение может как использовать, так и не использовать SQL для обращения к базе данных. SQL не единственное, хотя и очень эффективное средство получения, добавления и изменения данных, и если есть возможность использовать его в приложении, то это следует делать.
Реляционные базы данных могут и действительно существуют вне' зависимости от приложений, обеспечивающих пользовательский интерфейс. Если по каким-либо причинам такого интерфейса нет, то доступ к базе данных можно осуществить с помощью SQL, используя консоль или какое-нибудь приложение, с помощью которого можно соединиться с базой данных, ввести и отправить SQL-запрос (например, Borland SQL Explorer).
Язык SQL считают декларативным (описательным) языком, в отличие от языков, на которых пишутся программы. Это означает, что выражения на языке SQL описывают, что требуется сделать, а не каким образом. Например, для того чтобы выбрать из таблицы сотрудники сведения о фамилиях и должностях сотрудников 102 отдела, достаточно выполнить следующий запрос:
SELECT Фамилия, Должность FROM Сотрудники WHERE Отдел=102;
По-русски данное выражение звучит так:
ВЫБРАТЬ Фамилия, Должность ИЗ Сотрудники ПРИ УСЛОВИИ, ЧТО Отдел = 102;
Чтобы изменить значение "Иванов" на "Петров" столбца Фамилия, достаточно выполнить следующий запрос:
UPDATE Сотрудники SET Фамилия = ’Петров* WHERE Фамилия = *Иванов *;
По-русски данное выражение выглядит так:
ОБНОВИТЬ Сотрудники УСТАНОВИВ Фамилия РАВНЫМ 1 Петров1 ГДЕ Фамилия — * Иванов’'
Вам не нужно подробно описывать действия, которые должна выполнить СУБД, чтобы выбрать из таблицы указанные в запросе данные. Вы просто описываете, что желаете получить. В результате выполнения запроса СУБД возвращает таблицу, содержащую запрошенные вами данные. Если в базе данных не оказалось данных, соответствующих запросу, то будет возвращена пустая таблица.
Однако последние версии SQL поддерживают операторы управления вычислениями, свойственные процедурным языкам управления (операторы условного перехода и цикла). Поэтому SQL сейчас это не чисто декларативный язык.
Кроме выборки, добавления, изменения и удаления данных из таблиц, SQL позволяет выполнять все необходимые действия по созданию, модификации и обеспечению безопасности баз данных. Все эти возможности распределены между тремя компонентами SQL:
DML (Data Manipulation Language — язык манипулирования данными) предназначен для поддержки базы данных: выбора (select), добавления (insert), изменения (update) и удаления (delete) данных из таблиц. Эти операторы (команды) могут содержать выражения, в том числе и вычисляемые, а также подзапросы — запросы, содержащиеся внутри другого запроса. В общем случае выражение запроса может быть настолько сложным, что сразу и не скажешь, что он делает. Однако сложный запрос можно мысленно разбить на части, которые легче анализировать. Аналогично, сложные запросы создаются из относительно простых для понимания выражений (подзапросов). DDL посвящена большая часть данной книги (см. гл. 3—6, 8—10);
DDL (Data Definition Language — язык определения данных) предназначен для создания, модификации и удаления таблиц и всей базы данных. Примерами операторов, входящих в DDL, ЯВЛЯЮТСЯ CREATE TABLE (создать Таблицу), CREATE VIEW (СОЗдать представление), create shema (создать схему), alter
table (изменить таблицу), drop (удалить) и др. Основные элементы DDL рассматриваются в гл. 7\
DCL (Data Control Language — язык управления данными) предназначен для обеспечения защиты базы данных от различного рода повреждений. СУБД предусматривает некоторую защиту данных автоматически. Однако в ряде случаев следует предусмотреть дополнительные меры, предоставляемые DCL, которые будут рассмотрены в гл. 8—11.
Внимание
Ключевые слова в выражениях на языке SQL могут записываться как прописными, так и строчными буквами, в одну или несколько строк. В конце выражения должна стоять точка с запятой (;). Однако во многих системах, обеспечивающих ввод, редактирование и отправку на выполнение SQL-выражений, в случае ввода одного такого выражения допускается не указывать признак окончания (;). Если же требуется выполнить блок из нескольких SQL- выражений, то они обязательно должны быть разделены точкой с запятой.
Все ключевые слова SQL (команды, операторы и проч.) являются зарезервированными и не должны использоваться в качестве имен таблиц, столбцов, переменных и т. п. Вот пример неправильного использования ключевых слов:
SELECT SELECT FROM WHERE WHERE SELECT=UPDATE;
Типы данных
Во всех языках программирования, а также в SQL важное место занимают поддерживаемые типы данных. Данные, которые хранятся в памяти компьютера и подвергаются обработке, можно отнести к различным типам. Понятие типа данных возникает естественным образом, когда необходимо применить к ним операции обработки. Например, операция умножения применяется к числам, т. е. к данным числового типа. А что получится, если умножить слово "Вася" на число 25? Поскольку трудно дать вразумительный ответ на этот вопрос, то напрашивается вывод: некоторые операции не следует применять к разнотипным данным. Мы также не знаем, что должно получиться в результате умножения слов "Саша" и "Маша", поэтому заключаем, что оп-
ределенные операции вообще не применимы к данным некоторых типов. С другой стороны, существуют операции, результат которых зависит от типа данных. Например, операция сложения, обозначаемая символом (+), может применяться и к двум числам, и к двум строкам, состоящим из произвольных слов. В первом случае результатом применения этой операции будет некоторое число, а во втором — строка, получающаяся путем приписывания второй строки к концу первой. В случае строк операцию сложения еще называют склейкой или конкатенацией. Операции, применимые к различным типам данных, но обозначаемые одним и тем же символом, называют перегруженными. Так, операция, обозначаемая символом (+), является перегруженной: применительно к числам она выполняет арифметическое сложение, а применительно к строкам символов — склейку. Типы данных столбцов таблиц базы данных могут отличаться от типов данных, поддерживаемых SQL. Это обстоятельство необходимо учитывать при составлении SQL-запросов. Так, например, если в таблице сотрудники столбец зарплата определен по каким-либо причинам как символьный, то следующий запрос приведет к ошибке из-за несоответствия типов сравниваемых данных:
SELECT Фамилия FROM Сотрудники WHERE Зарплата > 25000;
Данный запрос пытается получить список всех сотрудников, у которых зарплата больше 25 000. Однако в таблице сотрудники столбец зарплата содержит символьные, а не числовые данные. В условии запроса (оператор where) сравниваются символьное и числовое значения. На практике данная проблема легко преодолима с помощью функции casto приведения значения к требуемому типу (см. разд. 2.2.9). Кроме того, между некоторыми типами столбцов таблицы и данных, участвующих в SQL- выражениях, существуют соответствия, допускающие их совместное использование. Такие типы называют согласованными.
Различные СУБД поддерживают несколько отличающиеся наборы типов данных для столбцов таблиц базы данных. Аналогичная ситуация и с типами данных, поддерживаемых различными версиями и реализациями SQL. Вместе с тем, всегда имеются типы данных, которые поддерживаются всеми реализациями SQL.
В спецификации SQL:2003 признаны пять предопределенных общих типов, внутри которых могут быть подтипы:
□ строковый (символьный):
CHARACTER (ИЛИ CHAR);
CHARACTER VARYING (ИЛИ VARCHAR);
CHARACTER LARGE OBJECT (ИЛИ CLOB);
□ числовой:
точные числовые типы:
integer;
smallint;
bigint;
numeric;
decimal;
• приблизительные числовые типы:
real;
DOUBLE precision;
float;
логический (булевский) — boolean;
даты-времени:
date;
time without time zone;
TIME WITH TIME zone;
TIMESTAMP WITHOUT TIME ZONE;
TIMESTAMP WITH TIME ZONE;
□ интервальный.
Кроме того, существуют особые типы: row (запись), array (массив) и multiset (мультимножество).
Строки
Строковые данные (последовательности символов) имеют три главных строковых типа. Для столбца таблицы можно указать тип character (п) или char (п) (строка фиксированной длины), где п — максимальное количество символов, содержащихся в строке. Если (п) не указано, то предполагается, что строка состоит из одного символа. Если в столбец типа character (л) вводится т < п символов, то оставшиеся позиции заполняются пробелами.
ТИП ДаННЫХ CHARACTER VARYING (л) ИЛИ VARCHAR (л) (строка переменной длины) применяется тогда, когда вводимые данные имеют различную длину и нежелательно дополнять их пробелами. При этом сохраняется только то количество символов, которое ввел пользователь. В данном случае указание максимального количества символов обязательно (в отличие от character).
Данные типов character и character varying могут участвовать в одних и тех же строковых операциях.
Тип данных CHARACTER LARGE OBJECT (CLOB — боЛЬШОЙ СИМВОЛЬНЫЙ объект) используется для представления очень больших символьных строк (например, статей, книг и т. п.). В некоторых СУБД данный тип называется мемо, а в других — text. С данными этого типа можно выполнять не все операции, предусмотренные ДЛЯ ТИПОВ CHARACTER И CHARACTER VARYING. Так, ИХ НСЛЬЗЯ использовать в операциях сравнения, за исключением равенства и неравенства. Кроме того, столбцы этого типа не могут быть первичными и внешними ключами, а также быть объявлены как имеющие уникальные значения. Иначе говоря, при создании таблиц с помощью оператора create и объявлении столбцов типа clob нельзя использовать ключевые слова primary key, FOREIGN KEY И UNIQUE.
В следующем примере создается таблица с обычным символьным столбцом и столбцом типа clob, значения которого могут содержать 100 000 символов:
CREATE TABLE шуТаЫе (
FIELD1 CHARACTER (60), FIELD2 CLOB (100000)
Здесь оператор create table создает таблицу с именем туТаЫе, которая состоит из двух столбцов с именами fieldi и field2, типы столбцов указаны рядом с их именами.
Различные языки используют различные наборы символов. Даже английский и немецкий наборы отличаются, не говоря уж о русском и китайском. Система может быть настроена на некоторый набор символов, принимаемый по умолчанию. Однако при этом можно использовать и другие национальные символьные наборы. Так, в следующем примере создается таблица, в которой столбец fieldi объявляется как строковый с набором символов, принятым по умолчанию, а столбец field2 — как строковый с греческим набором символов:
CREATE TABLE myTable
(
FIELDI CHARACTER (60),
FIELD2 CHARACTER VARYING (80) CHARACTER SET GREEK );
Значения строкового типа в SQL-выражениях заключаются В одинарные кавычки. Например, •Иванов Иван Иванович1, '12345 рублей’, ’тел. (812) 123-4567’. Пустая строка НС содержит ни одного символа и имеет вид: ’ ’. Строка, содержащая один или более пробелов, не является пустой.
Иногда бывает так, что строковый столбец в таблице содержит только числа (точнее, строки, содержащие цифры, знаки числа и разделительные точки). Чтобы использовать такие данные в операциях с числами, необходимо привести данные одного типа к другому типу с помощью функции cast о (см. разд. 2.2.9).
Значения типа character и character varying (varchar) совместимы в том смысле, что они могут участвовать как операнды в строковых операциях и операциях сравнения.
Числа
Числовой тип данных может быть двух видов — точный и приблизительный. Точные числовые типы позволяют точно выразить значение числа. Некоторые величины имеют очень большой диапазон значений, и в таких случаях достаточно ограничиться некоторым приближенным их представлением с учетом технических возможностей компьютера (размеров регистра).
К точным числовым относятся следующие пять типов:
integer —- целое (без дробной части) число. Количество разрядов (точность) зависит от реализации SQL. В некоторых реализациях числа этого типа лежат в диапазоне от -2 147 483 648 до 2 147 483 647 (четырехбайтное целое число);
smallint — малое целое число. Количество разрядов зависит от реализации SQL, но не больше количества разрядов integer в этой же реализации. В некоторых реализациях числа этого типа лежат в диапазоне от -32 768 до 32 767 (двухбайтное целое число);
bigint — большое целое число. Количество разрядов зависит от реализации SQL и превышает количество разрядов числа типа integer;
numeric (х, у) — число, в котором всего х разрядов (точность), из которых у разрядов (масштаб) отводится для дробной части. Если у не указано (numeric (х)), то для дробной части отводится количество разрядов, установленное в системе по умолчанию. Если не указаны ни х, ни у (numeric), то принимаются обе эти величины, установленные по умолчанию. Например, если указан тип numerc (6, 2), то максимальное значение числа равно 9999.99;
decimal (х, у) — десятичное число, в котором всего х разрядов, из которых у разрядов отводятся для дробной части. Если х или/и у не указаны, то принимаются значения по умолчанию. Этот тип очень похож на numeric. Отличие состоит в том, что если в decimal (х, у) указанные х и у меньше, чем допустимые реализацией SQL, то будут использоваться последние. Если х и у не указаны, то применяется система умолчаний. Например, вы задали для столбца тип decimal (6, 2). Если реализация SQL позволяет, то в этот столбец можно ввести числа, превышающие 9999.99. В отличие от decimal (х, у), тип numeric (х, у) жестко задает диапазон возможных значений числовой величины.
К приблизительным числовым типам относятся следующие три типа:
real — вещественное число одинарной точности с плавающей разделительной точкой (эта точка "плавает", появляясь
в различных местах числа). Например, 5.25, 5.257, 5.2573. Точность представления числа зависит от реализации SQL и оборудования. Например, 32-битовый компьютер дает бульшую точность, чем 16-битовый;
double precision — вещественное число двойной точности с плавающей разделительной точкой. Точность представления числа зависит от реализации SQL и оборудования. Применяется для представления научных данных (например, результатов измерений) в широком диапазоне значений, т. е. как очень малых (близких к 0), так и очень больших;
float (х) — вещественное число с плавающей разделительной точкой и минимальной точностью х, занимающее не более 8 байтов. Если компьютер может поддержать указанную точность, используя аппаратную одинарную точность, то система будет использовать арифметику одинарной точности. Если указанная точность требует арифметики с двойной точностью, то система будет использовать ее. Данный тип следует применять, если предполагается возможность переноса базы данных на другую аппаратную платформу, отличающуюся размерами регистров. Пример значения типа float: 5.318Е-24 (т. е. 5.318, умноженное на ю в степени -24). Такую же форму представления имеют и числа типа real и double PRECISION.
При создании таблиц целочисленные типы применяются для столбцов, содержащих разного рода идентификаторы, например, номера (коды) клиентов, товаров, заказов и т. п. Разумеется, если содержимое столбца должно быть целым числом (например, количество ящиков, бутылок, штук и т. п.), то тип этого столбца естественно определить как integer, smallint или bigint.
Допустим, в таблице клиенты имеется столбец го_клиента, содержащий уникальные идентификаторы клиентов. Если количество клиентов не превышает 32 000, то тип столбца можно определить как smallint. Если в вашей таблице будут храниться сведения о сотнях тысяч клиентов, то тип столбца 1В_клиента следует определить как integer.
Если столбец в проектируемой таблице должен содержать числа с дробной частью, то для него можно задать какой-нибудь нецелочисленный тип. Если вы не уверены, что применить: точные
числовые типы или приблизительные, выбирайте точные (numeric, decimal). Они требуют меньше ресурсов и дают точные результаты. Если в столбце предполагается хранить данные из очень широкого диапазона (и очень малые, и очень большие числа), то используйте приблизительные типы данных (float, real).
Обратите внимание, что строка, содержащая число (например, ’12345.47’), является данным строкового, а не числового типа. Чтобы в SQL-выражениях сравнивать строковые и числовые данные, необходимо один из типов привести к другому с помощью функции CAST() .
Логические данные
В этой части математической логики, основоположником которой был английский математик Джон Буль, данные имеют только два значения — ИСТИНА и ЛОЖЬ, обозначаемые как true и false соответственно. Данные логического типа получаются в результате операций сравнения. Например, результатом вычисления выражения 3 < 5 является ИСТИНА, а выражения 2 + 3 = 10 - ЛОЖЬ.
В SQL тип данных boolean (булевский) имеет три значения — true, false и unknown. Значение unknown (неизвестное) было введено для обозначения результата, получающегося при сравнении со значением null (неопределенное). Если пользователь еще не ввел в ячейку таблицы никакого значения, то эта "пустая" ячейка содержит значение null, интерпретируемое как неизвестное или неопределенное значение.
Результатом любой операции сравнения true или false с null или с unknown всегда является unknown.
В SQL-выражениях логические значения заключаются в кавычки, например, ’true’ или ’true’.
Дата и время
Тип data (дата) предназначен для хранения значений даты, элементы которых расположены в следующем порядке: год (4 цифры), дефис (-), месяц (2 цифры), дефис, день (2 цифры).
Таким образом, значения даты занимают 10 позиций, например, 2005-10-02.
Данные этого типа могут содержать любую дату с 0001 года по 9999 год.
Для представления времени предусмотрены два типа:
time without time zone (время без часового пояса) предназначен для хранения значений времени, элементы которых расположены в следующем порядке: часы, двоеточие, минуты, двоеточие, секунды. Часы и минуты представляются двумя цифрами, а секунды могут быть представлены двумя и более цифрами (если требуется дробная часть), например 18:35:19.547. Длина дробной части секунд зависит от реализации, но внутреннее представление времени должно иметь не менее б цифр. По умолчанию время данного типа представляют без дробной части секунд. Чтобы указать, что время должно быть представлено с п цифрами после разделительной точки, достаточно использовать такой синтаксис: time without time zone (n). Например, чтобы кроме секунд указывались еще и миллисекунды, следует определить тип как time without time zone (3). Длина данных рассматриваемого типа без дробной части равна 8 символам, а с дробной частью — 9 плюс количество цифр после разделительной точки. Для задания времени без указания часового пояса с использованием установок по умолчанию можно использовать короткий синтаксис — time;
time with time zone (время с часовым поясом) — такой же тип данных, как и time without time zone. Отличие заключается лишь в том, что к значению времени добавляется еще и информация о разности между местным и всемирным временем. Всемирное время (Universal Time Coordinated, UTC) — это время по Гринвичу, т. е. время нулевого меридиана, проходящего через г. Гринвич в Великобритании (Greenwich Mean Time, GMT). Значение разности между локальным и всемирным временем находится в диапазоне от -12:59 до 13:00. Длина данных рассматриваемого типа равна длине данных типа time without time zone плюс б, поскольку дополнительная информация о разности времен занимает б позиций (дефис, знак (+) или (-), 2 цифры для часов, двоеточие, 2 цифры для минут).
Для одновременного представления даты и времени служат следующие два типа:
timestamp without time zone (дата и время без часового пояса). Элементы данных этого типа имеют такие же характеристики, как и для данных типа date и time without time zone, за исключением одного: данные типа timestamp without time zone по умолчанию имеют 6 цифр в дробной части секунд, а не 0, как в типе time without time zone. Для указания количества цифр в дробной части используется синтаксис timestamp without time zone (л). Если дробной части нет, то данные занимают 19 позиций: 10 позиций для даты, один пробел и 8 позиций для времени. Если определена дробная часть, то длина данных равна 20 плюс количество цифр в дробной части секунд;
timestamp with time zone (дата и время с часовым поясом) — такой же тип данных, как и timestamp with time zone. Отличие состоит в том, что к значению времени добавляется еще и информация о разности между местным и всемирным временем (см. time with time zone). Дополнительная информация занимает 6 позиций. Данные типа timestamp with time zone без дробной части занимают 25 позиций, с дробной частью — 26 плюс количество цифр в дробной части секунд.
Чтобы представить в SQL-выражении дату, время или дату- время, необходимо использовать функцию casto приведения к заданному типу. Допустим, в таблице продажи имеется столбец дата типа data. Чтобы получить сведения из этой таблицы за период после 2005-09-30, следует выполнить такой запрос:
SELECT * FROM Продажи WHERE Дата > CAST (’2005-09-30* AS DATE);
Здесь строка, содержащая дату, приводится к типу data, и полученный результат участвует в операции сравнения с данными столбца Дата.
В языке SQL имеются три функции, которые возвращают текущие дату и время:
current date — возвращает текущую дату (тип date). Например, 2005-06-18;
cuRRENTjriME (число) — возвращает текущее время (тип time). Целочисленный параметр число указывает точность
представления секунд. Например, при число = 2 секунды будут представлены с точностью до сотых (две цифры в дробной части): 12:39:45.27;
current_timestamp(число) — возвращает дату и время (тип TIMESTAMP), например 2005-06-18 12:39:45.27. ЦеЛОЧИСЛСН- ный параметр число указывает точность представления секунд.
Интервалы
Интервал представляет собой разность между двумя значениями типа дата-время. SQL поддерживает два типа интервалов: год- месяц и день-время. Интервал типа год-месяц — это количество лет и месяцев между двумя датами, а интервал день-время — количество дней, часов, минут и секунд между двумя моментами в пределах одного месяца. Нельзя смешивать вычисления, использующие интервал год-день, с вычислениями, в которых используется интервал день-время.
Интервал времени можно задать двумя способами: в виде начального и конечного моментов или в виде начального момента и длительности, например:
(TIME '12:25:30', TIME ' 14 : 30 : 00 ') — Интервал, заданный начальным и конечным моментами;
(TIME '12:45:00', INTERVAL '5' HOUR) — Интервал, ЗЭДаН- ный начальным моментом и длительностью в часах.
Чтобы задать значение типа интервал, используется такой синтаксис:
INTERVAL 'длина' YEAR | MONTH | DAY | HOUR | MINUTE | SECOND
Здесь длина длина интервала, после которой указывается еди— ница измерения (возможные значения указаны через вертикальную черту):
YEAR — ГОД;
month — месяц;
day — день;
hour — час;
minute — минута;
second — секунда.
Например, для задания интервала длиной 15 дней следует использовать выражение interval 151 day.
Еще один пример использования интервалов приведен в разд. 3.2.1 (предикат overlaps).
Специальные типы данных
К специальным типам данных относятся следующие типы:
row — запись;
ARRAY — массив;
multiset — мультимножество.
Использование этих типов данных нарушает принцип, согласно которому таблицы реляционной базы данных должны находиться в первой нормальной форме. Напомню, что любой столбец таблицы в первой нормальной форме не может содержать структурированные данные, например, набор полей: значения столбцов должны быть едиными и неделимыми. Тем не менее, жизнь неоднократно предлагает искушения отступить от теории в пользу эффективности. Если вы не отступаете от первой нормальной формы, то возникающие недоразумения в поддержке реляционной баз данных вы можете объяснить нарушениями законов реляционной теории. В противном случае для получения объяснений требуются более тщательные расследования происшедшего. Да, вне теории трудно жить в крайних случаях, хотя и более удобно в обычных обстоятельствах. Рассмотрим специальные типы данных подробнее.
Тип row (запись) позволяет объявить набор полей (запись) в качестве значения столбца таблицы. Таким образом, строки таблиц могут содержать записи.
Во многих базах данных имеются таблицы, содержащие сведения об адресах. Адрес, как известно, состоит из нескольких элементов, таких как почтовый индекс, страна, город, улица и т. д. Если в таблице завести один символьный столбец Адрес, в котором поместить все элементы адреса просто как символьную строку, то дальнейшая работа с отдельными элементами адреса будет довольно хлопотным делом (вам придется использовать функции разбора строк). Поэтому на практике для адреса обычно
заводят несколько столбцов, по одному на каждый элемент (например, столбцы город, Улица и т. д.). Если же использовать тип row, то можно хранить все элементы адреса как значения одного столбца. В следующем примере сначала на основе типа row создается тип addr, а затем он назначается столбцу Адрес при создании таблицы Клиенты:
CREATE ROW TYPE addr
PostCode VARCHAR (9),
City VARCHAR (30),
Street VARCHAR (30),
House VARCHAR (10)
CREATE TABLE Клиенты
1В_клиентаINTEGERPRIMARY KEY,
ИмяVARCHAR(25),
Адресaddr,
ТелефонVARCHAR(15)
);
Тип данных row появился впервые в SQL: 1999, а раньше без него прекрасно обходились.
Тип array (массив) также впервые появился в SQL: 1999 и нарушает принцип первой нормальной формы, но несколько иначе, чем тип row. Тип array дает возможность одному из обычных типов иметь множество значений внутри значения одного табличного столбца. Например, клиент может иметь несколько номеров телефонов и вы можете пожелать хранить их все как одно многозначное значение единственного столбца телефон, а не как обычные значения нескольких столбцов телефон1, телефон2 и т. п. Вот пример, в котором каждая ячейка столбца телефон может содержать до трех номеров телефона:
CREATE TABLE Клиенты
(
1В_клиента INTEGER PRIMARY KEY,
ИмяVARCHAR (25) ,
Адресaddr,
ТелефонVARCHAR (15) ARRAY [3]
);
Следующее SQL-выражение добавляет в таблицу клиенты новую запись и вводит в нее значения столбцов:
INSERT INTO Клиенты (Имя, Телефон)
VALUES ('Петров Петр Петрович', {'444-4444','123-4567',
'777-8899'});
Тип данных multiset (мультимножество) появился впервые в SQL:2003. Если массив — это упорядоченное множество элементов, однозначно связанных с их местом в массиве посредством индекса, то мультимножество — неупорядоченная совокупность элементов. Этот тип данных используется очень редко.
Пользовательские типы данных
Пользовательские типы данных (User Defined Types, UDT) определяются пользователем по своему усмотрению для каких-то своих целей. Данная возможность впервые появилась в SQL: 1999. Одно из важнейших направлений применения заключается в устранении некоторых несоответствий между основными типами данных SQL и типами данных в базовом языке, на котором пишутся приложения. К созданию своих типов данных обычно прибегают программисты, а не рядовые пользователи баз данных, даже владеющие языком SQL. Тем не менее, им также не помешает ознакомиться в общих чертах с этой возможностью SQL.
Отдельные типы
Простейшей формой пользовательских типов являются так называемые отдельные (различающиеся) типы. Они создаются на основе ранее определенных типов, например, основных типов SQL. Синтаксис создания отдельного типа данных такой:
CREATE DISTINCT TYPE имяТипа AS определенныйТип;
Определим в качестве примера тип money для хранения денежных сумм. С этой целью можно воспользоваться уже определенным числовым типом decimal:
CREATE DISTINCT TYPE MONEY AS DECIMAL (9,2);
Все отдельные типы, даже созданные на базе одного и того же типа, не могут сравниваться ни друг с другом, ни с базовым типом. Таким образом, данные типа money нельзя сравнивать с данными типа decimal, а также с данными других числовых id нечисловых типов. Создав специальный тип для денег, мы тем самым исключили возможность сравнивать денежные суммы с количеством других вещей. По аналогии с типом money можно создать типы данных для различных валют, чтобы исключить возможность сравнения их просто как обычных чисел:
CREATE DISTINCT TYPE EURO AS DECIMAL (9,2);
CREATE DISTINCT TYPE USD AS DECIMAL (9,2);
CREATE DISTINCT TYPE RU AS DECIMAL (9,2);
Чтобы сравнить денежные суммы в различных валютах, придется сначала выполнить конвертацию одной валюты в другую. В SQL это можно сделать с помощью функции приведения типов CAST() .
Допустим, имеется таблица Прайс_лист (товар, Цена_и50, Це- на_ри), в которой столбцы цена изЕ и Цена_ри определены как столбцы типов usd и ru соответственно. Эти столбцы содержат цены товаров в американских долларах и рублях. Предположим, что курс доллара к рублю изменился, и мы хотим изменить цены В рублях. Если бы столбцы UeHa USD И Цена_Ри были ОДНОГО и того же типа, то данную операцию можно было бы выполнить с помощью следующего SQL-выражения:
UPDATE Прайс_лист SET Цена_Ри = к * Цена_и50;
Здесь к — коэффициент конвертации.
Но поскольку столбцы цена изо и ценами имеют различные типы, то необходимо выполнить такое выражение:
UPDATE Прайс_лист
SET CAST(Цена_Ки AS DECIMAL (9,2) = k * CAST (L(eHa_USD AS DECIMAL (9,2));
Структурированные типы
Другая форма пользовательских типов данных — структурированные типы. Структурированный тип определяется не на основе какого-то базового (ранее определенного типа), а путем задания своих атрибутов и методов. Эта конструкция пришла в SQL
из объектно-ориентированного программирования (ООП). Она представляет средство некой технологии, отличающейся от традиционной технологии применения SQL, но близкой к современной технологии ООП.
При создании структурированного типа СУБД автоматически создает для него три функции:
функцию-конструктор с именем, совпадающим с именем создаваемого типа;
функцию-мутатор, с помощью которой можно изменить значение атрибута создаваемого типа;
функцию-наблюдатель, позволяющую узнать значение атрибута создаваемого типа.
Между структурированными типами может быть задана иерархия — отношение вложенности. Тип, содержащий другие типы, называется супертипом, а вложенные в него типы — подтипами.
Рассмотрим пример, в котором создаются некий тип и его подтип данных, таблица со столбцом созданного типа, а также блок операторов SQL, которые производят изменения данных в таблице. Вначале создадим тип данных Book, содержащий сведения (атрибуты) о книгах, затем создадим его подтип myBooks, который наследует атрибуты своего супертипа Books:
CREATE TYPE Books AS TitleCHAR (50),
AuthorCHAR (20),
Publisher CHAR (20), YearINTEGER,
VolumeINTEGER,
NOT FINAL;
Здесь в типе Books (книги) определяются атрибуты (имена и типы данных): Title (наименование), Author (автор), Publisher (издательство), Year (год издания), volume (количество страниц). Ключевые слова not final (не конец) означают, что данный тип имеет хотя бы один подтип.
Определим подтип myBooks для хранения данных, например, о моих любимых книгах:
CREATE TYPE myBooks UNDER Books FINAL;
По-русски это выглядит как:
СОЗДАТЬ ТИП myBooks ПОД Books ЗАКОНЧИТЬ;
Это означает, что создается конечный подтип, который не содержит других типов. Если бы потребовалось создать еще один тип, вложенный в тип myBooks, то в SQL-выражении создания подтипа вместо ключевого слова final следовало бы написать NOT FINAL.
Итак, создан суТтертип Books и его подтип myBooks. Супертип Books понадобится в дальнейшем, быть может, и для каких-то других целей, но сейчас он нам нужен как родительский тип для конкретного типа myBooks. Обратите внимание, ЧТО здесь myBooks является просто копией типа Books. При желании мы могли бы добавить в него дополнительные свойства, которых нет в супертипе, но в данном случае мы обойдемся без этого.
Создадим Таблицу, КОТОраЯ ИСПОЛЬЗует ТИП myBooks:
CREATE TABLE Книги
(
Книга myBooks, Цена NUMERIC (6,2) );
Теперь добавим новые записи в созданную таблицу книги:
BEGIN
DECLARE х myBooks; /* объявление переменной х типа myBooks*/
SET х = myBooks();/* Выполняем функцию-конструктор */
/* Вызов функций-мутаторов */
SET х = х.Title('HTML, скрипты и стили');
SET х = х .Author('Дунаев Вадим');
SET х = х.Publisher('БХВ-Петербург');
SET х =х.Year(2005);
SET x. Volume (832) ;
/* Добанление новой записи с установкой значений столбцов */
INSERT INTO Книги (х, 350.50);
END;
Здесь ключевые слова begin (начало) и end (конец) окаймляют блок SQL-команд, в котором вызываются функции-мутаторы
4 Зак. 120
для установки значений атрибутов типа данных myBook, а затем выполняется SQL-оператор insert для добавления новой записи с установкой значений столбцов. Ключевые слова begin и end окаймляют SQL-операторы так называемой составной команды (см. разд. 10.1). Комментарии заключаются в символы (/*) и (*/).
Неопределенные значения
Если в ячейку (поле) таблицы вы вводите какие-то значения (буквы, цифры, пробелы или еще что-нибудь), то эти ячейки приобретают так называемые определенные значения. С определенными значениями, принадлежащими тому или иному типу, можно выполнять какие-то операции. Однако вы можете создать запись (строку) таблицы, ничего не вводя в ее поля (столбцы). Такая запись ничего не содержит. Запись в таблице, которая создана, но в которую не введены конкретные значения, называется пустой. В каждой ячейке пустой записи содержится так называемое неопределенное значение, обозначаемое через null. Значение null понимается как неопределенное или неизвестное. Итак, каждый раз, когда вы добавляете в таблицу новую запись, не вводя в нее каких-либо конкретных значений, СУБД устанавливает в ее полях значение null.
Значение null вы можете интерпретировать так, как вам хочется, например, "еще не введено", "пока не известно". Однако независимо от смысловой интерпретации СУБД будет интерпретировать значение null вполне определенно. Задумайтесь, подойдет ли эта интерпретация к вашим конкретным целям. В обычных случаях это самая лучшая интерпретация. Но дело в том, как влияет значение типа null на результат его участия в тех или иных операциях.
Важно понимать, что число 0 или пустая строка 11 являются вполне определенными значениями и отличаются от null, хотя визуально пустая строка и неопределенное значение могут отображаться одинаково — в виде пустой ячейки таблицы. Многие функции SQL возвращают в зависимости от обстоятельств какое-то определенное значение либо null. Это необходимо учитывать при составлении SQL-выражений, поскольку комбинация null и других вполне конкретных значений может дать тот или иной результат в зависимости от конкретной операции.
Таблица базы данных может иметь некоторые записи, имеющие неопределенные значения. Такие значения появляются по различным причинам, как уже было отмечено, однако при этом могут возникать неоднозначные ситуации при извлечении данных. Чтобы их избежать, необходимо помнить, что при сравнении любого определенного значения с null результат равен false, т. е. любое определенное значение не равно null. То же самое происходит при сравнении двух величин, значениями которых являются null, т. е. сравнение двух неопределенных величин дает в результате false.
При проектировании баз данных можно указать, что некоторые столбцы не могут иметь значение null, т. е. их значения обязательно должны быть определены (при определении столбца в операторе create table указывается not null). Однако во многих случаях значения null являются очень полезными. Так, например, вы можете создать таблицу и ввести в нее сведения, которыми вы располагаете в данный момент, оставив другие значения пока неопределенными. Затем, по мере поступления новой информации, вы можете изменить значения null на те, которые стали известны.
В следующем примере из таблицы клиенты выбираются все записи, в которых не определено имя клиента:
SELECT * FROM Клиенты WHERE Имя IS NULL;
Преобразование типов
Чтобы выполнить какую-либо операцию над данными различных типов, необходимо сделать преобразование типов. Точнее, необходимо выполнить приведение данных одного типа к другому типу, чтобы участвующие в операции данные были либо однотипными, либо их типы были соответствующими.
Соответствующими типами являются:
строковые CHARACTER И CHARACTER VARYING;
все числовые типы;
дата, время, дата-время и соответствующие интервалы.
Соответствующие типы не обязательно приводить друг к другу.
Приведение значения одного типа к другому осуществляется с помощью функции cast ():
CAST (выражение AS тип) ;
Например:
CAST(’1234.521 AS NUMERIC (9,2));
CAST('2005-10-03' AS DATE);
CAST(CURRENT_TIMESTAMP (2) AS CHAR (20));
SELECT 'Цена: ' || CAST(Цена AS CHAR(5)) FROM Продажи;
В последнем примере пара вертикальных черт означает операцию конкатенации строк (см. разд. 4.3). Другие примеры использования функции cast () будут приведены в гл. 4.
Глава 3
Простые выборки данных
Предположим, что реляционная база данных, состоящая из одной или нескольких таблиц, создана и вы к ней уже подключились. В этом случае типичной практической задачей является получение (извлечение) нужных данных. Например, может потребоваться просто просмотреть все содержимое какой-либо таблицы из базы данных или некоторых ее полей. При этом, возможно, вы захотите получить не все записи, а лишь те, которые удовлетворяют заданным условиям. Однако чаще возникает более интересная и сложная задача извлечения данных сразу из нескольких таблиц. Данные из двух и более таблиц необходимо скомпоновать в одну таблицу, чтобы представить ее для обозрения, анализа или последующей обработки. Язык SQL предоставляет для этого широкие возможности, которые мы и рассмотрим в этой главе.
В результате выполнения выражения на языке SQL (SQL- выражения) создастся таблица, которая либо содержит запрошенные данные, либо пуста, если данных, соответствующих запросу, нс нашлось. Эта таблица, называемая еще результатной, существует только во время сеанса работы с базой данных и нс присоединяется к числу таблиц, входящих в базу данных. Иначе говоря, она не хранится на жестком диске компьютера подобно исходным таблицам базы данных, и поэтому се еще называют виртуальной.
Примечание
Термин "виртуальный" в современной обычной речи означает "мнимый", "возможный", т. е. не "реальный". Однако исконное значение этого слова как раз противоположное— "не номинальный", "фактический",
"реальный". Данная ситуация аналогична употреблению слова "оригинальный". Этот термин исконно означает "настоящий", "первоначальный", "подлинный", "исходный". Однако в обычной речи чаще используются значения "отличающийся", "из ряда вон выходящий", "особенный".
Выборка данных из нескольких таблиц, их обработка, а также использование подзапросов (запросов, которые нужны в качестве промежуточных для получения окончательного результата) относятся к теме сложных запросов. Сложные запросы будут рассмотрены в последующих главах, а здесь мы остановимся на задаче выборки данных из одной таблицы при относительно простых условиях отбора, группировки и сортировки записей. Тем не менее, операторы SQL, применяемые в простых запросах на выборку данных, используются и в сложных запросах, направленных не только на получение, но и на изменение данных. Начните с простого, чтобы потом было легко понять сложное. Я имею в виду, что материал данной главы относится к фундаментальным темам SQL, хотя многие пользователи баз данных могут им и ограничиться. Последнее вполне вероятно, поскольку материал этой главы сам по себе исключительно практичен.
Все SQL-выражения, предназначенные для выборки данных из существующих таблиц базы данных, начинаются с ключевого слова (оператора) SELECT (выбрать). Для уточнения запроса служат дополнительные операторы, такие как FROM (из), WHERE (где) и др. Сейчас важно понять и запомнить, что результатом выполнения запроса, сформулированного в виде SQL-выражения, является таблица, содержащая запрошенные данные. Эта таблица виртуальна в том смысле, что только представляет результаты запроса и не принадлежит к базе данных. Замечу также, что SQL позволяет изменять существующую базу данных — создавать и добавлять к ней новые таблицы, а также модифицировать и удалять уже существующие. Но об этом в следующих главах.
Основное SQL-выражение для выборки данных
Чтобы выбрать из таблицы базы данных требуемые записи, следует, по крайней мере, указать столбцы и имя этой таблицы. Это требование было бы естественно сформулировать так:
ВЫБРАТЬ такие-то столбцы ИЗ такой-то таблицы;
Разумеется, вам может потребоваться выбрать не все записи таблицы, а лишь те, которые отвечают некоторому условию. На практике именно так и бывает. Отложим пока рассмотрение формирования условий отбора записей, а сконцентрируем внимание на выборке всех записей из заданной таблицы. SQL- запрос к базе данных, результатом которого является таблица, полученная из указанной в запросе, но отличающаяся от нее тем, что содержит лишь указанные столбцы, выглядит так:
SELECT списокСтолбцов FROM списокТаблиц;
Операторы select (выбрать) и from (из) в SQL-выражении, определяющем выборку данных, являются обязательными, т. е. ни один из них нельзя пропустить. SQL-выражение, содержащее только эти операторы, является основным выражением, определяющим запрос к базе данных на выборку данных. В результате выполнения этого запроса создается виртуальная таблица, содержащая указанные столбцы и все записи исходной таблицы.
£ Примечание
Оператор select осуществляет проекцию отношения, указанного в выражении from, на заданное множество атрибутов (столбцов), указанное в выражении select. Так, например, если исходная таблица r содержит столбцы ai, а2, ..., ап (другими словами, таблица представляет некоторое отношение R^AA, А2,..., Дп) над атрибутами А1, А2,... , Ап), то оператор:
SELECT Al,A2,Ak FROM R;
реализует проекцию Я[Д1, Д2, Ak\ этого отношения на атрибуты Д1, Д2, Ак (к= 1, 2п).Понятие проекции отношения и соот
ветствующие обозначения были рассмотрены в гл. 1.
В выражении from указывается список имен таблиц базы данных, из которых требуется выбрать данные. В простейшем случае списокТаблиц содержит лишь одно имя таблицы. Если же таблиц несколько, то их имена в списке разделяются запятыми. Если в выражении from указано более одной таблицы, то результатная таблица получается из декартового произведения перечисленных в списке таблиц. Иногда это используется для специальных целей, но чаще всего в выражении from указывается только одна таблица.
Список столбцов — это перечень имен столбцов, разделенных запятой, как они определены в таблице, указанной в выражении
from. Разумеется, можно указать все или только некоторые столбцы. Если вы хотите получить все столбцы таблицы, то вместо списка столбцов достаточно указать символ (*). Если в выражении from указано несколько таблиц, то в выражении select имена столбцов должны содержать префиксы, указывающие, к какой именно таблице они относятся. Префикс отделяется от имени столбца точкой. Например, выражение клиенты.Адрес означает столбец Адрес ИЗ таблицы Клиенты.
Тривиальный запрос, возвращающий все данные (все столбцы и все записи) из одной таблицы, формулируется так:
SELECT * FROM имяТа блицы;
Основное SQL-выражение может быть дополнено другими операторами, уточняющими запрос. Более подробно они будут рассмотрены в разд. 3.2. Чаще всего употребляется оператор where (где), с помощью которого можно задать условие выборки записей (строк таблицы). Таким образом, если выражение select задает столбцы таблицы, указанной в операторе from, то выражение where определяет записи (строки) из этой таблицы. Выражение, определяющее запрос на выборку данных, находящихся в некоторой таблице, имеет следующий вид:
SELECT * FROM имя Таблицы WHERE условие Поиска ;
Условие, указанное в выражении where, принимает одно из двух логических значений: true (ИСТИНА) ИЛИ false (ЛОЖЬ). Другими словами, это логическое выражение. При обработке запроса условие проверяется для каждой записи таблицы. Если оно истинно для данной записи, то она выбирается и будет представлена в результатной таблице. В противном случае запись не выбирается и в результатную таблицу нс попадает. Если выражение where не указано в SQL-выражении, то результатная таблица будет содержать все записи из таблицы, заданной в выражении from. Таким образом, выражение where определяет фильтр записей. Фильтр что-то пропускает в результатную таблицу, а что-то отбрасывает.
|^ Примечание
Фильтр — одно из основных понятий в области работы с базами данных. В литературе иногда можно встретить различные его трактовки. Так, "отфильтрдвать записи" может означать "получить
записи", а может наоборот— "отбраковать записи". Вопрос в том, что проходит через фильтр, а что остается. В этой книге я придерживаюсь значения "получить записи". Поэтому здесь понятия "отфильтровать", "пропустить через фильтр" или "наложить фильтр" всегда означают "выбрать записи, удовлетворяющие условию фильтра".
Сразу за оператором select до списка столбцов можно применять ключевые слова all (все) и distinct (отличающиеся), которые указывают, какие записи представлять в результатной таблице. Если эти ключевые слова не используются, то подразумевается, что следует выбрать все записи, что также соответствует применению ключевого слова all. В случае использования distinct в результатной таблице представляются только уникальные записи. При этом если в исходной таблице находятся несколько идентичных записей, то из них выбирается только первая.
Примечание
В Microsoft Access кроме ключевых слов all и distinct после select можно использовать ключевое слово тор с дополнительными параметрами. Выражение тор п требует, чтобы в выборку данных попали только первые п записей, удовлетворяющих заданному условию запроса. Это ограничение условия поиска нужных записей, формулируемого в выражении where. Если исходная таблица очень большая, то distinct может ускорить получение ответа.
В Access можно использовать и выражение top n percent, чтобы указать, что л выражается в процентах от общего количества записей. Не трудно понять, что использование такого выражения направлено не на ускорение поиска, а на получение таблицы, избавленной от лишних данных.
Заголовки столбцов в результатной таблице можно переопределить по своему усмотрению, назначив для них так называемые псевдонимы. Для этого в списке столбцов после соответствующего столбца следует написать выражение вида: as заголовок—столбца. Например:
SELECT ClientName AS Клиент, Address AS Адрес FROM Клиенты;
Псевдонимы также можно задать и для каждой таблицы после ключевого слова from. Для этого достаточно указать псевдоним
через пробел сразу после имени соответствующей таблицы. Псевдонимы таблиц, более короткие, чем их имена, удобно использовать в сложных запросах. Например:
SELECT Т1.Имя, Т2.Адрес FROM Клиенты Т1, Контакты Т2;
Уточнения запроса
Основное SQL-выражение для выборки данных, описанное в разд. 3.7, имеет вид:
SELECT списокСтолбцов FROM списокТаблиц;
Такой запрос возвращает таблицу, полученную из указанной в операторе from (или из декартового произведения указанных таблиц, если их несколько), путем выделения в ней только тех столбцов, которые определены в операторе select. Я уже упоминал, что для выделения требуемых записей (строк) исходной таблицы используется выражение, следующее за ключевым словом (оператором) where. Оператор where является наиболее часто используемым, хотя и не обязательным в SQL-выражении. Именно из-за популярности его можно считать основным компонентом SQL-выражения. Кроме where, в SQL-выражениях используются и другие операторы, позволяющие уточнить запрос.
Для уточнения запроса на выборку данных служит ряд дополнительных операторов:
where (где) — указывает записи, которые должны войти в результатную таблицу (фильтр записей);
group by (группировать по) — группирует записи по значениям определенных столбцов;
having (имеющие, при условии) — указывает группы записей, которые должны войти в результатную таблицу (фильтр групп);
order by (сортировать по) — сортирует (упорядочивает) записи.
Эти операторы не являются обязательными. Их можно совсем не использовать, или использовать лишь некоторые из них, или все сразу. Если применяются несколько операторов, то в SQL- выражении они используются в указанном в списке порядке.
Таким образом, запрос данных из таблицы с применением всех перечисленных операторов уточнения запроса имеет следующий вид:
SELECT списокСтолбцов FROM имяТаблицы
WHERE условиеПоиска
GROUP BY столбецГруппировки
HAVING условиеПоиска
ORDER BY условие Сортировки;
Порядок перечисления операторов в SQL-выражении не совпадает с порядком их выполнения. Однако знание порядка выполнения операторов поможет вам избежать многих недоразумений. Итак, перечисленные операторы SQL-выражения выполняются в следующем порядке, передавая друг другу результат в виде таблицы:
from — выбирает таблицу из базы данных; если указано несколько таблиц, то выполняется их декартово произведение и результирующая таблица передается для обработки следующему оператору.
where —- из таблицы выбираются записи, отвечающие условию поиска, и отбрасываются все остальные.
group by — создаются группы записей, отобранных с помощью оператора where (если он присутствует в SQL-выражении); каждая группа соответствует какому-нибудь значению столбца группирования. Столбец группирования может быть любым столбцом таблицы, заданной в операторе from, а не только тем, который указан в select.
having — обрабатывает каждую из созданных групп записей, оставляя только те из них, которые удовлетворяют условию поиска; этот оператор используется только вместе с оператором GROUP BY.
select — выбирает из таблицы, полученной в результате применения перечисленных операторов, только указанные столбцы.
order by — сортирует записи таблицы. При этом в условии сортировки можно обращаться лишь к тем столбцам, которые указаны в операторе select.
Допустим, среди таблиц вашей базы данных имеется таблица Клиенты, которая СОДерЖИТ столбцы С именами: Имя, Адрес, Сум- ма_заказа и, возможно, какие-то другие (рис. 3.1). Семантика этой таблицы тривиальна. В ней фиксируются данные о клиентах и денежные суммы, которые они заплатили вашей фирме, пользуясь ее услугами.
О Клиенты i
>WWAV.TOWAIW^
| J Имя
у.. :> <^^<й
таблица 'у'v":' :-
AyV..W,V,V/AV.Y.-.V.V.VlVAWXw)A\W.VXf.VXWAvXwAVA'AW.V2W*V>/AVVAVA-.V/W/AVAVA\WAVA-AW.W ■ Адрес,
iWW.ViWW.V.-K-A-X'AVASWW.WWAV.V.V/'.-ZA'AVAViVAvZ-.V.W.'.W
Сумма_закзза |Регион
'■ЧДвЛ у xw.V.-•_%•.<-.vz-".e.-.vvvvv w.
1 Телефон
Иврнов
197371 г Санкт-Петербург пр. Королева, д 30. кв 777
1000 Северо-запад
111-1111
1
Петров
197371 г.Санкт-Петербург Коломяжский пр , д 14
450 Северо-запад
111-2222 .
Г
Сидоров Васильев Захаров
jМосква, ул. Тверская, д. 25, кв. 375
Селижарово. ул Укромная, д 22
Санкт-Петербург Невский пр.. д 152
3000 Москва ~
200 Тверская область 8600 Северо-запад
222-3333
555-1234
112-4321 i
Павлов
; Иркутск. ул. Ленина, д 2
.300 Иркутская область
"888-0987
1 Запись; (11]
yL - д^ОйЛЯ w ДД
^ . XWVW1aWaxv^w xxxx-rx'.'xx- . * ■
Рис. 3.1. Таблица Клиенты
Внимание
Таблица, показанная на рис. 3.1, будет использоваться в качестве исходной во всех примерах и задачах данной главы.
Предположим, нас интересуют не все данные этой таблицы, а только те, которые касаются клиентов, заплативших фирме более 500 (сейчас не важно, в какой валюте производились оплаты). Точнее, нам нужны имена и адреса клиентов, которые заплатили фирме более 500 денежных единиц. Таким образом, нам необходимо получить не все, что содержится в таблице клиенты, а лишь некоторую ее часть, как по столбцам, так и по записям. Для этой цели подойдет следующее SQL-выражение:
SELECT Имя, Адрес FROM Клиенты WHERE Сумма_заказа > 500;
Это SQL-выражение представляет собой запрос, который на естественном языке выглядит приблизительно так:
ВЫБРАТЬ СТОЛБЦЫ Имя, Адрес ИЗ ТАБЛИЦЫ Клиенты ГДЕ Сумма_заказа > 500;
Здесь из таблицы клиенты выбираются записи, в которых значение столбца сумма_заказа превышает 500. При этом в результатной таблице будут представлены только два столбца таблицы Клиенты: Имя И Адрес (рИС. 3.2).
У Суммд„эака9а>500 : запрос йа выборку :У Суммд„эака9а>500 : запрос йа выборку :АдресАдресЗахаровЗахаровиз 3из 3Запись:Запись:197371 г.Санкт-Петербург пр. Королева, д.30, кв. 777Москва, ул Тверская, д 25,кв 375Санкт-Петербург Невский пр., д 152197371 г.Санкт-Петербург пр. Королева, д.30, кв. 777Москва, ул Тверская, д 25,кв 375Санкт-Петербург Невский пр., д 152ИвановИвановРис. 3.2. Результат запроса •WHERE Сумма_заказа > 500’Рис. 3.2. Результат запроса •WHERE Сумма_заказа > 500’
У Суммд„эака9а>500 : запрос йа выборку :
У Суммд„эака9а>500 : запрос йа выборку :
Адрес
Адрес
Захаров
Захаров
из 3
из 3
Запись:
Запись:
197371 г.Санкт-Петербург пр. Королева, д.30, кв. 777
Москва, ул Тверская, д 25,кв 375
Санкт-Петербург Невский пр., д 152
197371 г.Санкт-Петербург пр. Королева, д.30, кв. 777
Москва, ул Тверская, д 25,кв 375
Санкт-Петербург Невский пр., д 152
Иванов
Иванов
Рис. 3.2. Результат запроса •WHERE Сумма_заказа > 500’
Рис. 3.2. Результат запроса •WHERE Сумма_заказа > 500’
Столбец сумма_заказа, имеющийся в исходной таблице, в данном случае не выводится (отсутствует в результирующей таблице). Впрочем, если бы вам потребовалось увидеть конкретные суммы, превышающие 500, то для этого было бы достаточно указать в списке столбцов, следующем за оператором select, еще И столбец Сумма_заказа.
Следующее SQL-выражение создает виртуальную таблицу, содержащую три столбца: Регион, Имя И Адрес. Из таблицы Клиенты выбираются только те записи, в которых сумма заказа превышает 500, И ОНИ группируются ПО значениям столбцов Регион, имя и Адрес. Это означает, что в результатной таблице записи, имеющие одинаковые значения в столбце Регион, будут расположены рядом друг с другом. Наконец, все записи в результатной таблице упорядочиваются по значениям столбца имя (рис. 3.3).
SELECT Регион, Имя, Адрес FROM Клиенты
WHERE Сумма_заказа > 500
GROUP BY Регион, Имя, Адрес ORDER BY Имя;
J3 Сумь^_заказа>50^^ с группировкой й сортировкой: запрос на выборку 1*ьЙ
РегионИмя
Адрес=
Ц_
§$3133ЭЕ?Захаров
Северо-западИванов
Санкт-Петербург Невский пр , д.152J
197371 г.Санкг-Петербург пр. Королева, д.30, кв. 777 ।
Москва; Сидоров
Москва, ул. Тверская, д 25, кв 375
| Запись; (14 | ^ |1 [У, )ГЙГ
► ИЗ з ■
Рис. 3.3. Результат запроса ’WHERE Сумма_заказа > 500’С группировкой ’GROUP BY Регион, Имя, Адрес’И сортировкой ' ORDER BY Имя ’
Операторы группировки (group by) и сортировки (order by) будут подробно рассмотрены далее в разд. 3.2.2— 3.2.4. Здесь я лишь показал на примере, что оператор выборки записей из исходной таблицы может соседствовать с другими SQL-операторами.
Оператор WHERE
Условия поиска в операторе where (где) являются логическими выражениями, т. е. принимающими одно из двух возможных значений — true (ИСТИНА) или false (ЛОЖЬ). Например, выражение сумма__заказа > 500 является истинным (имеет значение true), если в текущей записи таблицы значение столбца Сумма заказа превышает 500. В противном случае это выражение ложно (имеет значение false). Одно и то же логическое выражение может быть истинным для одних записей и ложным для других. Вообще говоря, в SQL логические выражения могут принимать еще и неопределенное значение. Это происходит тогда, когда в выражении некоторые элементы имеют значение null. Таким образом, в SQL мы имеем дело не с классической двузначной, а с трехзначной логикой.
Напомню, что выражение, следующее за оператором where, возвращает одно из трех значений: true, false или null. При выполнении запроса (SQL-выражения) логическое выражение where применяется ко всем записям исходной таблицы. Если оно истинно для данной записи исходной таблицы, то эта запись выбирается и будет представлена в результатной таблице; в противном случае запись не попадет в результатную таблицу.
При составлении логических выражений используются специальные ключевые слова и символы операций сравнения, которые называют предикатами. Например, в выражении Сумма_заказа > 500 применен предикат сравнения (>).
Внимание
Выражения с оператором where используются не только при выборке данных (т. е. с оператором select), но и при вставке, модификации и удалении записей. Таким образом, материал данного раздела имеет значение, выходящее за границы рассматриваемой темы о выборке данных.
Наиболее часто используются предикаты сравнения, такие как (=), (<), (>), (о), (<=) и (>=). Однако имеются и другие. Далее приведен список всех предикатов:
предикаты сравнения: (=), (<), (>), (<>), (<*=), (>=);
between;
in, not in;
LIKE, NOT like;
is null;
ALL, SOME, ANY;
exists;
unique;
distinct;
overlaps;
match;
SIMILAR.
Предикаты сравнения
Предикаты сравнения, называемые также операторами сравнения, перечислены в табл. 3.1.
Таблица 3.1. Предикаты сравнения
СимволСимволОписание
Символ
Символ
Равно
Не равно
Меньше
Меньше или равно (не больше)
Больше
Больше или равно (не меньше)
В обычных процедурных языках программирования перечисленные символы сравнения называются операторами сравнения, а не предикатами. Если вы ознакомились с содержанием гл. 1,
то нетрудно понять, что, например, выражение х > у, понимаемое как выражение сравнения величин х и у с использованием оператора (>) (больше), можно было бы записать как некую функцию в виде > (х, у). Поскольку эта функция возвращает логическое значение после подстановки вместо переменных х и у некоторых значений, то она и есть предикат. Выражение х > у также возвращает логическую величину после подстановки значений вместо переменных х и у. Таким образом, как бы мы ни называли все, что связано с символами сравнения, суть остается неизменной.
BETWEEN
Предикат between (между) позволяет задать выражение проверки вхождения какого-либо значения в диапазон, определяемый граничными значениями. Например:
WHERE Сумма_заказа BETWEEN 100 AND 750
Здесь ключевое слово and представляет собой логический союз И. Граничные значения (в примере это 100 и 750) входят в диапазон. Причем первое граничное значение должно быть не больше второго.
Эквивалентным приведенному является выражение с предикатами сравнения:
WHERE Сумма_заказа >= 100 AND Сумма_заказа <= 750
Кроме данных числового типа, в выражениях с beetween можно использовать данные следующих типов: символьные, битовые, даты-времени. Так например, чтобы выбрать записи, в которых имена клиентов находятся в диапазоне от А до Ж, можно использовать такое выражение:
SELECT Имя, Адрес FROM Клиенты WHERE Имя BETWEEN ’A’ AND ’Ж’;
IN и NOT IN
Предикаты in (в) и not in (не в) применяются для проверки вхождения какого-либо значения в заданный список значений. Например, для выборки записей о клиентах из некоторых регионов можно использовать такое выражение:
SELECT Имя, Адрес FROM Клиенты
WHERE Регион IN (’Северо-запад’, ’Ставропольский край', ’Иркутская область’);
Если требуется получить данные о всех клиентах не из Москвы и Северо-запада, то можно использовать предикат not in:
SELECT Имя, Адрес FROM Клиенты
WHERE Регион NOT IN (’Москва1, ’Санкт-Петербург');
LIKE v\ NOT LIKE
Предикаты like (похожий) и not like (не похожий) применяются для проверки частичного соответствия символьных строк. Например, столбец телефон в некоторой таблице содержит полные номера телефонов, а вам требуется выбрать лишь те записи, в которых номера телефонов начинаются с 348 или содержат такое сочетание цифр.
Критерий частичного соответствия задается с помощью двух символов-масок: знака процента (%) и подчеркивания (_). Знак процента означает любой набор символов, в том числе и пустой, а символ подчеркивания — любой одиночный символ.
Например, чтобы выбрать записи о клиентах, у которых номера телефонов начинаются с 348, можно использовать такое выражение:
SELECT Имя, Адрес, Телефон FROM Клиенты
WHERE Телефон LIKE '348%';
Допустим, столбец Адрес содержит полный почтовый адрес (индекс, название города, улицы и т. д.). Если вам требуется выбрать записи о клиентах, проживающих в Санкт-Петербурге, то для этого подойдет следующее выражение:
SELECT Имя, Адрес, Телефон FROM Клиенты
WHERE Адрес LIKE ’%Санкт-Петербург%';
Если вы хотите исключить всех клиентов, проживающих в Москве, то воспользуйтесь таким выражением:
SELECT Имя, Адрес, Телефон FROM Клиенты
WHERE Адрес NOT LIKE '%Москва%';
Возможно, вам потребуется выбрать записи, содержащие символы процента и/или подчеркивания. Тогда необходимо, чтобы такие символы воспринимались интерпретатором SQL не как символы-маски. Чтобы знак процента или подчеркивания воспринимался буквально, перед ним необходимо указать специальный управляющий символ. Этот символ можно определить
произвольно, лишь бы он не встречался в качестве элемента данных. В следующем примере показано, как это можно сделать:
SELECT Имя, Адрес, Процент_скидки FROM Клиенты
WHERE Процент_скидки LIKE ’20#%’
ESCAPE •#•;
Здесь за ключевым словом escape указывается символ, который используется в качестве управляющего. Таким же способом можно отключить и сам управляющий символ,
IS NULL
Предикат is null применяется для выявления записей, в которых тот или иной столбец не имеет значения. Например, для получения записей о клиентах, для которых не указан адрес, можно использовать следующее выражение:
SELECT Имя, Адрес, Регион FROM Клиенты
WHERE Адрес IS NULL;
Для получения записей, в которых столбец Адрес содержит некоторые определенные значения (т. е. отличные от null), можно использовать аналогичное выражение, но с логическим оператором not (не):
SELECT Имя, Адрес, Регион FROM Клиенты
WHERE Адрес IS NOT NULL;
Не следует использовать предикаты сравнения с null, такие как Адрес = NULL.
Предикаты для вложенных запросов
В выражении where, кроме перечисленных предикатов, могут также использоваться выражения с оператором select. Любое выражение, начинающееся с оператора select, является запросом к базе данных. Если в выражении встречается еще хотя бы один оператор select, то он задает запрос, вложенный в первый. Вложенные запросы также называют подзапросами.
Вложенный запрос является обычным запросом, таким же, как и рассмотренные ранее. Он возвращает таблицу (набор записей), которая, так или иначе, используется для формирования ответа на основной запрос. Так, например, подзапрос используется, когда для выборки данных в одной таблице необходимо выпол-
нить проверки по другой таблице. Для этой цели подходят перечисленные далее специальные предикаты. В этой главе я лишь отмечу их основные характеристики (для справки), варианты применения будут рассмотрены в гл. 5.
ALL, SOME, ANY
Предикаты all (все), some (некоторый), any (любой) в действительности представляют собой кванторы, известные в математической логике как кванторы всеобщности и существования. all — квантор всеобщности, a some и any, являющиеся синонимами в SQL, — кванторы существования. Заметим, что в переводе на русский слово any следовало бы понимать как квантор всеобщности ("любой" означает "все"), однако в английском языке есть различные варианты значений этого слова.
Применение ключевого слова all следует понимать как "для всех" или "для каждого". Ключевые слова some и any следует понимать как "хотя бы какой-нибудь один".
Как бы то ни было, в языке SQL ключевые слова some и any имеют одинаковый смысл, отличающийся от all. Примеры запросов с кванторами будут рассмотрены в разд. 5.1.
Примечание
Выражения с ключевыми словами all, some (any) соответствуют логическим выражениям с кванторами и, как таковые, могут называться предикатами.
EXISTS
Обработка данных часто состоит из нескольких этапов. Так, сначала производится некоторая выборка данных, а затем выполняются какие-то манипуляции с ней. Однако, выполняя запрос на выборку, мы далеко не всегда можем быть уверенными, что ответ содержит хотя бы одну непустую строку. Если ответ на запрос пуст, то бессмысленно производить дальнейшую обработку данных. Таким образом, полезно знать, содержит ли ответ на запрос какие-либо данные. Для этого предназначен предикат exists (существует). Он становится истинным только тогда, кода результатная таблица, полученная в ответ на запрос, содержит хотя бы одну запись. Пример запроса с предикатом exists будет рассмотрен в разд. 5.1.
UNIQUE
Предикат unique (уникальный) имеет такой же смысл, как и exists, но при этом для его истинности требуется, чтобы все записи в результатной таблице не только существовали, но и были уникальны (т. е. не повторялись).
DISTINCT
Предикат distinct (отличающийся, особый) почти такой же, как и unique. Отличие этих предикатов обнаруживается применительно к значениям null. Так, если в результатной таблице все записи уникальны (предикат unique истинен), то и предикат distinct тоже истинен (т. е. если все записи уникальны, то они и отличающиеся). С другой стороны, если в результатной таблице имеются хотя бы две неопределенные записи, то предикат distinct ложен, хотя предикат unique истинен.
OVERLAPS
Предикат overlaps (перекрывает) используется для определения, перекрываются ли два интервала времени. Интервал времени можно задать двумя способами: в виде начального и конечного моментов или в виде начального момента и длительности. Далее приведены примеры задания интервала времени:
(time '12:25:30', time 114:зо:оо1) — интервал, заданный начальным и конечным моментами;
(TIME ' 12:45:00', INTERVAL '2* HOUR) — Интервал, ЗЗДан- ный начальным моментом и длительностью в часах.
Выражение с предикатом overlaps можно записать, например, так:
(TIME '12:25:30', TIME '14:30:00') OVERLAPS (TIME '12:45:00', INTERVAL '2' HOUR)
Поскольку временные интервалы в данном примере пересекаются, то предикат overlaps возвращает значение true.
Подробнее об интервалах было рассказано в разд. 2.2.5.
MATCH
Предикат match применяется для проверки сохранения ссылочной целостности при модификации данных, т. е. при добавлении, изменении и удалении записей (см. разд. 6.4).
SIMILAR
Предикат similar (подобный) применяется для проверки частичного соответствия символьных строк. Эту же задачу можно решить и с помощью предиката like, однако в ряде случаев similar более эффективен.
Предположим, что в некоторой таблице имеется столбец ос, содержащий названия операционных систем. Нужно выбрать записи, соответствующие Windows NT, Windows ХР и Windows 98. Тогда в выражении запроса можно использовать такой оператор where:
WHERE ОС SIMILAR ТО '(Windows (NT|ХР|98))*;
Предикат впервые появился в SQL: 1999.
Оператор GROUP BY
Оператор group by (группировать по) служит для группировки записей по значениям одного или нескольких столбцов. Если в SQL-выражении используется оператор where, задающий фильтр записей, то оператор group by находится и выполняется после него. Для определения, какие записи должны войти в группы, служит оператор having, используемый совместно с group by (см. разд. 3.2.3). Если оператор having не применяется, то группировке подлежат все записи, отфильтрованные оператором where. Если where не используется, то группируются все записи исходной таблицы. О порядке выполнения операторов уже говорилось в начале разд. 3.2.
Допустим, что на основе таблицы о клиентах (см. рис. 3.1) требуется сгруппировать данные о суммах заказов клиентов по регионам. Для этого можно воспользоваться следующим SQL- выражением:
SELECT Регион, Сумма_заказа FROM Клиенты
GROUP BY Регион;
На рис. 3.4 показана результатная таблица на фоне исходной таблицы Клиенты. Обратите внимание, что записи с одинаковыми названиями регионов расположены рядом друг с другом (в одной группе).
Рис. 3.4. Результат запроса сумм заказов с группировкой по регионам
Рис. 3.5. Результат запроса итоговых сумм заказов по регионам
Чтобы получить таблицу, в которой суммы заказов подытожены по регионам, потребуется использовать итоговую функцию sumo (см. разд. 4.1) и группировку по регионам:
SELECT Регион, SUM(Сумма_заказа) FROM Клиенты GROUP BY Регион;
Здесь в выражении select указаны обычный столбец таблицы клиенты и итоговая функция sumo, вычисляющая сумму значений столбца сумма заказа. Поскольку группировка задана по столбцу Регион, то функция sum(Сумма_заказа) вычисляет суммы значений столбца Сумма заказа для каждого значения столбца Регион. На рис. 3.5 показана результатная таблица
на фоне исходной таблицы клиенты. Обратите внимание, что в этой таблице названия регионов не повторяются.
Оператор group by собирает записи в группы и упорядочивает (сортирует) группы по алфавиту (точнее, по ASCII-кодам символов). Это обстоятельство следует иметь в виду перед тем, как принять решение об использовании оператора сортировки order by (см. разд. 3.2.4).
Оператор HAVING
Оператор having (имеющие, при условии) обычно применяется совместно с оператором группировки group by и задает фильтр записей в группах. Правила его формирования такие же, что и для оператора where.
Предположим, что из таблицы клиенты требуется выбрать данные о регионах и суммах заказов, сгруппированные по регионам и такие, в которых сумма заказа превышает 500. Иначе говоря, требуется сгруппировать данные с ограничением записей, входящих в группы. Запрос, выполняющий это задание, имеет вид:
SELECT Регион, Сумма_заказа FROM Клиенты
GROUP BY Регион, Сумма заказа
HAVING Сумма_заказа > 500;
Результат выполнения этого запроса представлен на рис. 3.6.
у
Kwhw-h^w--
. .WbWVWW»-.VAVAy^:AW.W.V.WiWAV. .V/V/AW.SW.V.WM
. Имя 1 .
V.V.WAW.'V^^VUVb.'V»VlAW.V*,WWWWV.W^V^WVwA4VWliwiVwVeWWVWWAV.VW*VA'V.V.V.-.-.V.1.SV>.SV.SV.V. .. е. .VA¥,,W.W.V.V;.V.V.W-A- .•.". .•Z.V. . "О'А »W^
Адрес ‘ •?.••.| Сумма_ззказа |Регион| Телефон-
а
Г* ■
Иванов197371 г Санкт Петербург пр Королева,д.30,кв 777 ;1000 Северо-запад111 1111
; Петров197371 г Санкт-Петербург Коломяжскийпр , д14450 Северо-запад111-2222
Сидоров Москва, ул. Тверская, д. 25, кв. 3753000:Москва222-3333
: ВасильевСелижарово, ул. Укромная, д. 22200 Тверская область 555-1234
ЗахаровСанкт-Петербург Невский пр., д. 1528600- Северо-запад112-4321j
; Павлов Иркутск, ул. Ленина, д.2
gg^gg^gg^gg^^'$ Г руппировка по регионам$ запрос на быбо-рку :
||||||^^
I Запись; Q4j 1 |1 Е®
J__^^_JlC£^^
□ Москва!
Северо-запад ;1000ЖИ® "ЗЖ®^
Северо-запад0ОН
^RgHdttAooobowo^xxAMOfrwAXACC-cwywwiWWWWweoafre-sww^^|•
Запись. - (.К) ^ |1 £kJ(H^ * • ИЗ 3
Рис. 3.6. Результат запроса с группировкой по регионам
и ограничением по суммам заказов
Если в SQL-выражении оператора group by нет, то оператор having применяется ко всем записям, возвращаемым оператором where. Если же отсутствует и where, то having действует на все записи таблицы.
Оператор ORDER BY
Оператор order by (сортировать по) применяется для упорядочивания (сортировки) записей. Если он используется в запросе, то в самом конце запроса. Этот оператор сортирует строки всей таблицы или отдельных ее групп (в случае применения оператора group by). Если в выражении запроса оператора group by нет, то оператор order by рассматривает все записи таблицы как одну группу.
Вслед за ключевым словом order by указывается столбец, по значениям которого следует произвести сортировку. После имени столбца можно указать ключевое слово, задающее порядок (режим) сортировки:
asc — по возрастанию {ascending). Это значение принято по умолчанию, поэтому если необходима сортировка, например, в алфавитном порядке, то специально указывать порядок не требуется;
DESC — по убыванию {descending).
Если в выражении order by указаны несколько столбцов сортировки, то сначала записи упорядочиваются по значениям первого столбца, затем для каждого значения первого столбца записи упорядочиваются по значениям второго столбца и т. д. Столбцы в списке разделяются, как обычно, запятыми. Таким образом, создается иерархическая система сортировки записей результатной таблицы.
В следующем примере данные исходной таблицы клиенты сортируются по регионам и по именам клиентов (рис. 3.7). При этом сортировка по именам клиентов производится по убыванию, т. е. в порядке, противоположном алфавитному.
SELECT * FROM Клиенты
ORDER BY Регион, Имя DESC;
:> ikklk Ы ) <<>.# :?kk f■,::
Имя.;'Адрес .?] Сумма_закаэа П" Регион ] Телефон
i ►
Иванов
197371 г Санкт-Петербург пр. Королева, д 30, кв 7771C00i Северо-запад
111-1111
Петров
197371 г.Санкт-Петербург Коломяжский пр., д.14450:Северо-запад
111-2222
Сидоров Москва, ул. Тверская, д 25, кв. 3753000 Москва
222-3333 •
Васильев Селижарово. ул. Укромная, д 22200 Тверская область
555-1234
Захаров
Санкт-Петербург Невский пр , д 1528600; Северо-запад
112-4321
Г*
Павлов ня
Иркутск, ул. Ленина, д.2300: Иркутская область 888-0987
Сортирувкй запрос на выборку "В Д
>>>WC-X<<K><>«$0tt->tt<«"X<<4>yX'>C~<0CKii>>.-W^>>>J<>X<<4<<<*X^4<*>>>>^
. Регион : I Имя ] .' Адрес• | :£умма_закэ^^^
rt
Иркутская область Павлов Иркутск, ул. Ленина, д.2
300; ;Ш
г
Москва:Сидоров : Москва, ул. Тверская, д. 25, кв. 375
3000 Й
Северо-западПетров 197371 г Санкт-Петербург Коломяжский пр., д 14
450
■
i
Северо-западИванов 197371 г.Санкт-Петербург пр Королева д 30, кв. 777
100(|
г.
Северо-западЗахаров Санкт-Петербург Невский пр , д. 152
8№ :|
■■
в
Тверская область Васильев Селижарово, ул. Укромная, д. 22
200^:0
Звпись | К |: 4 |1Из 6
4$$$S8ms$8S$S&iRS$$S$SSS$$3S$SS$S8i&S8i^SSS8$^^ksii8^^
; Запись: I И
Рис. 3.7. Результат сортировки по регионам в алфавитном порядкеи по именам клиентов в обратном порядке
Логические операторы
Логические выражения в операторах where и having могут быть сложными, т. е. состоять из двух и более простых выражений, соединенных между собой логическими операторами (союзами) and и/или or. Оператор and выполняет роль логического союза И, а оператор or — союза ИЛИ. Так, если х и у — два логических выражения, то составное выражение х and у принимает значение true (ИСТИНА) только тогда, когда х и у одновременно истинны; в противном случае выражение х and у принимает значение false (ЛОЖЬ). Выражение х or у истинно, если хотя бы одно из выражений, х или у, истинно; если х и у одновременно ложны, то составное выражение х or у ложно.
Логический оператор not применяется к одному выражению (возможно и к сложному), расположенному справа от него. Этот оператор меняет значение выражения на противоположное. Так, если выражение х имеет значение true, то выражение not х имеет значение false, и, наоборот, если х ложно, то not х истинно.
Предположим, из таблицы клиенты (см. рис. 3.1) требуется выбрать записи о клиентах из Москвы и Северо-запада. Соответствующий запрос имеет вид:
SELECT Регион, Имя, Сумма_заказа FROM Клиенты
WHERE Регион=1Москва1 OR Регион=1 Северо-запад';
Обратите внимание, что здесь используется логический оператор OR (ИЛИ), а не and (И), поскольку нам нужны клиенты, проживающие или в Москве, или на Северо-западе. Если бы вместо оператора ок мы применили and, то получили бы пустую таблицу, т. к. в исходной таблице нет ни одной записи, в которой один и тот же столбец имел бы различные значения.
Внимание
Будьте внимательны при формулировке запроса на естественном языке и при его переводе на SQL
Следующее SQL-выражение эквивалентно рассмотренному ранее. Оно основано на применении оператора in (см. разд. 3.2.1)*.
SELECT Регион, Имя, Сумма_заказа FROM Клиенты
WHERE Регион IN (’Москва*, ’Северо-запад’);
Если требуется получить данные о всех клиентах, которые не проживают ни в Москве, ни на Северо-западе, то можно использовать такое SQL-выражение:
SELECT Регион, Имя, Сумма_заказа FROM Клиенты
WHERE NOT (Регион=’Москва’ OR Регион=’Северо-запад’);
Это выражение эквивалентно следующим двум:
SELECT Регион, Имя, Сумма_заказа FROM Клиенты
WHERE Регион о ’Москва’ AND Регион <> ’Северо-запад’;
и:
SELECT Регион, Имя, Сумма_заказа FROM Клиенты
WHERE Регион NOT IN (’Москва’, ’Северо-запад’);
3.3. Задачи
Выберите в качестве исходной таблицу Клиенты, показанную на рис. 3.1. Вы можете не копировать ее содержимое в точности, а создать похожую таблицу самостоятельно. Важно, чтобы в таблице имелись символьные (текстовые) и числовые столбцы. Хорошо, если некоторые столбцы имели бы одинаковые значения, например, столбец Регион. В предлагаемых далее задачах требуется сформировать SQL-выражения, обеспечивающие некоторую выборку записей.
При формировании условий поиска (выборки) записей старайтесь абстрагироваться от конкретных данных в имеющейся таблице, поскольку пользователь может добавлять новые записи и модифицировать уже имеющиеся, делая это так, как ему хочется и как позволяют ограничения для данной таблицы. Например, столбец Адрес имеет символьный тип. Это означает, что он содержит произвольные символьные данные, структура которых поддерживается пользователем на семантическом уровне. В полях данного столбца может присутствовать или нет почтовый индекс, перед названием улицы может быть указано "ул." или "улица", или может быть ничего не указано, номер телефона может иметь различную значность, содержать или не содержать код региона, дефисы и т. п. При формулировании запроса в подобных ситуациях следует учесть как можно больше семантических нюансов.
Задача 3.1
Выберите записи, сгруппированные по регионам и исключающие Северо-западный регион. Попробуйте сделать это по крайней мере двумя способами (с использованием where и having).
Задача 3.2
Выберите записи о клиентах, проживающих в городах, название которых оканчивается на "бург", а сумма заказа превышает 2000.
Задача 3.3
Выберите записи, в которых номера телефонов пятизначные. При этом следует предусмотреть, что номера телефонов могут содержать, а могут и не содержать дефисы, а сам номер может содержать или нет код региона, заключенный или не заключенный в круглые скобки.
Глава 4
Вычисления
В выражениях SQL-запросов нередко требуется выполнить предварительную обработку данных. С этой целью используются специальные функции и выражения.
Итоговые функции
Довольно часто требуется узнать, сколько записей соответствует тому или иному запросу, какова сумма значений некоторого числового столбца, его максимальное, минимальное и среднее значения. Для этого служат так называемые итоговые (статистические, агрегатные) функции. Итоговые функции обрабатывают наборы записей, заданные, например, выражением where. Если их включить в список столбцов, следующий за оператором select, то результатная таблица будет содержать не только столбцы таблицы базы данных, но и значения, вычисленные с помощью этих функций. Далее приведен список итоговых функций.
count (параметр) — возвращает количество записей, указанных в параметре. Если требуется получить количество всех записей, то в качестве параметра следует указать символ звездочки (*). Если в качестве параметра указать имя столбца, то функция вернет количество записей, в которых этот столбец имеет значения, отличные от null. Чтобы узнать, сколько различных значений содержит столбец, перед его именем следует указать ключевое слово distinct.
Например:
SELECT COUNT(*) FROM Клиенты;
SELECT COUNT (Сумма_заказа) FROM Клиенты;
SELECT COUNT(DISTINCT Сумма_заказа) FROM Клиенты;
Попытка выполнить следующий запрос приведет к сообщению об ошибке:
SELECT Регион, COUNT(*) FROM Клиенты;
sum (параметр) — возвращает сумму значений указанного в параметре столбца. Параметр может представлять собой и выражение, содержащее имя столбца.
Например:
SELECT SUM(Сумма_заказа) FROM Клиенты;
Данное SQL-выражение возвращает таблицу, состоящую из одного столбца и одной записи и содержащую сумму всех определенных значений столбца Сумма_заказа из таблицы Клиенты.
Допустим, что в исходной таблице значения столбца сумма_заказа выражены в рублях, а нам требуется вычислить общую сумму в долларах. Если текущий обменный курс равен, например, 27,8, то получить требуемый результат можно с помощью выражения:
SELECT SUM(Сумма_заказа*27.8) FROM Клиенты;
avg (параметр) — возвращает среднее арифметическое всех значений указанного в параметре столбца. Параметр может представлять собой выражение, содержащее имя столбца.
Например:
SELECT AVG(Сумма_заказа) FROM Клиенты;
SELECT AVG(Сумма_заказа*27.8) FROM Клиенты
WHERE Регион о ’СеверО-Запад’;
мах (параметр) — возвращает максимальное значение в столбце, указанном в параметре. Параметр может также представлять собой выражение, содержащее имя столбца.
Например:
SELECT МАХ(Сумма_заказа) FROM Клиенты;
SELECT МАХ(Сумма_заказа*27.8) FROM Клиенты WHERE Регион о 'СеверО-Запад';
min (параметр) — возвращает минимальное значение в столбце, указанном в параметре. Параметр может представлять собой выражение, содержащее имя столбца.
Например:
SELECT MIN(Сумма_заказа) FROM Клиенты;
SELECT MIN(Сумма_заказа*27.8) FROM Клиенты WHERE Регион о 'СеверО-Запад1;
На практике нередко требуется получить итоговую таблицу, содержащую суммарные, усредненные, максимальные и минимальные значения числовых столбцов. Для этого следует использовать группировку (group by) и итоговые функции. В разд. 3.2.2 был рассмотрен пример простой итоговой таблицы, полученной на основе таблицы клиенты (см. рис. 3.1):
SELECT Регион, SUM(Сумма_заказа) FROM Клиенты
GROUP BY Регион;
Результатная таблица для данного запроса содержит имена регионов и итоговые (общие) суммы заказов всех клиентов из соответствующих регионов (рис. 3.5).
Теперь рассмотрим запрос на получение всех итоговых данных по регионам:
SELECT Регион, SUM (Сумма_заказа) , AVG(Сумма_заказа), МАХ (Сумма_заказа), MIN (Сумма_заказа)
FROM Клиенты
GROUP BY Регион;
Исходная и результатная таблицы показаны на рис. 4.1. В данном примере только северо-западный регион представлен в исходной таблице более чем одной записью. Поэтому в результатной таблице для него различные итоговые функции дают различные значения.
При использовании итоговых функций в списке столбцов в операторе select заголовки соответствующих им столбцов в результатной таблице имеют ВИД ExprlOOl, Ехрг1002 и т. д. (или что- нибудь аналогичное, в зависимости от реализации SQL). Однако заголовки для значений итоговых функций и других столбцов вы можете задавать по своему усмотрению. Для этого достаточно после столбца в операторе select указать выражение вида:
AS заголовок столбца
«да Я
Запись: [ И | < Г*~
Иркутск, ул Ленина, д ?300 Иркутская область 688-0987
■№№RW£ ^ ^6ГМ по pe r йо том: запрос на выборку'" ■ "$" '^ ?;’ ^••^ ?^""[»... D.|
Рис. 4.1. Итоговая таблица сумм заказов по регионам
—
Имя
Адрес
| Сумма_ззказа |Регион
| Телефон ]
Иванов
197371 г.Санкт Петербург пр Королева, д 30, кв. 777
1000 Северо-запад
111-1111
; Петров
197371 г. Санкт-Петербург Коломяжский пр . д 14
450 Северо-запад
111-2222
;Сидоров
Москва, ул Тверская, д 25,кв 375
3000 Москва
222-3333
Васильев
Селижарово, ул Укромная, д. 22
200 Тверская область
555 1234'
Захаров ,
Санкт-Петербург Невский пр;| д.152
8600 Северо-запад
112 4321
Павлов
Регион
ExprlOO
Ехрг1002 | ЕхрИООЗ |
Ехрг10С4 Ц
I ►
Иркутская область
300
300300
зоо||
Москва
3000
30003000
зооо|;‘
Северо-запад
10050
3350:8600
450||
Тверская область
_200 _
200: _200
200||
| Запись: 11< |
4
область 888-0987
Запись.
Рис. 4.2. Итоговая таблица сумм заказов по регионам с применением псевдонимов столбцов
Имя |Адрес
| Сумма_закаэа |Регион
) Телефон |
i ►
Иванов 197371 г.Санкт Петербург пр Королева, д 30. кв. 777
1000 Севере запад
111 1111
: Петров 197371 г.Санкт Петербург Коломяжский пр , д 14
; Сидоров Москва, ул Тверская, д 25, кв 375
450. Северо-запад
3000 Москва
111-2222
222-3333■
Васильев Селижарово. ул Укр мная.д 22
Захаров • СанкгПетербург Невский пр., д 152
200 Тверская область __ 8600 Се в еро з а п ад
555-1234
J112-4321.
Павлов
5
Ито г и ■ то ре г ио нам
Mfr6wfr^№X4M<vW
Регион
f запрос на выборку.:.
■VvXNWWA-XriAWA-X^XtAvX-XV/v^XV^MQMw^w^yXwW.vwwXw#*^Жrt^мw.r#XKA•AXXW^^<ww^wXvMrw^- я
Общая сумма заказа | Средняя сумма заказа | Максимум | Минимум
I
Иркутская область Москва Северо-запад Тверская область
300300300300|
3000 300Q3000:30001: 100503350:86004501
200200:2002001
| Запись: 114 j * [
Ленина, д.2
Ключевое слово as (как) означает, что в результатной таблице соответствующий столбец должен иметь заголовок, указанный после as. Назначаемый заголовок еще называют псевдонимом. В следующем примере (рис. 4.2) задаются псевдонимы для всех вычисляемых столбцов:
SELECT Регион,
SUM (Сумма_заказа) AS
[Общая сумма заказа],
AVG (Сумма_заказа) AS
[Средняя сумма заказа],
МАХ (Сумма_заказа) AS Максимум,
MIN (Сумма_заказа) AS Минимум
FROM Клиенты
GROUP BY Регион;
Псевдонимы, состоящие из нескольких слов, разделенных пробелами, заключаются в квадратные скобки.
Итоговые функции можно использовать в выражениях select и having, но их нельзя применять в выражении where. Оператор having аналогичен оператору where, но в отличие от where он отбирает записи в группах.
Допустим, требуется определить, в каких регионах более одного клиента. С этой целью можно воспользоваться таким запросом:
SELECT Регион, Count(*)
FROM Клиенты
GROUP BY Регион HAVING COUNT(*) > 1;
Функции обработки значений
При работе с данными часто приходится их обрабатывать (преобразовывать к нужному виду): выделить в строке некоторую подстроку, удалить ведущие и заключительные пробелы, округлить число, вычислить квадратный корень, определить текущее время и т. п.
В SQL имеются следующие три типа функций:
строковые функции;
числовые функции;
функции даты-времени.
Строковые функции
Строковые функции принимают в качестве параметра строку и возвращают после ее обработки строку или null.
□ SUBSTRING (строка FROM начало [FOR длина]) — возвращает подстроку, получающуюся из строки, которая указана в качестве параметра строка. Подстрока начинается с символа, порядковый номер которого указан в параметре начало и имеет длину, указанную в параметре длина. Нумерация символов
5 Зак. 120
строки ведется слева направо, начиная с 1. Квадратные скобки здесь указывают лишь на то, что заключенное в них выражение нс является обязательным. Если выражение for длина не используется, то возвращается подстрока от начало и до конца исходной строки. Значения параметров начало и длина должны выбираться так, чтобы искомая подстрока действительно находилась внутри исходной строки. В противном случае функция substring вернет null.
Например:
SUBSTRING (’Дорогая Маша!’ FROM 9 FOR 4) — ВОЗВращаеТ 'Маша';
SUBSTRING ('Дорогая Маша!' FROM 9)— ВОЗВращаеТ 'Маша! ';
SUBSTRING ('Дорогая Маша!' FROM 15) — ВОЗВращаеТ NULL.
Использовать эту функцию в SQL-выражении можно, например, так:
SELECT * FROM Клиенты
WHERE SUBSTRING(Регион FROM 1 FOR 5) = 'Север';
□ upper (строка) — переводит все символы указанной в параметре строки в верхний регистр.
□ lower (строка) — переводит все символы указанной в параметре строки в нижний регистр.
□ TRIM (LEADING | TRAILING I BOTH [’символ’] FROM строка) — удаляет ведущие (leading), заключительные (trailing) или те и другие (both) символы из строки. По умолчанию удаляемым символом является пробел (’ ’), поэтому его можно не указывать. Чаще всего эта функция используется именно для удаления пробелов.
Например:
TRIM (LEADING ’ ’ FROM ’город Санкт-Петербург ’) — ВОЗ-
вращает ’город Санкт-Петербург •;
TRIM (TRALING ’ ’ FROM ’город Санкт-Петербург ’) — воз
вращает ’ город Санкт-Петербург’;
TRIM (BOTH ’ ’ FROM ’ город Санкт-Петербург ’) — ВОЗВра- щает ’город Санкт-Петербург’;
TRIM (BOTH FROM ' город Санкт-Петербург ’) — возвращает ’город Санкт-Петербург1;
TRIM (BOTH ’г* FROM ’город Санкт-Петербург1) — возвращает ’ород Санкт-Петербур1.
Среди этих функций наиболее часто используемые — это SUBSTRING() И TRIM() .
Числовые функции
Числовые функции в качестве параметра могут принимать данные не только числового типа, но возвращают всегда число или null (неопределенное значение).
□ POSITION (целеваяСтрока IN строка) — ищет ВХОЖДение Це- левой строки в указанную строку. В случае успешного поиска возвращает номер положения ее первого символа, иначе — 0. Если целевая строка имеет нулевую длину (например, пустая строка 1 ’), то функция возвращает 1. Если хотя бы один из параметров имеет значение null, то возвращается null. Нумерация символов строки ведется слева направо, начиная с 1. Например:
position (’е’ in ’Привет всем’) — возвращает 5;
POSITION (’всем’ IN ’Привет всем’) — возвращает 8;
position (’’ in ’привет всем’) — возвращает!;
POSITION (’Привет! ’ IN ’Привет всем’) — возвращает 0.
В таблице клиенты (см. рис. 3.1) столбец Адрес содержит, кроме названия города, почтовый индекс, название улицы и другие данные. Возможно, вам потребуется выбрать записи о клиентах, проживающих в определенном городе. Так, если требуется выбрать записи, относящиеся к клиентам, проживающим в Санкт-Петербурге, то можно воспользоваться следующим выражением SQL-запроса:
SELECT * FROM Клиенты
WHERE POSITION(’Санкт-Петербург’ IN Адрес) > 0;
Заметим, что этот простой запрос на выборку данных можно сформулировать иначе:
SELECT * FROM Клиенты
WHERE Адрес LIKE ’%Петербург%’;
□ extract (параметр) — извлекает элемент из значения типа дата-время или из интервала.
Например:
EXTRACT (MONTH FROM DATE 12005-10-25') —ВОЗВращаеТ 10.
character-Length(строка) — возвращает количество символов в строке.
Например:
CHARACTER_LENGTH(’Привет всем’) — ВОЗВращаеТ 11.
octet-Length (строка) — возвращает количество октетов (байтов) в строке. Каждый символ латиницы или кириллицы представляется одним байтом, а символ китайского алфавита — двумя байтами.
cardinality (параметр) — принимает в качестве параметра коллекцию элементов и возвращает количество элементов в коллекции (кардинальное число). Коллекция может быть, например, массивом или мультимножеством, содержащим элементы различных типов.
abs (число) — возвращает абсолютное значение числа.
Например:
abs(-123) — возвращает 123;
abs (2 - 5) — возвращает 3.
□ mod (число 1, число2) — возвращает остаток от целочисленного деления первого числа на второе.
Например:
mod (5, 3) — возвращает 2;
mod (2, з) — возвращает 0.
ьп(чгисло) — возвращает натуральный логарифм числа.
ехр (число) — возвращает ечисло (основание натурального логарифма в степени число).
POWER (число!, ЧИСЛО2) — ВОЗВращаеТ число 1число2 (число! в степени число2).
sqrt (число) — возвращает квадратный корень из числа.
floor (число) — возвращает наибольшее целое число, не превышающее заданное параметром (округление в меньшую сто- рону).
Например:
floor (5.123) — возвращает 5.0.
□ ceil (число) или ceiling (число) — возвращает наименьшее целое число, которое не меньше заданного параметром (округление в большую сторону).
Например:
ceil(5.123) — возвращает 6.о.
WIDTH_BUCKET (число!, число2, числоЗ, число4) — ВОЗВра- щает целое число в диапазоне между 0 и число4 + 1. Параметры число2 и числоЗ задают числовой отрезок, разделенный на равновеликие интервалы, количество которых задается параметром число4. Функция определяет номер интервала, в который попадает значение число 1. Если число 1 находится за пределами заданного диапазона, то функция возвращает 0 или число4 + 1.
Например:
width-Bucket(3.14, о, 9, 5) — возвращает 2.
Функции даты-времени
В языке SQL имеются три функции, которые возвращают текущие дату и время.
current date — возвращает текущую дату (тип date). Например, 2005-06-18.
current time (число) — возвращает текущее время (тип time). Целочисленный параметр указывает точность представления секунд. Например, при значении 2 секунды будут представлены с точностью до сотых (две цифры в дробной части): 12:39:45.27.
current_timestamp( число) — возвращает дату и время (тип timestamp). Например, 2005-06-18 12:39:45.27. Целочисленный параметр указывает точность представления секунд.
Обратите внимание, что дата и время, возвращаемые этими функциями, имеют не символьный тип. Если требуется представить их в виде символьных строк, то для этого следует использовать функцию преобразования типа cast ().
Функции даты-времени обычно применяются в запросах на вставку, обновление и удаление данных. Например, при записи сведений о продажах в специально предусмотренный для этого столбец вносятся текущие дата и время. После подведения итогов за месяц или квартал, данные о продажах за отчетный период можно удалить.
Данные типа даты-времени подробно были рассмотрены в разд. 2.2.4, 2.2.5.
Вычисляемые выражения
Вычисляемые выражения строятся из констант (числовых, строковых, логических), функций, имен полей и данных других типов путем соединения их арифметическими, строковыми, логическими и другими операторами. В свою очередь, выражения могут быть объединены посредством операторов в более сложные (составные) выражения. Для управления порядком вычисления выражений используются круглые скобки.
Логические операторы and, or и not были рассмотрены в разд. 3.2.5, а функции — в разд. 4.1, 4.2.
Арифметические операторы приведены далее:
+ — сложение;
вычитание;
* — умножение;
/ — деление.
Строковый оператор только один — оператор конкатенации или склейки строк (| |). В некоторых реализациях SQL (например, в Microsoft Access) вместо (| |) используется символ (+). Оператор конкатенации приписывает вторую строку к концу первой. Например, выражение:
’Саша’ || ’любит’ || ’ Машу’
вернет в качестве результата строку ’ сашалюбит машу ’.
При составлении выражений необходимо следить, чтобы операнды операторов имели допустимые типы. Например, выражение:
123 + ’Саша*
недопустимо, поскольку арифметический оператор сложения применяется к строковому операнду.
Вычисляемые выражения могут находиться после оператора select, а также в выражениях условий операторов where и having.
Рассмотрим несколько примеров.
Пусть таблица Продажи содержит столбцы Тип_товара, Количество и цена, а нам требуется знать выручку для каждого типа товара. Для этого достаточно в список столбцов после оператора SELECT ВКЛЮЧИТЬ Выражение Количество*Цена*.
SELECT Тип_товара, Количество, Цена, Количество*Цена AS Итого FROM Продажи;
Здесь используется ключевое слово as (как) для задания псевдонима столбца с вычисляемыми данными.
На рис. 4.3 показаны исходная таблица продажи и результатная таблица запроса.
Рис. 4.3. Результат запроса с вычислением выручки по каждому типу товара
Если требуется узнать общую выручку от продажи всех товаров, то достаточно применить следующий запрос:
SELECT SUM(Количество*Цена) FROM Продажи;
Следующий запрос содержит вычисляемые выражения и в списке столбцов, и в условии оператора where. Он выбирает из таблицы продажи те товары, выручка от продажи которых больше 1000:
SELECT ТИП—товара. Количество*Цена AS Итого
FROM Продажи
WHERE Количество*Цена > 1000;
Предположим, что требуется получить таблицу, в которой два столбца:
товар, содержащий тип товара и цену;
Итого, содержащий выручку.
Рис. 4.4. Результат запроса с объединением разнотипных данных в одном столбце
Поскольку предполагается, что в исходной таблице продажи столбец Тип товара является символьным (тип char), а столбец Цена — числовой, то при объединении (склейке) данных из этих столбцов необходимо выполнить приведение числового типа
к символьному с помощью функции CAST () . Запрос, выполняющий это задание, выглядит так (рис. 4.4):
SELECT Тип_товара || 1 (Цена: * || CAST(Цена AS CHAR(5)) || 1)1 AS Товар, Количество*Цена AS Итого
FROM Продажи;
Примечание
В Microsoft Access аналогичный запрос будет иметь следующий вид:
SELECT Тип_товара + ’ (Цена: * + CStr(UeHa) + *)* AS Товар, Количество*Цена AS Итого
FROM Продажи;
Условные выражения с оператором CASE
В обычных языках программирования имеются операторы условного перехода, которые позволяют управлять вычислительным процессом в зависимости от того, выполняется или нет некоторое условие. В языке SQL таким оператором является case (случай, обстоятельство, экземпляр). В SQL:2003 этот оператор возвращает значение и, следовательно, может использоваться в выражениях. Он имеет две основные формы, которые мы рассмотрим в данном разделе.
Оператор CASE со значениями
Оператор case со значениями имеет следующий синтаксис:
CASE проверяемое—значение
WHEN значение! THEN результат!
WHEN значение2 THEN результат2
WHEN значением THEN резуль та тМ ELSE резуль та тХ
END
В случае, когда проверяемое—значение равно значение!, оператор case возвращает значение результат!, указанное после
ключевого слова then (то). В противном случае проверяемое- значение сравнивается с значение2, и если они равны, то возвращается значение результат2. В противном случае проверяемое- значение сравнивается со следующим значением, указанным после ключевого слова when (когда) и т. д. Если проверяе- мое_значение не равно ни одному из таких значений, то возвращается значение результатх, указанное после ключевого слова else (иначе).
Ключевое слово else не является обязательным. Если оно отсутствует и ни одно из значений, подлежащих сравнению, не равно проверяемому значению, то оператор case возвращает null.
Допустим, на основе таблицы Клиенты (см. рис. 3.1) требуется получить таблицу, в которой названия регионов заменены их кодовыми номерами. Если в исходной таблице различных регионов не слишком много, то для решения данной задачи удобно воспользоваться запросом с оператором case:
SELECT Имя, Адрес, CASE Регион
WHEN ’Москва’ THEN ’77'
WHEN ’Тверская область’ THEN *69’
ELSE Регион
END
AS Код региона FROM Клиенты;
Оператор CASE с условиями поиска
Вторая форма оператора case предполагает его использование при поиске в таблице тех записей, которые удовлетворяют определенному условию:
CASE
WHEN условие! THEN результат!
WHEN условие2 THEN результат2
WHEN условием THEN резуль та тМ
ELSE результатХ
END
Оператор case проверяет, истинно ли условие! для первой записи в наборе, определенном оператором where, или во всей таблице, если where отсутствует. Если да, то case возвращает значение результат!. В противном случае для данной записи проверяется условие2. Если оно истинно, то возвращается значение резуль та т2 и т. д. Если ни одно из условий не выполняется, то возвращается значение результатх, указанное после ключевого слова else.
Ключевое слово else не является обязательным. Если оно отсутствует и ни одно из условий не выполняется, оператор case возвращает null. После того как оператор, содержащий case, выполнится для первой записи, происходит переход к следующей записи. Так продолжается до тех пор, пока не будет обработан весь набор записей.
Предположим, В таблице Книги (Название, Цена) столбец Цена имеет значение null, если соответствующей книги нет в наличии. Следующий запрос возвращает таблицу, в которой вместо null отображается текст "нет в наличии":
SELECT Название,
CASE
WHEN Цена IS NULL THEN ’Нет в наличии’
ELSE CAST(Цена AS CHAR(8))
END
AS Цена
FROM Книги;
Все значения одного и того же столбца должны иметь одинаковые типы. Поэтому в данном запросе используется функция преобразования типов cast для приведения числовых значений столбца цена к символьному типу.
Обратите внимание, что вместо первой формы оператора case всегда можно использовать вторую:
CASE
WHEN проверяемое_значение = значение! THEN результат!
WHEN проверяемое—Значение = значение2 THEN результат2
WHEN проверяемое—значение = значением THEN резуль та тМ ELSE результатХ
END
Функции NULLIFia COALESCE
В ряде случаев, особенно в запросах на обновление данных (оператор update), удобно использовать вместо громоздкого оператора case более компактные функции nullifo (null, если) и coalesce () (объединять).
Функция NULLIF(значение!, значение2) возвращает NULL, если значение первого параметра соответствует значению второго параметра, в случае несоответствия возвращается значение первого параметра без изменений. То есть если равенство значение! = значение2 выполняется, то функция возвращает null, иначе — значение значение!.
Данная функция эквивалентна оператору case в следующих двух формах:
О CASE значение!
WHEN значение2 THEN NULL
ELSE значение!
END
□ CASE
WHEN значение! = значение2 THEN NULL
ELSE значение!
END
Пример использования функции nullifo в запросе будет приведен в разд. 6.3.
Функция COALESCE (значение1Г значение2, ... , значением') принимает список значений, которые могут быть как определенными, так и неопределенными (null). Функция возвращает первое определенное значение из списка или null, если все значения не определены.
Данная функция эквивалентна следующему оператору case:
CASE
WHEN значение! IS NOT NULL THEN значение!
WHEN значение2 IS NOT NULL THEN значение2
WHEN значением IS NOT NULL THEN значением
ELSE NULL
END
Предположим, ЧТО В таблице Книги (Название, Цена) столбец Цена имеет значение null, если соответствующей книги нет в наличии. Следующий запрос возвращает таблицу, в которой вместо NULL отображается текст "Нет в наличии":
SELECT Название, COALESCE (CAST (Цена AS CHAR(8)),
Нет в наличии1) AS Цена
FROM Книги;
Глава 5
Сложные запросы
В обычной жизни сложные вопросы считаются таковыми, потому что на них трудно найти ответ. Например, вопрос "В чем смысл жизни?” считается сложным потому, что на него трудно однозначно ответить. Поэтому такими вопросами занимаются философы, поэты и обыватели. Простой вопрос предполагает, хотя бы в общих чертах, что именно может быть ответом на него или каким способом можно получить ответ. Например, вопрос "Сколько будет дважды два?" предполагает, что ответчик вспомнит, или вычислит, или обратится в другие инстанции за справками, но, в конце концов, вернет некий ответ, соответствующий вопросу.
В SQL сложные вопросы (запросы) являются комбинацией простых SQL-запросов. Каждый простой запрос в качестве ответа возвращает набор записей (таблицу), а комбинация простых запросов возвращает результат тех или иных операций над ответами на простые запросы. Чтобы понять запрос, лучше всего разобраться в том, что является ответом на него, или понять, каким образом получается ответ.
В SQL сложные запросы получаются из других запросов следующими способами:
вложением SQL-выражения запроса в SQL-выражение другого запроса. Первый из них называют подзапросом, а второй — внешним или основным запросом;
применением к SQL-запросам операторов объединения и соединения наборов записей, возвращаемых запросами. Эти операторы называют теоретико-множественными или реляционными.
Подзапросы
Подзапрос — это SQL-выражение, начинающееся с оператора select, которое содержится в условии оператора where или having для другого запроса. Таким образом, подзапрос — это запрос на выборку данных, вложенный в другой запрос. Внешний запрос, содержащий подзапрос, если только он сам не является подзапросом, не обязательно должен начинаться с оператора select. В свою очередь, подзапрос может содержать другой подзапрос и т. д. При этом сначала выполняется подзапрос, имеющий самый глубокий уровень вложения, затем содержащий его подзапрос и т. д. Часто, но не всегда, внешний запрос обращается к одной таблице, а подзапрос — к другой. На практике именно этот случай наиболее интересен.
Простые подзапросы
Простые подзапросы характеризуются тем, что они формально никак не связаны с содержащими их внешними запросами. Это обстоятельство позволяет сначала выполнить подзапрос, результат которого затем используется для выполнения внешнего запроса. Кроме простых подзапросов, существуют еще и связанные (коррелированные) подзапросы, которые будут рассмотрены в разд. 5.1.2.
Рассматривая простые подзапросы, следует выделить три частных случая:
подзапросы, возвращающие единственное значение;
подзапросы, возвращающие список значений из одного столбца таблицы;
подзапросы, возвращающие набор записей.
Рассмотрим эти частные случаи более подробно.
Работа с единственным значением
Допустим, из таблицы клиенты требуется выбрать данные о тех клиентах, сумма заказов которых больше среднего значения. Это можно сделать с помощью следующего запроса (рис. 5.1):
SELECT * FROM Клиенты
WHERE
Сумма_заказа > (SELECT AVG(Сумма_заказа) FROM Клиенты);
В данном запросе сначала выполняется подзапрос (select AVG(Сумма_заказа) FROM Клиенты). Он возвращает единственное значение (а не набор записей) — среднее значение столбца сумма_ заказа. Если сказать точнее, то данный подзапрос возвращает единственную запись, содержащую единственное поле. Далее выполняется внешний запрос, который выводит все столбцы таблицы клиенты и записи, в которых значение столбца сумма_ заказа больше значения, полученного с помощью подзапроса. Таким образом, сначала выполняется подзапрос, а затем внешний запрос, использующий результат подзапроса.
ИмяИмяАдресАдресзаказазаказаИвановИванов300; Иркутская область 888-0987300; Иркутская область 888-0987Сумма заказа большее среднего уровня г запросйа выборкуСумма заказа большее среднего уровня г запросйа выборкузаказазаказа222-3333222-33333000; Москва3000; Москва112-4321112-43218600; Северо-запад8600; Северо-запад1000 Северо-запад450 Северо-запад3000 Москва200 Тверская область8600 Северо-запад1000 Северо-запад450 Северо-запад3000 Москва200 Тверская область8600 Северо-запад197371 г.Санкт-Петербург пр Королева, д.30, кв. 777197371 г Санкт-Петербург Коломяжский пр , д 14Москва, ул. Тверская, д 25, кв 375Селижарово, ул. Укромная, д 22Санкт-Петербург Невский пр., д.152Иркутск, ул. Ленина, д 2197371 г.Санкт-Петербург пр Королева, д.30, кв. 777197371 г Санкт-Петербург Коломяжский пр , д 14Москва, ул. Тверская, д 25, кв 375Селижарово, ул. Укромная, д 22Санкт-Петербург Невский пр., д.152Иркутск, ул. Ленина, д 2w*- ^*ew^*-*«»**i^i^s Регион; '• j Телефонw*- ^*ew^*-*«»**i^i^s Регион; '• j Телефон► Сидоров Москва, ул Тверская, д. 25, кв 375 _ Захаров ; Санкт-Петербург Невский пр . д.152► Сидоров Москва, ул Тверская, д. 25, кв 375 _ Захаров ; Санкт-Петербург Невский пр . д.152Петров Сидоров Васильев Захаров ПавловПетров Сидоров Васильев Захаров ПавловТелефон 111-1111 111-2222 222-3333 555-1234 112-4321Телефон 111-1111 111-2222 222-3333 555-1234 112-4321Рис. 5.1. Результат запроса с подзапросом среднего значенияРис. 5.1. Результат запроса с подзапросом среднего значения
Имя
Имя
Адрес
Адрес
заказа
заказа
Иванов
Иванов
300; Иркутская область 888-0987
300; Иркутская область 888-0987
Сумма заказа большее среднего уровня г запросйа выборку
Сумма заказа большее среднего уровня г запросйа выборку
заказа
заказа
222-3333
222-3333
3000; Москва
3000; Москва
112-4321
112-4321
8600; Северо-запад
8600; Северо-запад
1000 Северо-запад
450 Северо-запад
3000 Москва
200 Тверская область
8600 Северо-запад
1000 Северо-запад
450 Северо-запад
3000 Москва
200 Тверская область
8600 Северо-запад
197371 г.Санкт-Петербург пр Королева, д.30, кв. 777
197371 г Санкт-Петербург Коломяжский пр , д 14
Москва, ул. Тверская, д 25, кв 375
Селижарово, ул. Укромная, д 22
Санкт-Петербург Невский пр., д.152
Иркутск, ул. Ленина, д 2
197371 г.Санкт-Петербург пр Королева, д.30, кв. 777
197371 г Санкт-Петербург Коломяжский пр , д 14
Москва, ул. Тверская, д 25, кв 375
Селижарово, ул. Укромная, д 22
Санкт-Петербург Невский пр., д.152
Иркутск, ул. Ленина, д 2
w*- ^*ew^*-*«»**i^i^s Регион; '• j Телефон
w*- ^*ew^*-*«»**i^i^s Регион; '• j Телефон
► Сидоров Москва, ул Тверская, д. 25, кв 375 _ Захаров ; Санкт-Петербург Невский пр . д.152
► Сидоров Москва, ул Тверская, д. 25, кв 375 _ Захаров ; Санкт-Петербург Невский пр . д.152
Петров Сидоров Васильев Захаров Павлов
Петров Сидоров Васильев Захаров Павлов
Телефон 111-1111 111-2222 222-3333 555-1234 112-4321
Телефон 111-1111 111-2222 222-3333 555-1234 112-4321
Рис. 5.1. Результат запроса с подзапросом среднего значения
Рис. 5.1. Результат запроса с подзапросом среднего значения
Попытка выполнить следующий простой запрос приведет к ошибке:
SELECT * FROM Клиенты
WHERE Сумма_заказа > AVG (Сумма__заказа) ;
Внимание
Выражение подзапроса обязательно должно быть заключено в круглые скобки.
Подзапрос, вообще говоря, может возвращать несколько записей. Чтобы в этом случае в условии внешнего оператора where можно было использовать операторы сравнения, требующие единственного значения, используются кванторы, такие как all (все) и some (некоторый), описанные в разд. 3.2.1.
Допустим, в базе данных имеются две таблицы:
□ сотрудники (имя, зарплата), содержащая значения зарплаты для сотрудников некоторой фирмы;
О Ср_зарплата_в_промышленности (Тип_промышленности, ср зарплата), содержащая значения среднего уровня зарплаты для различных типов промышленности.
Требуется вывести список сотрудников, получающих зарплату, превышающую средний уровень для любого типа промышленности. С этой целью можно воспользоваться следующим запросом (рис. 5.2):
SELECT Сотрудники.Имя FROM Сотрудники
WHERE Сотрудники.Зарплата >
ALL (SELECT Ср_зарплата FROM Ср_зарплата_в_промышленности);
^груч^^кк тИмяЗарплатаИванов9500:.Петров7000Сидоров3000:Васильев120001Захаров100001Павлов4000|^груч^^кк тИмяЗарплатаИванов9500:.Петров7000Сидоров3000:Васильев120001Захаров100001Павлов4000|11О "“•Ср- ч^рплат^ i^^f^WnfcKH&£Tl№О "“•Ср- ч^рплат^ i^^f^WnfcKH&£Tl№№№^... ""^"^-— ""*Тип промь тленности Ср^зарплата^... ""^"^-— ""*Тип промь тленности Ср^зарплата75007500Рис. 5.2. Список сотрудников, зарплата которых выше средней для всех типов промышленностиРис. 5.2. Список сотрудников, зарплата которых выше средней для всех типов промышленности
^груч^^кк т
Имя
Зарплата
Иванов
9500:.
Петров
7000
Сидоров
3000:
Васильев
120001
Захаров
100001
Павлов
4000|
^груч^^кк т
Имя
Зарплата
Иванов
9500:.
Петров
7000
Сидоров
3000:
Васильев
120001
Захаров
100001
Павлов
4000|
1
1
О "“•Ср- ч^рплат^ i^^f^WnfcKH&£Tl№
О "“•Ср- ч^рплат^ i^^f^WnfcKH&£Tl№
№
№
^... ""^"^-— ""*
Тип промь тленности Ср^зарплата
^... ""^"^-— ""*
Тип промь тленности Ср^зарплата
7500
7500
Рис. 5.2. Список сотрудников, зарплата которых выше средней для всех типов промышленности
Рис. 5.2. Список сотрудников, зарплата которых выше средней для всех типов промышленности
В данном примере используются полные имена столбцов, содержащие в качестве префиксов имена таблиц. Подзапрос (SELECT Ср_зарплата FROM Ср_зарплата_в_промышленности) возвращает список средних зарплат для различных типов промышленности. Выражение > all (больше всех) означает, что внешний запрос должен вернуть только те значения столбца зарплата
из таблицы сотрудники, которые больше каждого значения, возвращенного вложенным подзапросом.
Результатный список сотрудников будет иным, если вместо квантора all применить some или any:
SELECT Сотрудники.Имя FROM Сотрудники
WHERE Сотрудники.Зарплата >
SOME (SELECT Ср_зарплата FROM Ср_зарплата_в_промышпенности) ;
Этот запрос вернет список сотрудников, у которых зарплата выше средней хотя бы для какого-нибудь одного типа промышленности. В данном примере к списку, показанному на рис. 5.2, добавится 'петров1, поскольку его зарплата выше средней для третьего типа промышленности.
Разумеется, вложенные подзапросы могут содержать условия, определяемые оператором where.
Теперь рассмотрим запросы с кванторами all и some для более общего случая. Пусть имеются две таблицы: т1, содержащая как минимум столбец а, и Т2, содержащая, по крайней мере, один столбец в. Тогда запрос с квантором all можно сформулировать следующим образом:
SELECT A FROM Т1
WHERE А оператор_сравнения
ALL (SELECT В FROM Т2) ;
Здесь опера тор_сравнения обозначает любой оператор сравнения из тех, что перечислены в разд. 3.2.1. Данный запрос должен вернуть список всех тех значений столбца а, для которых оператор сравнения истинен для всех значений столбца в.
Запрос с квантором some, очевидно, имеет аналогичную структуру. Он должен вернуть список всех тех значений столбца а, для которых оператор сравнения истинен хотя бы для какого- нибудь одного значения столбца в.
Запрос с квантором all станет, возможно, более понятным, если мы перепишем его в терминах математической логики:
Va Е Tl.A Vb Е Т2.В (а оператор_сравнения Ь) ;
Данное выражение определяет подмножество всех значений столбца а таблицы Т1, таких, что для всех значений столбца в таблицы Т2 выполняется выражение сравнения значений из а и в,
записанное в круглых скобках. Чтобы сформировать указанное подмножество значений столбца а, достаточно просто проверить для каждого значения а, выполняется ли выражение сравнения для всех значений столбца в. Если для некоторого значения из а это выражение не выполняется хотя бы для одного значения из в, то оно отбраковывается, т. е. не попадает в формируемое множество. В противном случае оно попадает в требуемое множество.
Условие с квантором some можно записать так:
Va G Tl.A 3b G Т2.В (а оператор_сравнения Ь) ;
Данное выражение определяет подмножество всех значений столбца а таблицы ti, таких, что для каждого из них найдется хотя бы одно значение столбца в таблицы Т2, при котором выполняется выражение сравнения значений из а и в, записанное в круглых скобках. Чтобы сформировать указанное подмножество значений столбца а, достаточно просто проверить для каждого значения а, выполняется ли выражение сравнения хотя бы для какого-нибудь одного значения из в. Если для некоторого значения из а это выражение не выполняется для всех значений из в, то оно отбраковывается, т. е. не попадает в формируемое множество. В противном случае оно попадает в требуемое множество.
Работа со списком значений из одного столбца
Рассмотрим применение подзапросов, возвращающих не единственное значение, а список значений из одного столбца. Предположим, что некая фирма собирает некоторые технические системы из компонентов. Информация о системах, компонентах и связях между ними хранится в следующих трех таблицах:
О Товар (10_системы, Название, Описание, Цена);
□ Компонент (10_компонента, Название, Описание);
О Состав (lD—Системы, 1В_компонента).
Допустим, нас интересует список систем, в которых имеется некоторый компонент, скажем, микропроцессор. Для этой цели подойдет сложный запрос с предикатом in (в):
SELECT 1р_системы FROM Состав
WHERE ID компонента IN
{ SELECT Ю_компонента FROM Компонент
WHERE Название = ’Микропроцессор’ );
Сначала выполняется подзапрос, возвращающий список идентификаторов компонентов, имеющих название ’микропроцессор'. Далее, внешний запрос сравнивает значение идентификатора компонента из каждой записи таблицы Состав С ПОЛучеННЫМ списком. Если сравнение успешно (сравниваемое значение имеется в списке), то идентификатор системы из той же записи добавляется в результатную таблицу.
Теперь сформулируем запрос, возвращающий список систем, в которых нет заданного компонента, например, микропроцессора. В этом запросе будет два подзапроса с использованием ключевых слов in и not in (не в):
SELECT 1р_системы FROM Состав
WHERE Ю_системы NOT IN
( SELECT 1р_системы FROM Состав
WHERE 10_компонента IN
( SELECT 1р_компонента FROM Компонент
WHERE Название = ’Микропроцессор’ ));
Разумеется, это один из возможных вариантов запросов, возвращающих требуемые данные.
Работа с набором записей
В полнофункциональных базах данных подзапрос можно вставлять не только в операторы where и having, но и в оператор from. Трудно однозначно сказать, зачем это надо делать, но, тем не менее, следующая конструкция работает:
SELECT Т.столбец!, Т.столбец?, ... , Т.столбецК
FROM (SELECT ... ) Т
WHERE ... ;
Здесь таблице, возвращаемой подзапросом в операторе from, присваивается псевдоним т, а внешний запрос выделяет столбцы этой таблицы и, возможно, записи в соответствии с некоторым условием, которое указано в операторе where.
Пример для данной формы запроса приведен в конце разд. 5.2.2.
Связанные подзапросы
Связанные (коррелированные) подзапросы с практической точки зрения наиболее интересны, поскольку позволяют выразить более сложные вопросы относительно сведений, хранящихся в базе данных. Однако для их применения важно понимать, что именно будет делать СУБД для предоставления ответа. Для начала достаточно уяснить, что при выполнении запросов, содержащих связанные подзапросы, нет такого четкого разделения во времени выполнения между подзапросом и запросом, как в случае простых подзапросов. Напомню, что в случае простых подзапросов сначала выполняется подзапрос, а затем содержащий его запрос. В случае связанных подзапросов порядок выполнения запроса в целом иной, и его желательно понимать, чтобы избежать недоразумений. Основной признак связанного подзапроса заключается в том, что он не может быть выполнен самостоятельно, вне всякой связи с основным запросом. Формально этот признак обнаруживается в выражении сложного запроса следующим образом: подзапрос ссылается на таблицу, которая упоминается в основном запросе. Следовательно, подзапрос должен быть выполнен в некоем контексте с текущим состоянием выполнения основного запроса. Рассмотрим это на следующих примерах.
Прежде чем обратиться к конкретным примерам, рассмотрим сначала некоторый абстрактный и, в то же время, типичный запрос, содержащий связанный подзапрос:
SELECT A FROM Т1
WHERE В -
( SELECT В FROM Т2 WHERE С = Т1.С ) ;
Данный запрос на выборку данных (поскольку он начинается с оператора select) содержит подзапрос, сформулированный в выражении, размещенном в основном запросе после ключевого слова WHERE.
Очевидно, данный запрос в целом использует две таблицы: Т1 и Т2, в которых есть столбцы с одинаковыми именами вис и одинаковыми типами. Подзапрос, расположенный в выражении после ключевого слова where основного запроса (select в from Т2 where с = Т1.с), обращается к этим же таблицам. Поскольку
одна из таблиц (ti) фигурирует как в подзапросе, так и во внешнем запросе, то подзапрос нельзя выполнить самостоятельно, вне связи с внешним запросом. Поэтому выполнение запроса в целом (т. е. внешнего запроса) происходит следующим образом:
Сначала выделяется первая запись из таблицы Т1, указанной в операторе гном внешнего запроса (вся запись таблицы ti, а не только значение столбца а). Эта запись называется текущей. Значения столбцов для этой записи доступны и могут быть использованы в подзапросе.
Затем выполняется подзапрос, который возвращает список значений столбца в таблицы Т2 в тех записях, в которых значение столбца с равно значению столбца с из таблицы Т1.
Запрос, сформулированный в рассматриваемом примере, предполагает, что его подзапрос возвращает единственное значение (список с единственным элементом), поскольку в операторе where используется оператор сравнения (=). Если это не так, то потребуется использование, например, предикатов. Как бы то ни было, считаем, что в этом примере подзапрос возвращает единственное значение. Теперь выполняется оператор where основного запроса. Если значение столбца в в текущей (выделенной) записи таблицы ti равно значению, возвращенному подзапросом, то эта запись выделяется внешним запросом.
Оператор select внешнего запроса выполняет проверку условия своего оператора where. А именно он проверяет, равно ли текущее значение столбца в таблицы ti значению, возвращенному подзапросом. Если да, то значение столбца а текущей записи таблицы Т1 помещается в результатную таблицу, в противном случае запись игнорируется. Затем происходит переход к следующей записи таблицы Т1. Теперь для нее выполняется подзапрос. Аналогичным образом все описанное происходит для каждой записи таблицы ti.
Примечание
При формировании связанных запросов рекомендуется использовать полные имена столбцов, а также псевдонимы таблиц и столбцов для того, чтобы не запутаться. В приведенном примере
имена таблиц и столбцов достаточно коротки, чтобы не использовать для них псевдонимы. Однако применение полных имен столбцов сделало бы SQL-выражение запроса более понятным:
SELECT Tl.A FROM Т1
WHERE Tl.B =
( SELECT T2.B FROM T2 WHERE T2.C = Tl.C );
Теперь обратимся к более или менее конкретным примерам.
Нередко торгующие фирмы делают своим клиентам скидки, размер которых зависит от суммы покупки (заказа). Предположим, размеры скидок (коэффициенты) заданы в виде таблицы Скидки, показанной на рис. 5.3. В этой таблице содержатся границы диапазонов суммы покупки и соответствующие коэффициенты скидки. Понятно, что чем больше сумма покупки, тем больше коэффициент скидки (размер скидки равен сумме покупки, умноженной на коэффициент скидки). Тогда, чтобы узнать, какова скидка для клиента, скажем, Захарова, сведения о котором находятся в другой таблице, например, клиенты (см. рис. 3.1), можно воспользоваться следующим запросом:
SELECT Скидка FROM Скидки
WHERE Мин_сумма <=
( SELECT Сумма_заказа FROM Клиенты
WHERE Имя = ’Захаров* )
AND
Макс_сумма >= ( SELECT Сумма_заказа FROM Клиенты WHERE Имя = * Захаров’ );
j Скидки: таблицаj Скидки: таблицаРис. 5.3. Таблица скидокРис. 5.3. Таблица скидок
j Скидки: таблица
j Скидки: таблица
Рис. 5.3. Таблица скидок
Рис. 5.3. Таблица скидок
Здесь подзапрос состоит из двух аналогичных простых запросов, связанных логическим союзом and (и). Таким образом, подзапрос является составным. Запрос в целом выполняется следующим образом. Сначала в таблице скидки выделяется первая запись. Далее выполняются два идентичных подзапроса, возвращающие значение столбца Сумма_заказа таблицы Клиенты при условии, что столбец имя имеет значение * Захаров’. Предполагается, что подзапрос возвращает единственное значение. Если это не так, то в операторе select вместо имени столбца следовало бы написать функцию sum(Сумма_заказа). Результаты подзапросов сравниваются с текущими границами диапазона скидок. Если оба сравнения успешны (истинны), то внешний запрос возвращает текущее значение коэффициента скидки. В противном случае в таблице скидки происходит переход к следующей записи, и все повторяется, как описано ранее.
Как известно, запрос возвращает одну или несколько записей либо не возвращает ничего. Рассмотрим пример, в котором требуется проверка существования записей. Так, иногда требуется выборка записей из одной таблицы при условии, что в другой таблице существует хотя бы одна соответствующая запись.
Пусть в базе данных имеются:
таблица продажи, содержащая данные о продажах товаров некоторой фирмы, в том числе столбец 10_клиента (идентификатор клиента);
таблица контакты, содержащая данные о покупателях (Ю_клиента, Имя, Адрес, Телефон), НО НС содержащая сведений об их покупках.
Требуется получить сведения о клиентах, сделавших хотя бы одну покупку. Далее приведен запрос, выполняющий данное задание:
SELECT Имя, Адрес, Телефон FROM Контакты
WHERE EXISTS
( SELECT DISTINCT 10_клиента FROM Продажи
WHERE Продажи.IО_клиента = Контакты.IП_клиента );
Здесь предикат exists (существует) принимает значение true, если подзапрос возвращает хотя бы одну запись, и тогда внешний запрос возвращает имя клиента и его данные для контакта. Поскольку в запросе требуется проверка существования записей,
возвращаемых подзапросом, а не сами записи, то приведенное SQL-выражение можно несколько упростить:
SELECT Имя, Адрес, Телефон FROM Контакты
WHERE EXISTS
( SELECT DISTINCT 1 FROM Продажи
WHERE Продажи.10_клиента = Контакты.IО_клиента );
Здесь, чтобы выполнить подзапрос, содержимое результата которого для нас не важно, вместо имени столбца вставлена просто цифра 1. Разумеется, в данном случае можно было бы использовать и любое другое число. Это своего рода "фокус-покус".
Связанные подзапросы могут относиться к той же таблице, что и внешний запрос. В этих случаях для одной и той же таблицы используются различные псевдонимы, чтобы показать, из каких записей следует выбирать требуемые значения. Кроме того, подзапросы могут содержаться не только в операторе where, но и в операторе having, который обычно (но не всегда) используется вместе с оператором группировки group by (см. разд. 3.2.2, 3.2.3).
В следующем примере из таблицы продажи выбирается список тех клиентов, у которых максимальная сумма заказов как минимум в 1,5 раза больше среднего размера заказов остальных клиентов:
SELECT Т1.1В_клиента FROM Продажи Т1
GROUP BY Т1. Ю_клиента
HAVING МАХ (Т1. Сумма_заказа) > ALL
( SELECT 1.5*AVG(T2. Сумма_заказа) FROM Продажи Т2
WHERE Т1.1В_клиента о Т2.Ю_клиента );
Данный запрос работает следующим образом:
Записи таблицы продажи группируются по значениям столбца 1В_клиента.
Получившиеся группы обрабатываются оператором having: для каждой группы вычисляется максимальное значение столбца Сумма_заказа (МАХ(Т1.Сумма_заказа) ).
Подзапрос дважды проверяет среднее значение столбца Сумма_заказа для всех тех записей, в которых значение 1в_клиента отличается от значения этого же столбца в текущей группе внешнего запроса (where Т1.1п_клиента <> Т2.1В_клиента). Поэтому для одной и той же таблицы использовались два различных псевдонима.
Теоретико-множественные операции
Над наборами записей, содержащихся в таблицах базы данных и/или возвращаемых запросами, можно совершать теоретикомножественные операции, такие как декартово произведение, объединение, пересечение и вычитание. Что такое теоретикомножественные операции, достаточно подробно рассматривалось в гл. 1.
Декартово произведение наборов записей
Декартово произведение двух таблиц уже рассматривалось ранее.
Напомню, что запрос:
SELECT списокСтолбцов FROM Tl, Т2, . . . Тп;
возвращает набор записей, полученный в результате декартового произведения наборов записей из таблиц ti, Т2, ... , тп. Таблицы, указанные в операторе from, могут быть как таблицами базы данных, так и виртуальными таблицами, возвращаемыми какими- нибудь запросами.
Иногда требуется получить декартово произведение таблицы самой на себя. В этом случае необходимо применить различные псевдонимы для этой таблицы, например:
SELECT списокСтолбцов FROM Mytab Tl, Му tab Т2;
Обратите внимание, что попытка выполнить запрос:
SELECT списокСтолбцов FROM Mytab, Mytab;
приведет к ошибке.
В списке столбцов следует использовать полные имена столбцов, используя псевдонимы таблиц, или символ (*), если требуется получить все столбцы.
Для декартова произведения в SQL также допустим синтаксис с ключевыми словами cross join (перекрестное соединение):
SELECT списокСтолбцов FROM Mytab Tl CROSS JOIN Mytab T2;
Примечание
Рассмотренные выражения работают в полнофункциональных базах данных. В Microsoft Access для получения декартового произведения возможно использование выражения (select список- Столбцов from ti, т2, ... тп), только если все таблицы в списке имеют различные имена. Если требуется декартово произведение таблицы самой на себя, то в выражении (select спи- сокСтолбцов FROM Mytab Tl, Му tab Т2) Access автоматически добавит ключевое слово as перед каждым псевдонимом. Попытка использования ключевых слов cross join в Access приведет к ошибке.
Запросы на декартово произведение сами по себе очень редко используются. Они приобретают некоторый смысл, если применяются с оператором where.
Допустим, что имеется таблица Рейсы (начальный_пункт, конечный пункт), содержащая сведения о том, из каких пунктов
и ви вкакие можно попасть с помощью того или иного авиарейса.
и в
и в
Рис. 5.4. Граф и таблица достижимости пунктовРис. 5.4. Граф и таблица достижимости пунктов
Рис. 5.4. Граф и таблица достижимости пунктов
Рис. 5.4. Граф и таблица достижимости пунктов
На рис. 5.4 показан граф достижимости пунктов некоторым авиарейсом и соответствующая ему таблица Рейсы. Каждой стрелке на графе и каждой записи таблицы соответствует некоторый рейс. Не трудно заметить, что из некоторых пунктов в другие можно попасть только с пересадкой на другой рейс, т. е. через транзитный пункт. Следующий запрос возвращает
таблицу (рис. 5.5), содержащую сведения о достижимости пунктов в точности через один транзитный пункт:
SELECT Т1.НАЧАЛЬНЫЙ_ПУНКТ, Т2.КОНЕЧНЫЙ_ПУНКТ
FROM Рейсы Т1, Рейсы Т2
WHERE Т1.КОНЕЧНЫЙ_ПУНКТ = Т2.НАЧАЛЬНЫЙ_ПУНКТ;
Рис. 5.5. Таблица достижимости пунктов через один транзитный пункт
Рассмотрим данный запрос более подробно. Сначала он выполняет декартово произведение таблицы Рейсы на эту же таблицу. В результате получается таблица с четырьмя столбцами: Т1.НАЧАЛЬНЫЙ_ПУНКТ, Т1.КОНЕЧНЫЙ_ПУНКТ, Т2.НАЧАЛЬНЫЙ_ПУНКТ и т2.конечный_пункт. Затем из полученной таблицы выбираются такие записи, в которых ti . конечный_пункт = Т2. начальный__пункт. Это и есть пары пунктов, между которыми в графе достижимости находится один промежуточный пункт. Наконец, из четырех столбцов выделяются только два: тт.начальный_пункт И Т2.КОНЕЧНЫЙ_ПУНКТ.
Запрос, содержащий сведения о том, в какие пункты можно попасть так или иначе, будет рассмотрен в разд. 5.2.2.
Объединение наборов записей
(UNION)
Нередко требуется объединить записи двух или более таблиц с похожими структурами в одну таблицу. Иначе говоря, к набору записей, возвращаемому одним запросом, требуется добавить
записи, возвращаемые другим запросом. Для этого служит оператор union (объединение):
Запрос! UNION Запрос2;
При этом в результатной таблице остаются только отличающиеся записи, Чтобы сохранить в ней все записи, после оператора union следует написать ключевое слово all.
Например, таблицы Клиенты И Контакты имеют ОДНОТИПНЫе столбцы имя и Адрес. Тогда, чтобы пополнить список данных О клиентах сведениями ИЗ таблицы Контакты, достаточно выполнить следующий запрос:
SELECT Имя, Адрес FROM Клиенты
UNION
SELECT Имя, Адрес FROM Контакты;
Возможно, что в обеих объединяемых таблицах, клиенты и Контакты, имеются записи с одинаковыми парами значений в столбцах имя и Адрес. Однако в результатной таблице данного запроса повторений не будет, как если бы вы использовали оператор UNION DISTINCT.
Оператор union можно применять только таблицам, удовлетворяющим следующим условиям совместимости:
количества столбцов объединяемых таблиц должны быть равны;
данные в соответствующих столбцах объединяемых таблиц должны иметь совместимые типы. Например, символьные (строковые) типы char и varchar совместимы, а числовой и строковый типы не совместимы.
Обратите внимание, что имена соответствующих столбцов и их размеры могут быть различными. Важно, чтобы количества столбцов были равны, а их типы были совместимы. Чтобы объединить наборы записей с несовместимыми по типу данных столбцами, следует применить функцию преобразования типа данных cast (). Например, следующий запрос возвращает список имен клиентов, к которому добавлен список сумм заказов (не будем обсуждать практическую пользу или смысл такого списка):
SELECT Имя FROM Клиенты
UNION
SELECT CAST (Сумма_заказа AS CHAR (10)) FROM Клиенты;
Когда требуется объединить записи двух таблиц, имеющих одноименные столбцы с совместимыми типами, можно использовать оператор union corresponding (объединение соответствующих):
SELECT * FROM Таблица!
UNION CORRESPONDING (списокСтолбцов)
SELECT * FROM Таблица?;
После ключевого слова corresponding можно указать столбцы, имеющиеся одновременно в обеих таблицах. Если этот список столбцов не указан, то предполагается список всех имен. При этом возможны недоразумения.
В разд. 5.2.1 рассматривался запрос, возвращающий таблицу со сведениями о достижимости пунктов в точности через один промежуточный пункт (см. рис. 5.4, 5.5). Теперь сформулируем запрос по-другому: требуется получить сведения о том, в какие пункты можно попасть, сделав не более одной пересадки (т. е. без пересадок или с одной пересадкой). Для этого достаточно объединить записи исходной таблицы Рейсы с результатом запроса о достижимости через один промежуточный пункт:
SELECT НАЧАЛЬНЫЙ_ПУНКТ, КОНЕЧНЫЙ-ПУНКТ FROM Рейсы
UNION
SELECT Т1.НАЧАЛЬНЫЙ_ПУНКТ, Т2.КОНЕЧНЫЙ_ПУНКТ
FROM Рейсы Т1, Рейсы Т2
WHERE Т1.КОНЕЧНЫЙ_ПУНКТ = Т2.НАЧАЛЬНЫЙ__ПУНКТ;
Результат данного запроса показан на рис. 5.6.
Аналогичным образом можно сформулировать запрос о достижимости пунктов, в которые можно попасть посредством не более двух, трех и т. д. пересадок. Запрос, содержащий сведения о том, в какие пункты вообще можно попасть, будет рассмотрен далее в разд. 5.3.5.
Рассмотренный запрос возвращает сведения о достижимости пунктов из всех возможных начальных пунктов. А если нам нужны сведения о достижимости только из одного пункта, например, а? В этом случае достаточно добавить еще одно условие в оператор where:
SELECT НАЧАЛЬНЫЙ_ПУНКТ, КОНЕЧНЫЙ_ПУНКТ FROM Рейсы
UNION
SELECT Т1.НАЧАЛЬНЫЙ_ПУНКТ, Т2.КОНЕЧНЫЙ_ПУНКТ FROM Рейсы Т1, Рейсы Т2
WHERE Т1.КОНЕЧНЫЙ_ПУНКТ = Т2. НАЧАЛЬНЫЙ_ПУНКТ AND Т1. НАЧАЛ ЬНЫЙ_ПУНКТ = ’А*;
НАЧАЛ ЬНЫЙ_ПУНКТ
КОНЕЧНЫЙ.ПУНКТ
►
А
В
А
С
А
D
А
Е
А
F
А
G
В
С
В
D
В
Е
В
F
В
G
С
D
С
Е
С
Н
D
Н
Рис. 5.6. Таблица достижимости пунктов, в которые можно попасть,сделав не более одной пересадки
Вместе с тем, допустим и такой эквивалентный запрос:
SELECT * FROM (
SELECT НАЧАЛЬНЫЙ_ПУНКТ, КОНЕЧНЫЙ_ПУНКТ FROM Рейсы
UNION
SELECT Т1.НАЧАЛЬНЫЙ_ПУНКТ, Т2.КОНЕЧНЫИ_ПУНКТ
FROM Рейсы Т1, Рейсы Т2
WHERE Т1.КОНЕЧНЫЙ_ПУНКТ = Т2.НАЧАЛЬНЫЙ_ПУНКТ) Т
WHERE Т.НАЧАЛЬНЫЙ-ПУНКТ = ’А’;
Пересечение наборов записей
(INTERSECT)
Пересечение двух наборов записей осуществляется с помощью оператора intersect (пересечение), возвращающего таблицу, записи в которой содержатся одновременно в двух наборах:
Запрос! INTERSECT Запрос2;
При этом в результатной таблице остаются только отличающиеся записи. Чтобы сохранить в ней повторяющиеся записи, после оператора intersect следует написать ключевое слово all.
Как и в операторе union, в intersect можно использовать ключевое слово corresponding. В этом случае исходные наборы данных нс обязательно должны быть совместимыми для объединения, но соответствующие столбцы должны иметь одинаковые тип и длину.
Как известно, различные технические системы могут содержать одинаковые компоненты. Если требуется получить список компонентов, входящих одновременно в две различные системы, то можно воспользоваться таким запросом:
SELECT * FROM Система!
INTERSECT
SELECT * FROM Система2;
Здесь предполагается, ЧТО таблицы Система! И Система2 имеют одинаковые структуры. Если же структуры этих таблиц в чем-то различаются, но столбцы 10_компонента и Тип_компонента имеют одинаковые тип и длину, то можно применить следующий запрос:
SELECT * FROM Система!
INTERSECT CORRESPONDING (10_компонента, Тип_компонента) SELECT * EROM Система2;
Если в операторе intersect используется ключевое слово corresponding, то после него в круглых скобках можно указать имена столбцов, значения которых должны проверяться на равенство при выполнении операции пересечения.
Вычитание наборов записей (EXCEPT}
Для получения записей, содержащихся в одном наборе и отсутствующих в другом, служит оператор except (за исключением):
Запрос! ЕХЕРТ Запрос2;
С помощью этого оператора из первого набора удаляются записи, входящие во второй набор. Так же, как и в операторах union и intersect, в операторе except можно использовать ключевое СЛОВО CORRESPONDING.
6 Зак. 120
Например, таблицы Клиенты И Контакты имеют однотипные столбцы Имя и Адрес. Чтобы узнать, все ли клиенты содержатся в списке контактов, можно воспользоваться следующим запросом: SELECT * FROM Клиенты
EXCEPT CORRESPONDING (Имя, Адрес)
SELECT * FROM Контакты;
Возвращенные этим запросом записи будут содержать сведения о клиентах, которых нет в таблице контакты. Если же запрос вернет пустую таблицу, то это будет означать, что все клиенты представлены в таблице контакты.
Операции соединения
Операции соединения наборов записей возвращают таблицы, записи в которых получаются путем некоторой комбинации записей соединяемых таблиц. Для этого используется оператор join (соединить).
Существуют несколько разновидностей соединения, которым соответствуют определенные ключевые слова, добавляемые к слову join. Так, например, декартово произведение (см. разд. 5.2.1) является операцией перекрестного соединения. В SQL-выражении для обозначения этой операции используется оператор cross join. Впрочем, декартово произведение можно получить и без использования этих ключевых слов. В основе любого соединения наборов записей лежит операция их декартового произведения.
Примечание
Довольно часто операции, основанные на операторе join, называют объединением таблиц (наборов записей). Однако термин "объединение" лучше подходит для union— оператора теоретико-множественного объединения записей, при котором записи исходных наборов не комбинируются (не соединяются) друг с другом, а просто к одному набору записей добавляется другой набор. В случае оператора join в результатную таблицу попадают записи, полученные из разных наборов путем присоединения одной из них к другой. Поэтому операции, основанные на операторе join, будем называть операциями соединения таблиц (наборов записей).
Естественное соединение
(NATURAL JOIN)
Рассмотрим суть операции естественного соединения на типичном примере. Пусть в базе данных имеются следующие две таблицы:
Продажи (lD_TOBapa, Количество, 1П_клиента);
Клиенты (lD—Клиента, Имя, Телефон).
Общим столбцом для этих таблиц является ю_клиента. Декартово произведение этих таблиц получается с помощью следующего запроса:
SELECT * FROM Продажи, Клиенты;
На рис. 5.7 показаны примеры таблиц продажи и клиенты, а также результат их декартового произведения.
: ЗП^ЖЙ ' Ш<^^ "^'"V V1JID тог фа J Количестве j ID клиента -120:2! Жр2150:250:1h1200:3|33:1 E240:.tn :ь395л—itUHJl>*J и: ЗП^ЖЙ ' Ш<^^ "^'"V V1JID тог фа J Количестве j ID клиента -120:2! Жр2150:250:1h1200:3|33:1 E240:.tn :ь395л—itUHJl>*J и^.ii^5?T^ ' -^й'?»^^ "' Ю_клиента^ ? Имя^AXC*"^bl6OtiC«O*-^bti»-.^^»X«bi^«^iv>«*>iJw "Телефон I***r ■Г Иванов2: Петров111-1111111-2222'j ►3; Сидоров222-3333 уIWJtyjзЕЕЮ@>^.ii^5?T^ ' -^й'?»^^ "' Ю_клиента^ ? Имя^AXC*"^bl6OtiC«O*-^bti»-.^^»X«bi^«^iv>«*>iJw "Телефон I***r ■Г Иванов2: Петров111-1111111-2222'j ►3; Сидоров222-3333 уIWJtyjзЕЕЮ@>
: ЗП^ЖЙ ' Ш<^^ "^'"V V1J
ID тог фа J Количестве j ID клиента -
1
20:
2! Ж
р
2
150:
2
50:
1
h
1
200:
3|
3
3:
1 E
2
40:
.tn :ь
395
л—it
UHJl>*J и
: ЗП^ЖЙ ' Ш<^^ "^'"V V1J
ID тог фа J Количестве j ID клиента -
1
20:
2! Ж
р
2
150:
2
50:
1
h
1
200:
3|
3
3:
1 E
2
40:
.tn :ь
395
л—it
UHJl>*J и
^.ii^5?T^ ' -^й'?»^^ "
' Ю_клиента^ ? Имя^
AXC*"^bl6OtiC«O*-^bti»-.^^»X«bi^«^iv>«*>iJw "
Телефон I***
r ■
Г Иванов
2: Петров
111-1111
111-2222'
j ►
3; Сидоров
222-3333 у
IWJtyjз
ЕЕЮ@>
^.ii^5?T^ ' -^й'?»^^ "
' Ю_клиента^ ? Имя^
AXC*"^bl6OtiC«O*-^bti»-.^^»X«bi^«^iv>«*>iJw "
Телефон I***
r ■
Г Иванов
2: Петров
111-1111
111-2222'
j ►
3; Сидоров
222-3333 у
IWJtyjз
ЕЕЮ@>
^ •-V1 ^^^т^ ^^"л
^ Декарттоао произведение : запрос на выборку "
1р_п ipa
Количества |
Продажи 1О_клиента | Клиенты К
)_j Имя
Телефон:
i ►
1
20^
2;
1:Иванов
111-1111
1
20:
21
2i Петров
111-2222
1
20:
2^
3; Сидоров
222-3333
2
150:
й
Г; Иванов
111-1111
2
150:
21
2: Петров
111-2222
2
150^
2:
3= Сидоров
222-3333
1
2
50=
Т
1iИванов
111-1111
2
50;
Т
2; Петров
111-2222
2
50^
1;
3^ Сидоров
222-3333
1
200
3:
Г Иванов
111-1111
1
200;
з;
2. Петров
111-2222
1
200:
3=
3. Сидоров
222-3333
3
3?
1:
Г Иванов
111-1111
1
3
31
Т
2: Петров
111-2222
3
3:
т
3: Сидоров
222-3333
2
40;
1:
11 Иванов
111-1111
2
40:
1:
2i Петров
111-2222
2
401
3; Сидоров
222-3333
1 Запись» УйУ 4
11С
LltiJ> * to 18 :
Рис. 5.7. Таблицы Продажи, Клиенты и их декартовое произведение
Очевидно, в полученном декартовом произведении нас могут интересовать не все записи, а только те, в которых идентичные СТОЛбцЫ ИМеЮТ одинаковые Значения (Продажи. 10_клиента = клиенты. ю_клиента). Кроме того, в результатной таблице нам не нужны оба идентичных столбца, достаточно лишь одного из них. Такая таблица и будет естественным соединением таблиц продажи и клиенты (рис. 5.8). Она получается с помощью следующего запроса:
SELECT Продажи. *, Клиенты. Имя, Клиенты. Телефон
FROM Продажи, Клиенты
WHERE Продажи. Ю_клиента = Клиенты. Ю_клиента;
Данный запрос можно переписать, используя псевдонимы:
SELECT Т1.*, Т2.Имя, Т2.Телефон
FROM Продажи Т1, Клиенты Т2
WHERE Т1.10_клиента = Т2.10_клиента;
Эквивалентный запрос с оператором natural join выглядит следующим образом:
SELECT Т1. *, Т2.Имя, Т2.Телефон
FROM Продажи Т1 NATURAL JOIN Клиенты Т2;
J Естестве иное соединение :
запрос на выборку
Ю_товара
Количество! Ю^кпиента | • ИмяТелефон .
►
1
20f
2 Петров
111-2222
2
150.:
2 Петров
111-2222
50!
^Иванов
111-1111■
1
200?
3 Сидоров
222-3333
3
3|
1?Иванов
111-1111
.>fc>^y^4^v.>y.>w,>M*v<^^
ЦИванов
.-.X*V«y<-.-w»»-4> • • --> «•^WW'» <•»»>.x.VX.X»*-X-
111-1111J
| Запись; [К:) ^ 1
1 LLJL
►Q>^' изб
Рис. 5.8. Естественное соединение таблиц Продажи и Клиенты
Примечание
В Microsoft Access оператор natural join не поддерживается. Вместо него используется inner join (внутреннее соединение) и ключевое слово on (при), за которым следует условие отбора записей. Впрочем, inner join можно применять и в полнофункциональных базах данных.
При естественном соединении, выполняемом с помощью оператора natural join, проверяется равенство всех одноименных столбцов соединяемых таблиц.
Условное соединение (JOIN... ON)
Условное соединение похоже на соединение с условием равенства, которое рассматривалось в начале разд. 5.3.1. Отличие состоит в том, что в качестве условия может выступать любое логическое выражение, которое записывается после ключевого слова on (при), а не where. Если условие выполняется для текущей записи декартового произведения, то она входит в результатную таблицу.
Допустим, в базе данных имеются следующие две таблицы:
Продажи (10_товара, Количество, ID_клиента);
Клиенты (ю_клиента, Имя, Телефон).
Тогда эти таблицы можно соединить, используя, например, следующий запрос:
SELECT * FROM Продажи JOIN Клиенты
ON (Продажи.IО_клиента = Клиенты.10_клиента) AND (Продажи.Количество > 50);
Примечание
В Microsoft Access используется оператор inner join ... on. В полнофункциональных базах данных также допустимо ключевое СЛОВО INNER.
Соединение по именам столбцов
(JOIN... USING)
Соединение по именам столбцов похоже на естественное соединение. Отличие состоит в том, что можно указать, какие одноименные столбцы должны проверяться. Напомню, что в естественном соединении проверяются все одноименные столбцы.
Допустим, имеются две таблицы с одинаковыми структурами:
Болты (Тип, Количество, Материал);
Гайки (Тип, количество, Материал).
Предположим, что мы готовим крепежные комплекты, в каждом из которых количества однотипных болтов и гаек должны совпадать, а их материалы могут быть различными. Требуется узнать, какие комплекты уже готовы и сколько элементов они содержат. Для этого следует определенным образом соединить таблицы Болты и гайки. Естественное соединение в этому случае не подойдет, поскольку при нем проверяются все одноименные столбцы, а потому в результатную таблицу не попадут комплекты болтов и гаек из различных материалов. Например, если все болты стальные, а гайки латунные, то в результате естественного соединения будет получена пустая таблица. Поэтому следует использовать соединение по именам столбцов, при котором столбец материал исключен. Это соединение можно быть представлено так:
SELECT * FROM Болты JOIN Гайки
USING (Тип, Количество);
После ключевого слова using (используя) в круглых скобках указывается список одноименных столбцов соединяемых таблиц, которые необходимо проверять.
На рис. 5.9 показаны таблицы Болты и гайки, а также результат рассмотренного запроса.
Очевидно, данный запрос можно сформулировать иначе:
SELECT * FROM Болты, Гайки
WHERE (Болты.Тип = Гайки.Тип) AND (Болты.Количество = Гайки.Количество);
^ г «ж* та$>^ч?^■I£ ТипКоличество5 Материал 'л;$—М24М16120: Сталь;^30:СтальМ8250. ЛатуньМ4340; ЛатуньМ4180: Медьky| Запись; l±LllUr6^>1Низ 6 ;^ г «ж* та$>^ч?^■I£ ТипКоличество5 Материал 'л;$—М24М16120: Сталь;^30:СтальМ8250. ЛатуньМ4340; ЛатуньМ4180: Медьky| Запись; l±LllUr6^>1Низ 6 ;Рис. 5.9. Таблицы Болты и Гайки и результат их соединения по столбцам Тип и КоличествоРис. 5.9. Таблицы Болты и Гайки и результат их соединения по столбцам Тип и Количество
^ г «ж* та$>^ч?^■
I
£ Тип
Количество
5 Материал 'л;
$—
М24
М16
120: Сталь;^
30:Сталь
М8
250. Латунь
М4
340; Латунь
М4
180: Медьky
| Запись; l±LllUr
6^>1Н
из 6 ;
^ г «ж* та$>^ч?^■
I
£ Тип
Количество
5 Материал 'л;
$—
М24
М16
120: Сталь;^
30:Сталь
М8
250. Латунь
М4
340; Латунь
М4
180: Медьky
| Запись; l±LllUr
6^>1Н
из 6 ;
Рис. 5.9. Таблицы Болты и Гайки и результат их соединения по столбцам Тип и Количество
Рис. 5.9. Таблицы Болты и Гайки и результат их соединения по столбцам Тип и Количество
Примечание ^|
В Microsoft Access оператор join ... using не поддерживается. Вместо него можно использовать inner join ... on. Рассмотренный запрос в Access можно сформулировать так:
SELECT *
FROM Болты INNER JOIN Гайки
ON (Болты.Тип = Гайки.Тип) AND (Болты.Количество = Гайки .Количество);
Внешние соединения
Все соединения таблиц, рассмотренные до сих пор, являются внутренними. Во всех примерах вместо ключевого слова join можно писать inner join (внутреннее соединение). Из таблицы, получаемой при внутреннем соединении, отбраковываются все записи, для которых нет соответствующих записей одновременно в обеих соединяемых таблицах. При внешнем соединении такие несоответствующие записи сохраняются. В этом и заключается отличие внешнего соединения от внутреннего.
С помощью специальных ключевых слов left outer, right outer, full и union, написанных перед JOIN, можно выполнить соответственно левое, правое, полное соединение и объединение- соединение. В SQL-выражении запроса таблица, указанная слева от оператора join, называется левой, а указанная справа от него — правой.
Левое соединение (LEFT OUTER JOIN)
При левом внешнем соединении несоответствующие записи, имеющиеся в левой таблице, сохраняются в результатной таблице, а имеющиеся в правой — удаляются.
Допустим, в базе данных есть две таблицы:
Предложение (lD_TOBapa, Цена, Описание);
Склад (lD_TOBapa, Количество).
Таблица предложение содержит список идентификаторов, цены и описания товаров, предлагаемых к продаже. Таблица склад содержит сведения о наличии товаров на складе. При этом в таблице Склад могут содержаться не все товары, предлагаемые к продаже.
Чтобы получить список всех продаваемых товаров с указанием их количества на складе, достаточно выполнить следующий запрос:
SELECT Предложение.ID_товара, Предложение.Описание, Склад.Количество
FROM Предложение LEFT OUTER JOIN Склад
ON Предложение.ID_товара = Склад.1В_товара;
Здесь ключевые слова left outer join означают операцию левого внешнего соединения.
Примечание
В Microsoft Access ключевое слово outer не допускается. Поскольку левого внутреннего объединения не существует, то можно просто писать left join.
На рис. 5.10 показаны исходные таблицы и результат данного запроса. Пустые поля в действительности имеют неотображаемое значение null.
11
ID лова ра1ID лова ра1Цена Описание аЦена Описание аЮ^товара КоличествоЮ^товара Количество2;345672;3456717;Хлеб13 Молоко98;Свинина [ 1201 Говядина !15 Масло100 Сыр*18 Яйцо|17;Хлеб13 Молоко98;Свинина [ 1201 Говядина !15 Масло100 Сыр*18 Яйцо|Запись:Запись:ИИ11кк►I►I2424667j"1'7j"1'502085301502085301^ Наличие предлагаемых товаров : запрос... ^. □^ Наличие предлагаемых товаров : запрос... ^. □Ю_товараЮ_товараЗапись; ГНЗапись; ГНК Хлеб2< Молоко3 Свинина4Товядина5 Масло6j Сыр7 .ЯйцоК Хлеб2< Молоко3 Свинина4Товядина5 Масло6j Сыр7 .ЯйцоОписаниеОписаниеКоличествоКоличество50502Щ2Щ85]3085]30
ID лова ра
1
ID лова ра
1
Цена Описание а
Цена Описание а
Ю^товара Количество
Ю^товара Количество
2;
3
4
5
6
7
2;
3
4
5
6
7
17;Хлеб
13 Молоко
98;Свинина [ 1201 Говядина !
15 Масло
100 Сыр*
18 Яйцо|
17;Хлеб
13 Молоко
98;Свинина [ 1201 Говядина !
15 Масло
100 Сыр*
18 Яйцо|
Запись:
Запись:
И
И
1
1
к
к
►I
►I
2
4
2
4
6
6
7j
"1'
7j
"1'
50
20
85
30
1
50
20
85
30
1
^ Наличие предлагаемых товаров : запрос... ^. □
^ Наличие предлагаемых товаров : запрос... ^. □
Ю_товара
Ю_товара
Запись; ГН
Запись; ГН
К Хлеб
2< Молоко
3 Свинина
4Товядина
5 Масло
6j Сыр
7 .Яйцо
К Хлеб
2< Молоко
3 Свинина
4Товядина
5 Масло
6j Сыр
7 .Яйцо
Описание
Описание
Количество
Количество
50
50
2Щ
2Щ
85]
30
85]
30
Рис. 5.10. Таблицы Предложение, Склади результат их левого внешнего соединения
Эквивалентный запрос, не использующий ключевые слова left outer join, выглядит довольно громоздко:
SELECT Предложение.IО_товара, Предложение.Описание, Склад.Количество
FROM Предложение, Склад
WHERE Предложение.1и_товара=Склад.Ю_товара
UNION
SELECT Предложение.IО__товара, Предложение.Описание, NULL FROM Предложение
WHERE Предложение.Ю_товара NOT IN
(SELECT Предложение.1В_товара FROM Предложение, Склад
WHERE Предложение.1В_товара = Склад.1В_товара);
Этот запрос состоит из объединения (union) двух запросов, второй из которых содержит подзапрос. Чтобы выровнять количества столбцов в объединяемых таблицах, во втором запросе в список столбцов был добавлен null. Впрочем, вместо null можно указать 0 (ноль) и тогда в столбце количество для всех отсутствующих товаров будет стоять число 0.
Правое соединение (RIGHT OUTER JOIN)
При правом внешнем соединении несоответствующие записи, имеющиеся в правой таблице, сохраняются в результатной таблице, а имеющиеся в левой — удаляются. Ключевыми словами в запросе с таким соединением являются right outer join.
Чтобы решить задачу, рассмотренную в предыдущем разделе, используя правое соединение, достаточно просто поменять местами имена соединяемых таблиц:
SELECT Предложение.1В_товара, Предложение.Описание, Склад.Количество
FROM Склад RIGHT OUTER JOIN Предложение
ON Предложение.ID_TOBapa = Склад.10_товара;
Примечание
В Microsoft Access ключевое слово outer не допускается. Поскольку правого внутреннего объединения не существует, то можно просто писать right join.
Полное соединение (FULL JOIN)
Полное соединение выполняет одновременно и левое, и правое внешние соединения. Ключевыми словами в запросе с таким соединением являются full join.
Допустим, база данных некоторой компании содержит сведения о своих представительствах, отделах и сотрудниках. Эта информация хранится в трех таблицах:
О Представительства (lD_пр, Адрес);
Отделы (lD_отд, ID_пр, Название);
Сотрудники (lD_сотр, ID—Отд, Имя).
Таким образом, в таблице сотрудники имеются данные об отделах, в которых они работают, а в таблице отделы — о представительствах, в которые они входят. В общем случае в этой базе данных могут быть:
представительства без отделов;
отделы, не входящие ни в одно представительство;
отделы без сотрудников;
сотрудники, не входящие ни в один отдел.
Чтобы просмотреть все представительства, отделы и всех сотрудников, не зависимо от того, имеются ли в одних таблицах соответствующие записи в других, используется полное внешнее соединение:
SELECT *
FROM Представительства FULL JOIN Отделы
ON (Представительства.ID_пр = Отделы. ID_np)
FULL JOIN Сотрудники
ON (Отделы.ID—Отд = Сотрудники.ID_отд);
Примечание
В Microsoft Access оператор full join не поддерживается.
Объединение-соединение (UNION JOIN}
Операцию объединение-соединение еще называют объединение- слияние. Ключевыми словами в запросе с таким соединением ЯВЛЯЮТСЯ UNION JOIN.
При объединении-соединении создается виртуальная таблица, содержащая все столбцы обеих исходных таблиц. При этом столбцы из левой исходной таблицы содержат все записи левой таблицы, а в тех же записях в столбцах из правой исходной таблицы содержатся значения null. Аналогично, столбцы из правой исходной таблицы содержат все записи правой таблицы, а эти же записи в столбцах из левой таблицы содержат null. Общее количество записей в виртуальной таблице равно сумме количеств записей, имеющихся в обеих исходных таблицах.
В качестве примера приведем простой запрос:
SELECT * FROM Т1 UNION JOIN Т2;
На рис. 5.11 показаны две таблицы и результат их объединения - соединения.
Обычно результат объединения-соединения рассматривается в качестве промежуточного при выполнении более сложного запроса.
Рис. 5.11. Две исходных таблицы и результат их объединения-соединенияРис. 5.11. Две исходных таблицы и результат их объединения-соединения
Рис. 5.11. Две исходных таблицы и результат их объединения-соединения
Рис. 5.11. Две исходных таблицы и результат их объединения-соединения
Рекурсивные запросы
В некоторых языках программирования допускается рекурсия. Так, в определении функции возможен вызов этой же функции. Это мощная возможность языка программирования, хотя и редко используемая на практике. Типичным примером, приводимым в литературе для пояснения, что такое рекурсия, является определение функции, вычисляющей факториал заданного числа.
Факториал целого числа л, обозначаемый как и\, есть целое число, равное:
1, если и <= 1;
2 * 3 * 4 * ... * л, если n > 1.
Таким образом, л! равно произведению всех целых чисел, не больших л, или 1, если л меньше или равно 1.
Эту простую математическую задачу для заданного числа л можно решить программно различными способами, например, с помощью оператора цикла, который есть практически во всех языках. Однако более изящный код получается, если использовать рекурсию. Так, например, на языке JavaScript, на котором пишутся программы для Web-приложений, определение функции вычисления факториала посредством рекурсии выглядит следующим образом:
function factorial(n) {
if (n <= 1) return 1;
return n*factorial(n-1);
}
Рассмотрим в качестве примера вычисление факториала для л = 3.
Вызов функции factorial (3) вернет 3*factoriai(2), т. е. приведет к вызову функции factorial (2). Таким образом, требуется вторая итерация вычислений.
Вызов функции factorial (2) вернет 2*factorial (1). С учетом результата первой итерации получаем 3*2*factoriai(1). Требуется вызов функции factorial (1).
Вызов функции factorial (1) вернет 1. С учетом результата второй итерации получаем 3*2*1. Поскольку больше вызовов
функции factorial () не требуется, на этом вычислительный процесс завершается и возвращается результат 6.
Рекурсивным называется запрос, который содержит в себе этот же запрос. Более сложный случай возникает тогда, когда Запрос! функционально зависит от Запроса2, который, в свою очередь, зависит ОТ Запроса!.
В ранних версиях SQL рекурсивные запросы не поддерживались. В SQL: 1999 и SQL:2003 появились расширения, поддерживающие рекурсивные запросы. Однако в основной части стандарта SQL:2003 поддержка рекурсии не предусмотрена. Так, Microsoft Access не поддерживает рекурсивные запросы.
В разд. 5.2.1 рассматривалась задача определения достижимости пунктов на основе таблицы Рейсы (см. рис. 5.4). Точнее, в таблице Рейсы содержались сведения о том, какие пункты связаны между собой тем или иным авиарейсом. При этом рассматривался запрос, возвращающий таблицу достижимости пунктов через один промежуточный пункт. В разд. 5.2.2 был рассмотрен запрос, возвращающий таблицу достижимости пунктов с помощью не более одного промежуточного пункта. По аналогии можно сформулировать запросы о достижимости пунктов, в которые можно попасть с помощью не более двух, трех и т. д. пересадок. При этом сложность соответствующего SQL-выражения будет только возрастать. Чтобы решить общую задачу о том, в какие пункты можно вообще попасть, сделав сколько угодно пересадок, требуется рекурсивный запрос:
WIDTH RECURSIVE
Достижимость (НАЧАЛЬНЫЙ_ПУНКТ, КОНЕЧНЫЙ_ПУНКТ) AS ( SELECT НАЧАЛЬНЫЙ-ПУНКТ, КОНЕЧНЫЙ_ПУНКТ
FROM Рейсы
UNION
SELECT Т1.НАЧАЛЬНЫЙ_ПУНКТ, Т2.КОНЕЧНЫЙ_ПУНКТ
FROM Достижимость Т1, Рейсы Т2
WHERE Т1.КОНЕЧНЫЙ _ПУНКТ = Т2.НАЧАЛЬНЫЙ_ПУНКТ )
SELECT * FROM Достижимость;
Данный запрос выполняется итеративно в несколько проходов.
В начале первой итерации во временной таблице достижимость нет ни одной записи. Оператор union (объединить) копирует строки ИЗ таблицы Рейсы В таблицу Достижимость.
При второй итерации просматриваются только те записи, в которых значение столбца конечный_пункт таблицы достижимость равно значению начальный_пункт таблицы Рейсы. Для каждой такой записи берутся значения начальный_пункт из таблицы Достижимость и значения КОНЕЧНЫЙ_ПУНКТ ИЗ таблицы Рейсы, а затем в качестве новой записи добавляются в таблицу Достижимость. Теперь в таблице достижимость находятся все пункты, в которые можно попасть из любого пункта, находящегося в столбце начальный_пункт той же таблицы, делая при этом не более одной пересадки.
Во время следующей рекурсивной итерации будут обработаны маршруты с двумя промежуточными пунктами и т. д., пока не будут найдены все пункты, куда в принципе можно попасть.
По окончании рекурсии последний оператор select, не участвующий в итерациях, возвращает содержимое таблицы достижимость. При необходимости его можно дополнить выражением where с условием выборки нужных записей.
Обратите внимание, что в рассмотренном запросе временная таблица достижимость определяется на основе ее самой. Это обстоятельство и делает запрос рекурсивным. Рекурсивной частью определения является оператор select, расположенный сразу за оператором union. Временная таблица достижимость наполняется данными по мере выполнения рекурсии до тех пор, пока все возможные конечные пункты не окажутся в этой таблице. Дублирующих записей в этой таблице не будет, поскольку оператор union их подавляет.
Рекурсивные запросы позволяют писать экономные по объему кода и ясные по своей структуре SQL-выражения в случаях обработки таблиц с данными, которые имеют иерархическую организацию. Например, сложные технические системы обычно состоят из компонентов, которые в свою очередь состоят из других компонентов и т. д. Связи между компонентами можно хранить в одной таблице, в одном из столбцов которой находятся идеи-
тификаторы компонентов, а в другом — идентификаторы элементов, входящих в состав компонентов. При этом элемент некоторого компонента сам может содержать другие элементы. Таким образом, один и тот же идентификатор элемента может находиться и в первом, и во втором столбцах таблицы, содержащей сведения о связях компонент-элемент. Пример такой таблицы показан на рис. 5.12.
21 Система
: таблица
ИКА ^^^^'
1
Компонент
' Элемент |^1
Устройство!jУзел!
।
г
Устройство!j Узел2
Устройство!1 УзелЗ
Устройст Устройст
во2■ УзелЗ
во2; Узел4
Узел2
; Деталь!
Узел2
Щеталь2
Узел2
| ДетальЗ
Узел4
i Деталь!
1
Узел4
Узел4
ДетальЗ i Детальб
Детальб
j Деталь25
1 ►
Детальб
,Деталь49
из 13.
[запись:
wwwvmvwwviwxii
И ]
113
A-.WV.WWWWlV,WWVftW
Рис. 5.12. Таблица связей компонент-элемент для некоторой системы
Типичный запрос к такой таблице: дать список всех элементов, ИЗ которых СОСТОИТ компонент, например, Устройство 1. Не трудно заметить, что эта задача аналогична рассмотренной ранее задаче о достижимости пунктов.
Задачи
Задача 5.1
В разд. 4.1 рассматривались итоговые функции, позволяющие производить элементарные вычисления над числовыми данными в таблице. В данной главе вы познакомились со сложными за-
просами и, следовательно, можете выполнять более сложные вычисления. В качестве упражнения я предлагаю вам задачу, которая на первый взгляд может показаться слишком специальной, представляющей интерес лишь для математиков. С этой задачей чаще всего сталкиваются люди, занимающиеся статистическими расчетами. Однако она полезна для всех изучающих SQL, поскольку связана с комбинацией итоговых функций и вложенных запросов. Такого рода вычисления возникают при анализе данных.
Предлагается вычислить среднеквадратическое отклонение (СКО) некоторой величины. СКО характеризует разброс данных относительно их среднего значения. Напомню, что среднеквадратическое отклонение величины X, обозначаемое как оСА'), есть среднее значение отклонения значений величины X от ее среднего значения, обозначаемого здесь как М(Х).
Если величина X представлена списком своих значений хь х2,..., х^, где N — количество всех значений X, то СКО рассчитывается по формуле:
N /
где М(Х) = Hxt N — среднее значение величины X. ы /
Иначе говоря, для вычисления СКО величины X следует выполнить следующие вычисления.
Вычислить среднее значение М(Х\
Вычислить сумму всех квадратов разностей (х, -М^Х^, т. е. отклонений значений величины X от их среднего значения
Л/(А).
Полученный результат поделить на N^N-\Y
Из полученного результата извлечь квадратный корень.
Допустим, у нас имеется список значений некоторой величины, например, числовой столбец х в таблице Измерения. Пример
такой таблицы показан на рис. 5.13. Требуется вычислить СКО значений этого столбца.
Рис. 5.13. Таблица с числовыми значениями
Нетрудно подсчитать, что среднее значение столбца х таблицы Измерения равно 7. Среднее значений столбца, как известно, можно получить с помощью функции avg (). Сначала посмотрим, каковы отклонения значений величины х от среднего значения. Для этого выполним следующий запрос:
SELECT Измерения.X, Х-М AS Отклонение_от_ср
FROM Измерения, (SELECT AVG(X) AS М FROM Измерения);
Результат данного запроса показан на рис. 5.14.
Рис. 5.14. Таблица отклонений значений столбца X от среднего значения
Обратите внимание на использование декартового произведения исходной таблицы на результат, возвращаемый подзапросом, который вычисляет среднее значение (avg(x)).
Однако в качестве промежуточного решения поставленной задачи больше подойдет следующий запрос:
SELECT SUM( (Х-М) * (Х-М) ) AS Сумма_кв_отклонений
FROM Измерения, (SELECT AVG(X) AS М FROM Измерения);
Для таблицы, показанной на рис. 5.13, данный запрос вернет 118. Заметим, что в качестве параметра функции sumo можно было бы передать power((х-м), 2), т. е. выражение, вызывающее функцию возведения в степень. Однако в Microsoft Access этот вариант не пройдет, поскольку Access использует вычислительные функции из арсенала языка VBA, а нс SQL.
Теперь осталось совсем немного сделать, чтобы получить среднеквадратическое отклонение. Для этого вам понадобятся операции определения количества значений, деления и извлечения квадратного корня (см. разд. 4.2.2). Попробуйте самостоятельно сформировать запрос на получение СКО значений столбца х таблицы Измерения.
Задача 5.2
В разд. 5.2.1 рассматривалась таблица Рейсы (начальный^пункт, конечныЙ-Пункт), содержащая сведения о том, из каких пунктов и в какие можно попасть с помощью того или иного авиарейса. На рис. 5.4 показан граф достижимости пунктов авиарейсами и соответствующая ему таблица. Каждой стрелке на графе и каждой записи таблицы соответствует некоторый рейс. Так, например, из пункта А можно попасть в пункт В посредством некоторого авиарейса, но из В в А авиарейса нет. Допустим, авиакомпания, осуществляющая перевозки по маршрутам, организовала для каждого прямого рейса соответствующий ему обратный рейс. Например, если в исходной таблице имеется запись (А, В), то в модифицированной таблице должна быть запись (В, А).
Сформулируйте запрос к исходной таблице Рейсы, возвращающий таблицу, содержащую сведения как о прямых, так и об обратных рейсах.
Задача 5.3
Для таблицы Рейсы (начальный_пункт, конечный_пункт), которая показана на рис. 5.4, сформулируйте запрос, который возвращает
таблицу со сведениями о достижимости пунктов, в которые можно попасть посредством не более двух пересадок. Решите эту задачу, не используя рекурсивных запросов.
Задача 5.4
Для таблицы Рейсы (НАЧАЛЬНЫЙ_ПУНКТ, КОНЕЧНЫЙ-ПУНКТ), которая показана на рис. 5.4, сформулируйте запрос, который возвращает список пунктов, в которые можно попасть как из пункта А, так и из пункта В.
Задача 5.5
Пусть имеется таблица Сотрудники (ю_сотрудника, Описание, ю_начальника), показанная на рис. 5.15. Эта таблица содержит СПИСОК идентификаторов (юсотрудника) всех сотрудников некоторой фирмы. Для каждого сотрудника указан идентификатор его непосредственного начальника (ю^начальника). Любое определенное значение столбца iозначальника обязательно встречается и среди значений столбца ю сотрудника, поскольку любой начальник является в то же время и сотрудником фирмы.
J Сотрудники: таблицаJ Сотрудники: таблицаII _с6трудникаII _с6трудникаОписание |!Р_начальникаОписание |!Р_начальника1 j Генеральный директор2 Главный бухгалтер3; Заместитель директора4^ Нам. транспортного отдела5Нач. отдела маркетингаНач. склада7j БухгалтерВодительАвтомеханик10-Слесарь11j Кладовщик1 j Генеральный директор2 Главный бухгалтер3; Заместитель директора4^ Нам. транспортного отдела5Нач. отдела маркетингаНач. склада7j БухгалтерВодительАвтомеханик10-Слесарь11j Кладовщик12 Сторож13 Секретарь14^ УборщицаJ0OI—™В(ЕЗ@12 Сторож13 Секретарь14^ УборщицаJ0OI—™В(ЕЗ@из 14из 1411333244466131133324446613
J Сотрудники: таблица
J Сотрудники: таблица
II _с6трудника
II _с6трудника
Описание |!Р_начальника
Описание |!Р_начальника
1 j Генеральный директор
2 Главный бухгалтер
3; Заместитель директора
4^ Нам. транспортного отдела
5Нач. отдела маркетинга
Нач. склада
7j Бухгалтер
Водитель
Автомеханик
10-Слесарь
11j Кладовщик
1 j Генеральный директор
2 Главный бухгалтер
3; Заместитель директора
4^ Нам. транспортного отдела
5Нач. отдела маркетинга
Нач. склада
7j Бухгалтер
Водитель
Автомеханик
10-Слесарь
11j Кладовщик
12 Сторож
13 Секретарь
14^ Уборщица
J0OI—™В(ЕЗ@
12 Сторож
13 Секретарь
14^ Уборщица
J0OI—™В(ЕЗ@
из 14
из 14
1
1
3
3
3
2
4
4
4
6
6
1
3
1
1
3
3
3
2
4
4
4
6
6
1
3
Рис. 5.15. Таблица со сведениями о субординации сотрудников
В общем случае некоторые сотрудники могут не иметь подчиненных. Таким образом, данная таблица содержит информацию о иерархии (субординации) сотрудников фирмы.
Сформулируйте запрос, возвращающий список всех сотрудников, которые непосредственно подчинены тому же начальнику, что и данный сотрудник. Если решить данную задачу, например, относительно водителя, то из таблицы на рис. 5.15 видно, что он непосредственно подчинен начальнику транспортного отдела и, следовательно, необходимо определить всех сотрудников этого отдела.
Сформулируйте запрос, возвращающий список всех сотрудников, которые подчинены (в том числе и опосредовано) тому же начальнику, что и данный сотрудник.
Глава 6
Добавление,удаление и изменение данных в таблицах
Запросы, рассмотренные в предыдущих главах, были направлены на то, чтобы получить данные, содержащиеся в существующих таблицах базы данных. Главным ключевым словом таких запросов на выборку данных является select. Запросы на выборку данных всегда возвращают виртуальную таблицу, которая отсутствует в базе данных и создается временно лишь для того, чтобы представить выбранные данные пользователю. При создании и дальнейшем сопровождении базы данных обычно возникает задача добавления новых и удаления ненужных записей, а также изменения содержимого ячеек таблицы. В SQL для этого предусмотрены операторы insert (вставить), delete (удалить) и update (изменить). Запросы, начинающиеся с этих ключевых слов, не возвращают данные в виде виртуальной таблицы, а изменяют содержимое уже существующих таблиц базы данных. Запросы на модификацию (добавление, удаление и изменение) данных могут содержать вложенные запросы на выборку данных из той же самой таблицы или из других таблиц (см. гл. 5), однако сами не могут быть вложены в другие запросы. Таким образом, операторы insert, delete и update в SQL-выражении могут находиться только в самом начале.
Добавление новых записей
Когда создается таблица базы данных, она не содержит никаких записей, т. е. является пустой. Чтобы наполнить таблицу данными, необходимо добавить (вставить) в нее хотя бы одну запись.
Для этого служит оператор insert (вставить), который имеет несколько форм:
О INSERT INTO имя Таблицы VALUES (списокЗначений) — вставляет пустую запись в указанную таблицу и заполняет эту запись значениями из списка, указанного за ключевым словом values. При этом первое в списке значение вводится в первый столбец таблицы, второе значение — во второй столбец и т. д. Порядок столбцов задается при создании таблицы. Данная форма оператора insert не очень надежна, поскольку нетрудно ошибиться в порядке вводимых значений. Более надежной и гибкой является следующая форма;
□ INSERT INTO имяТаблицы (списокСтолбцов) VMJ3ES (списокЗна- чений) — вставляет пустую запись в указанную таблицу и вводит в заданные столбцы значения из указанного списка. При ЭТОМ в первый столбец ИЗ списокСтолбцов ВВОДИТСЯ Первое значение из списокЗначений, во второй столбец — второе значение и т. д. Порядок имен столбцов в списке может отличаться от их порядка, заданного при создании таблицы. Столбцы, которые не указаны в списке, заполняются значением null. Иногда требуется просто добавить пустую запись. В этом случае первая форма оператора insert требует после ключевого слова values указать список значений null, длина которого равна количеству столбцов. Однако есть лучшее решение:
INSERT INTO имяТаблицы ^имяЛюбогоСолбца) VALUES (NULL) ;
Рекомендуется использовать именно данную форму оператора insert. Следующий запрос добавляет новую запись в таблицу Клиенты, при ЭТОМ В столбцы Имя, Телефон И Сум- ма_заказа ВВОДЯТСЯ значения ’Петров’, ’444-4444’ И 25300 соответственно:
INSERT INTO Клиенты (Имя, Телефон, Сумма_заказа)
VALUES (’Петров’, ’444-4444’, 25300);
Начиная с SQL-92, появилась возможность работать со значениями типа запись. Это позволяет за ключевым словом values указать несколько наборов значений в круглых скобках (записей), которые необходимо вставить в таблицу. Например:
INSERT INTO Клиенты (Имя, Телефон, Сумма_заказа)
VALUES
(’Петров’, *444-4444’, 25300),
(’Иванов*, *555-5555*, 45100),
(•Сидоров*, *777-7777*, 1200),
(•Захаров*, *123-9870*, 7800);
О insert into имятаблицы (списокСтолбцов') select ... — вставляет в указанную таблицу записи, возвращаемые запросом на выборку.
На практике нередко требуется загрузить в одну таблицу данные из другой таблицы. Например, ранее созданная таблица контакты содержит имена и другие данные о клиентах, которые необходимо вставить в таблицу клиенты. Для этого в операторе insert вместо values можно использовать оператор select, выбирающий записи, необходимые для вставки.
Допустим, таблицы клиенты и контакты имеют одноименные и однотипные столбцы Имя, Адрес и Телефон. Требуется добавить все записи ИЗ таблицы Контакты В таблицу Клиенты. Это можно сделать с помощью следующего запроса:
INSERT INTO Клиенты (Имя, Адрес, Телефон)
SELECT Имя, Адрес, Телефон FROM Контакты;
Следующий запрос вставляет в таблицу клиенты только те записи из таблицы контакты, которых в ней еще нет. Таким образом, происходит дополнение первой таблицы данными из второй таблицы:
INSERT INTO Клиенты (Имя, Адрес, Телефон)
SELECT Имя, Адрес, Телефон FROM Контакты
WHERE Клиенты.Имя <> Контакты.Имя AND
Клиенты.Адрес о Контакты.Адрес AND
Контакты.Имя IS NOT NULL;
Здесь в операторе where применено сложное условие на случай, если в таблицах окажутся однофамильцы или в таблице Контакты окажутся неопределенные имена.
С помощью оператора insert можно добавить одну или несколько записей только в одну таблицу. Кроме того, необходимо учитывать следующие обстоятельства:
таблица может иметь столбец типа serial (счетчик), имеющий уникальные значения, которые СУБД назначает автоматически. Это обеспечивает уникальность всех записей таблицы
и, следовательно, ее соответствие 1-ой нормальной форме. Поэтому в списке столбцов в операторе insert не следует указывать столбцы типа serial;
если список столбцов, указанный в операторе insert, содержит не все столбцы таблицы, то в оставшиеся столбцы (за исключением столбца типа serial) будут введены значения null (неопределенное значение), а в столбец типа serial правильное числовое значение введет СУБД;
при введении значений в столбцы новой записи необходимо следить, чтобы типы значений соответствовали типам столбцов таблицы. При необходимости можно воспользоваться функцией преобразования типов cast ();
таблица может иметь ограничения (см. разд. 7.1). Если вводимые данные не удовлетворяют условиям этих ограничений, то запрос на добавление данных не будет выполнен. Так, например, если в ограничении на какой-либо столбец указано, что он не может иметь значения null, то попытка добавить пустую запись приведет к ошибке. В этом случае необходимо использовать оператор insert into с ключевым словом values, чтобы сразу ввести значение, удовлетворяющее ограничениям.
Удаление записей
Для удаления записей из таблицы применяется оператор delete (удалить):
DELETE FROM имяТаблицы WHERE условие;
Данный оператор удаляет из указанной таблицы записи (а не отдельные значения столбцов), которые удовлетворяют указанному условию. Условие — это логическое выражение, различные конструкции которого были рассмотрены в предыдущих главах.
Следующий запрос удаляет записи из таблицы клиенты, в которой значение столбца Имя равно 1 Иванов ’:
DELETE FROM Клиенты WHERE Имя = *Иванов1;
Если таблица клиенты содержит несколько записей, в которых есть клиент Иванов, то все они будут удалены.
В операторе where может находиться подзапрос на выборку данных (оператор select). Подзапросы в операторе delete работают точно так же, как и в операторе select.
Пусть в базе данных имеется таблица продажи (ю товара, сум- ма_заказа, Ю клиента), содержащая сведения о продажах товаров клиентам. Требуется удалить из этой таблицы записи о тех клиентах, которые приобрели товары на сумму, меньшую 1000. Для этого можно использовать такой запрос:
DELETE FROM Продажи Т1
WHERE 1000 >=
(SELECT SUM (Т2. Сумма_заказа) FROM Продажи Т2
WHERE Т1.10_клиента= Т2.10_клиента);
Здесь запрос на удаление записей из таблицы продажи содержит связанный (коррелированный) подзапрос, вычисляющий сумму значений столбца сумма_заказа. Обратите внимание на использование двух различных псевдонимов для одной и той же таблицы. При выполнении данного запроса на удаление происходит, как и положено в случае связанных подзапросов, последовательный просмотр записей в таблице продажи. Для каждой записи проверяется условие оператора where, а именно выполняется Подзапрос, ВЫЧИСЛЯЮЩИЙ сумму Значений СТОЛбца Сумма_заказа для всех записей, в которых идентификатор клиента равен значению этого идентификатора в текущей записи (текущей называется запись, просматриваемая в данный момент). Если вычисленное значение не превышает 1000, то условие оператора where выполняется и текущая запись удаляется, в противном случае запись не удаляется. Далее происходит переход к следующей записи, рассматриваемой в качестве текущей, и описанные действия повторяются снова.
Теперь рассмотрим пример, в котором требуется удалить записи в одной таблице в соответствии с условием, обрабатывающим записи в другой таблице. Допустим, в базе данных имеются две таблицы:
О Продажи (10_товара,Сумма_заказа,ID_клиента);
□ Клиенты (lD—Клиента, Имя, Адрес).
Требуется удалить из таблицы клиенты те записи о клиентах, которые приобрели товары на сумму, меньшую 1000. Суммы заказов
клиентов содержатся в таблице продажи, а записи необходимо удалять из таблицы клиенты, в которой нет данных о приобретенных товарах. Тем не менее, для решения этой задачи можно использовать запрос, аналогичный предыдущему:
DELETE FROM Клиенты Т1
WHERE 1000 >=
(SELECT SUM (Т2. Сумма_заказа) FROM Продажи Т2
WHERE Т1.ID_клиента= Т2.ID_клиента);
Операция удаления записей из таблицы является опасной в том смысле, что связана с риском необратимых потерь данных в случае семантических (но не синтаксических) ошибок при формулировке SQL-выражения. Чтобы избежать неприятностей, перед удалением записей рекомендуется сначала выполнить соответствующий запрос на выборку, чтобы просмотреть, какие записи будут удалены. Так, например, перед выполнением рассмотренного ранее запроса на удаление не помешает выполнить соответствующий запрос на выборку:
SELECT * FROM Клиенты Т1
WHERE 1000 >=
(SELECT SUM (Т2. Сумма_заказа) FROM Продажи Т2
WHERE Т1.ID—клиента= Т2.ID—клиента);
Для удаления всех записей из таблицы достаточно использовать оператор delete без ключевого слова where. При этом сама таблица со всеми определенными в ней столбцами остается и готова для вставки новых записей. Например:
DELETE FROM Клиенты;
Внимание
Таблица может иметь ограничения (см. разд. 7.1), которые могут препятствовать удалению некоторых или всех записей.
Изменение данных
Для изменения значений столбцов таблицы применяется оператор update (изменить, обновить). Чтобы изменить значения в одном столбце таблицы в тех записях, которые удовлетворяют некоторому условию, следует выполнить такой запрос:
UPDATE имя Та блицы SET имяСтолбца = значение WHERE условие;
За ключевым словом set (установить) следует выражение равенства, в левой части которого указывается имя столбца, а в правой — выражение, значение которого следует сделать значением данного столбца. Эти установки будут выполнены в тех записях, которые удовлетворяют условию в операторе where.
Чтобы одним оператором update установить новые значения сразу для нескольких столбцов, вслед за ключевым словом set записываются соответствующие выражения равенства, разделенные запятыми:
UPDATE имяТаблицы
SET имяСтолбца! = значение!, имяСтолбца2 = значение2, . . . , имяСтолбцаМ = значением
WHERE условие;
Например, следующий запрос изменяет значения столбцов Телефон И Сумма_заказа В таблице Клиенты ДЛЯ тех записей, В которых столбец Имя имеет значение •Иванов 1:
UPDATE Клиенты
SET Телефон = 1333-1234*, Сумма_заказа = 2570
WHERE Имя = *Иванов*;
Использование оператора where в операторе update не обязательно. Если он отсутствует, то указанные в set изменения будут произведены для всех записей таблицы.
Операция изменения записей, как и их удаление, связана с риском необратимых потерь данных в случае семантических ошибок при формулировке SQL-выражения. Например, стоит только забыть написать оператор where, и будут обновлены значения во всех записях таблицы. Чтобы избежать подобных неприятностей, перед обновлением записей рекомендуется выполнить соответствующий запрос на выборку, чтобы просмотреть, какие записи будут изменены. Например, перед выполнением приведенного ранее запроса на обновление данных не помешает выполнить соответствующий запрос на выборку данных:
SELECT * FROM Клиенты
WHERE Имя = ’Иванов *;
Внимание
Таблица может иметь ограничения (см. разд. 7.1). Если устанавливаемые значения столбцов не удовлетворяют условиям этих ограничений, то запрос на обновление данных выполнен не будет.
Условие в операторе where может содержать подзапросы, в том числе и связанные. Пусть в базе данных имеется таблица продажи (lD_TOBapa, Сумма_заказа, 10_клиента), содержащая сведения о продажах товаров клиентам. Предположим, что требуется сделать 5% скидку тем клиентам, которые приобрели товары на сумму, большую 1000. Для этого следует изменить значения столбца сумма заказа, просто умножить их на 0,95. Однако эти изменения должны быть выполнены, только если сумма значений этого поля для данного клиента превышает 1000. Таким образом, запрос на изменение данных должен содержать связанный подзапрос:
UPDATE Продажи Т1
SET Сумма_заказа = Сумма_заказа*0.95
WHERE 1000 <
(SELECT SUM (Т2. Сумма_заказа) FROM Продажи Т2
WHERE Т1.10_клиента = Т2. Ю_клиента) ;
Здесь запрос на изменение данных из таблицы продажи содержит связанный (коррелированный) подзапрос, вычисляющий сумму значений столбца сумма заказа. Обратите внимание на использование двух различных псевдонимов для одной и той же таблицы. При выполнении данного запроса происходит последовательный просмотр записей в таблице продажи. Для каждой записи проверяется условие оператора where, а именно выполняется подзапрос, ВЫЧИСЛЯЮЩИЙ сумму значений столбца Сумма заказа для всех записей, в которых идентификатор клиента равен текущему значению этого идентификатора. Если вычисленное значение превышает 1000, то условие оператора where выполняется и происходит изменение данных в соответствии с выражением set, в противном случае изменения не вносятся. Далее происходит переход к следующей записи, рассматриваемой в качестве текущей, и описанные действия повторяются снова.
Нередко требуется обновить значения столбцов в зависимости от их текущих значений. В SQL:2003 для этого можно использовать оператор case, возвращающий значения (см. разд. 4.4).
Допустим, имеется таблица Клиенты (Имя, Адрес, Регион, Телефон). Требуется изменить значения столбца Регион следующим образом: если значение столбца равно •северо-запад1, то его следует заменить на ’Санкт-Петербург1; если значение равно •тверская область •, то его следует заменить на •Тверь •; во всех остальных случаях нужно оставить прежние значения. Эту задачу можно решить с помощью такого запроса:
UPDATE Клиенты
SET Регион = CASE
WHEN Регион = ’Северо-запад’ THEN ’Санкт-Петербург’ WHEN Регион = ’Тверская область’ THEN ’Тверь’
ELSE Регион
END;
Без оператора case данную задачу пришлось бы решать с помощью двух последовательных запросов:
UPDATE Клиенты
SET Регион = ’Санкт-Петербург’ WHERE Регион ^ ’Северо- запад ’;
UPDATE Клиенты
SET Регион = ’Тверь’ WHERE Регион = ’Тверская область’;
Допустим, что в таблице клиенты было решено изменить значение ’Иркутск’ столбца Регион. Однако новое значение пока не определено и поэтому вместо ’Иркутск’ решили внести значение null. Так обычно поступают, чтобы обозначить тот факт, что значение не известно или еще не введено. В данном случае удобно применить запрос с использованием функции nullifo (см. разд. 4.4.3);
UPDATE Клиенты
SET Регион = NULLIF(Регион, ’Иркутск’);
При изменении значений в столбцах таблицы необходимо следить, чтобы типы значений соответствовали типам столбцов. При необходимости можно воспользоваться функцией преобразования типов cast ().
Примечание
Некоторые базы данных (например, PostgreSQL) имеют расширение стандарта SQL, позволяющее обновлять одну таблицу данными
из другой. Так, например, чтобы обновить таблицу клиенты данными из таблицы продажи, можно использовать такое выражение:
UPDATE Клиенты SET Сумма_заказа = Продажи. Сумма_заказа FROM Продажи
WHERE Ю_клиента = 5;
Проверка ссылочной целостности
Между таблицами в базе данных могут быть установлены связи. Эти связи задаются как ограничения ссылочной целостности данных. Что такое ссылочная целостность данных, было рассказано в разд. 1.4.3, а про установку связи между таблицами будет описано в разд. 7.1. При попытке добавить, изменить или удалить записи в связанных таблицах могут возникнуть так называемые аномалии модификации данных, обусловленные нарушением ссылочной целостности.
Целостность данных может быть нарушена при попытке добавить запись в дочернюю таблицу, для которой нет соответствующей записи в родительской таблице. Например, в базе данных имеется родительская таблица Клиенты (ИмяКлиента, Имя, Адрес, Телефон) И дочерняя таблица Продажи (ю_заказа, Товар, Цена, Количество, ИмяКлиента). В таблице Клиенты Столбец ИмяКлиента является первичным ключом (имеет уникальные и определенные значения), а в таблице продажи столбец ИмяКлиента не обязан иметь уникальные значения, поскольку один и тот же клиент может сделать несколько различных покупок. Чтобы эти две таблицы находились В СВЯЗИ, столбец ИмяКлиента в таблице продажи должен быть внешним ключом, ссылающимся на первичный ключ ИмяКлиента в таблице клиенты. О внешних ключах более подробно будет рассказано в разд. 7.1.3.
Если вы добавите в таблицу продажи новую запись, содержащую значение столбца ИмяКлиента, которого еще нет в родительской таблице клиенты, то возникнет аномалия модификации данных. Аналогичная ситуация произойдет, если вы попытаетесь удалить запись из таблицы клиенты: в дочерней таблице продажи некоторые записи могут ссылаться на клиентов, сведения о которых отсутствуют.
Поэтому вначале следует добавить запись в таблицу клиенты с соответствующим значением столбца Имяклиента, а затем — в продажи или, в случае с удалением, вначале нужно удалить соответствующие записи из таблицы продажи, а затем — из клиенты.
В ряде случаев перед добавлением или удалением записей можно проверить, не приведет ли это к нарушению ссылочной целостности. Такую проверку можно выполнить с помощью предиката MATCH.
Предикат match имеет следующий синтаксис:
ЗначениеТипаЗаписи ROW MATCH [UNIQUE] [SIMPLE | PARTIAL | FULL] (подзапрос) ;
Здесь в квадратных скобках указаны необязательные ключевые слова, а вертикальной чертой разделены их альтернативные варианты: UNIQUE (уникальный), SIMPLE (прОСТОЙ), PARTIAL (частичный), full (полный). Эти ключевые слова определяют правила обработки значений типа записи с полями, имеющими неопределенные значения.
Допустим, требуется определить, есть ли в таблице Продажи запись (’Иванов’, ’Компьютер’). Для этого можно выполнить такой запрос к базе данных:
SELECT * FROM Продажи
WHERE (’Иванов’, ’Компьютер’)
MATCH (SELECT ИмяКлиента, Товар FROM Продажи);
Если в таблице продажи есть запись с указанным именем клиента и товаром, то предикат match вернет значение true.
Глава 7
Создание и модификация таблиц
Создание и модификация таблиц базы данных обычно производятся специальными диалоговыми инструментами сред разработки, которые поставляются вместе с СУБД. Хотя при их использовании, как правило, не требуется написания программ, эти средства включают в себя языки программирования, с помощью которых можно создавать сложные приложения, ориентированные на работу с базами данных. Примерами являются dBase, Paradox, Delphi, Access и др. Вместе с тем язык SQL также обладает средствами создания таблиц базы данных, т. е. тех таблиц, которые сохраняются в составе базы данных в долговременной памяти компьютера, например, на жестком диске. Напомню, что таблицы, возвращаемые запросами на выборку данных, являются виртуальными и доступны только тому, кто инициировал эти запросы.
В данной главе будут рассмотрены создание и модификация таблиц базы данных, временных таблиц, а также представлений.
Создание таблиц
Создание таблицы производится с помощью оператора create table (создать таблицу) с указанием необходимых параметров. При этом создается постоянная таблица. Чтобы удалить ее из базы данных, требуется выполнить специальный SQL-оператор. Для создания временной таблицы используется оператор create temporary table (создать временную таблицу). Временная таблица, в отличие от постоянной, существует только в течение
7 Зак. 120
сеанса работы с базой данных, в котором она была создана. Однако временная таблица может быть доступна другим пользователям, как и постоянная таблица. Обычно временные таблицы создаются для представления в них текущих итоговых (отчетных) данных, доступных нескольким пользователям базы данных. Далее приведен синтаксис оператора create:
CREATE [TEMPORARY] TMLE имяТаблицы (
{ Столбец! тип [ (размер) ] (ограничение—столбца! [, ...] }
{ [ , CONSTRAINT ограничение—таблицы"! [ л - • • 1 }
Здесь квадратные и фигурные скобки, в отличие от круглых, не являются элементами синтаксиса. В квадратных скобках заключены необязательные элементы, а в фигурных — элементы, которые могут повторяться.
Для создания таблицы необходимо указать ее имя и определить столбцы. Определение столбца включает его имя и тип. Если указывается длина столбца, то она заключается в круглые скобки после типа. Кроме того, можно указать ограничения для столбца. Все перечисленные элементы определения столбца указываются друг за другом через пробел. Если создаваемая таблица содержит несколько столбцов, то их определения разделяются запятыми. Ограничение может быть определено и для всей таблицы, а не только для ее столбцов. В этом случае используется ключевое слово constraint (ограничение), после которого указывается само ограничение.
Только что созданная таблица пуста, т. е. не содержит ни одной записи. Для наполнения ее данными следует воспользоваться оператором insert into (см. разд. 6.1).
Далее приведен запрос на создание простой таблицы без ограничений:
CREATE TABLE Студент (
10_студентаINTEGER,
ФИОCHAR(20),
Специальность CHAR(15),
Примечание VARCHAR
Данный запрос создает таблицу Студент, содержащую четыре столбца. Первый столбец целочисленный, а три других — символьные.
Ограничения устанавливаются на данные, которые вводятся в таблицу. Например, на числовой столбец можно наложить ограничение, заключающееся в том, что вводимое число должно находиться в некотором диапазоне. Другой пример ограничения: значение столбца не должно быть неопределенным.
Нередко ограничения устанавливаются и отслеживаются в приложениях, работающих с базами данных. Тем не менее, ограничения могут быть установлены и могут поддерживаться многими современными СУБД. Если вы создаете таблицу посредством SQL, то также имеете возможность задать эти ограничения, а выполнять их будет СУБД. При этом если одна и та же база данных используется несколькими приложениями, то вам придется установить ограничения только один раз, а не столько, сколько имеется приложений.
Ограничения для столбцов
В табл. 7.1 приведены основные ограничения для столбцов. Однако существуют и более сложные ограничения, которые в этой книге не рассматриваются.
Таблица 7.1. Ограничения для столбцов
Определение
Описание
NOT NULL
Столбец не может содержать значение null, т. е. значения этого столбца должны быть определенными
UNIQUE
Значение, вводимое в столбец, должно отличаться от всех остальных значений в этом столбце, т. е. быть уникальным
PRIMARY KEY
Столбец является первичным ключом. В каждой таблице только один столбец может быть первичным ключом. Это означает, что он не может содержать значение NULL, а вводимое в него значение должно отличаться от всех остальных значений в этом столбце. Таким образом, primary key является комбинацией null и unique
Таблица 7.1 (окончание)
Определение
Описание
DEFAULT значение
Устанавливает значение по умолчанию. Так, при добавлении новой записи столбец с таким ограничением автоматически получит указанное значение
CHECK (условие)
Позволяет производить проверку условия при вводе данных. Значение будет сохранено, если условие выполняется, в противном случае — нет
Примечание
Microsoft Access не поддерживает ключевые слова default и check в определениях ограничений.
Рассмотрим в качестве примера создание таблицы Студент (1В_студента, ФИО, Специальность, Примечание). Идентификатор студента (целочисленный столбец 1В_студента) должен однозначно идентифицировать запись о студенте, т. е. иметь определенные и уникальные значения. Таким образом, данный столбец должен быть первичным ключом. От столбца фио (фамилия, имя и отчество) потребуем, чтобы в нем не было неопределенных значений. Целочисленный столбец Специальность должен содержать номера специальностей, которые не превышают 12. Тогда запрос на создание такой таблицы будет иметь вид:
CREATE TABLE Студент (
ID—студентаФИОID—студентаФИОINTEGER PRIMARY KEY, CHAR(20) NOT NULL,
ID—студента
ФИО
ID—студента
ФИО
Специальность INTEGER CHECK(Специальность < 12),
Примечание VARCHAR
);
Предположим, что данный запрос выполнен. Тогда следующий запрос на добавление новой записи в таблицу студент вызовет сообщение об ошибке и не будет выполнен:
INSERT INTO Студент;
Это произойдет потому, что данный запрос добавляет пустую запись, все столбцы которой содержат значение null, и, следо-
вательно, не выполняются ограничения для первых двух столбцов. А следующий запрос, добавляющий в таблицу первую запись и устанавливающий определенные значения для столбцов, не вызовет проблем со стороны СУБД:
INSERT INTO Студент (1В_студента, ФИО, Специальность)
VALUES (1, ’Иванов Иван Иванович’, 9);
Попытаемся вслед за данным запросом добавить еще одну запись:
INSERT INTO Студент (1и_студента, ФИО, Специальность)
VALUES (1, ’Петров Петр Петрович’, 5) ;
Данный запрос не будет выполнен из-за нарушения ограничения, наложенного на первый столбец: его значения должны быть не только определенными (отличными от null), но и уникальными. Запись можно добавить, если столбцу 1П_студента присвоить, например, значение 2.
Ограничения для столбцов можно выразить и иначе — через ограничения доменов. Напомню, что домен определяется как множество значений (см. разд. 1.2.2). Домен в реляционной теории связывается с атрибутом отношения и, как таковой, определяет некоторое ограничение на этот атрибут (атрибут может принимать значения только из этого домена). В SQL можно создать домен, сначала не связанный ни с какими атрибутами (столбцами), определив для него допустимые значения: тип данных и дополнительные ограничения. Затем этот домен можно задать в качестве ограничений для любого столбца в любой таблице подобно тому, как задаются типы столбцов. Другими словами, домен можно связать с одним или несколькими столбцами различных таблиц. В ряде случаев этот прием очень удобен, особенно если у вас имеется несколько "однотипных" столбцов в различных таблицах базы данных.
Чтобы создать домен, используется такой синтаксис:
CREATE DOMAIN имяДомена тицЦанных Ограничения;
Например, таблицу Студент (ю_студента, ФИО, Специальность, Примечание) можно определить традиционным образом:
CREATE TABLE Студент (
ID_студентаINTEGER PRIMARY KEY,
ФИОCHAR(20) NOT NULL,
Специальность INTEGER CHECK (Специальность < 12),
Примечание VARCHAR
);
Однако можно сначала определить домен с именем specDomain, задав для него тип данных и ограничение:
CREATE DOMAIN specDomain INTEGER
CHECK (Специальность < 12);
Обратите внимание, что в определении домена, пока не связанного ни с каким столбцом какой-либо таблицы, задается тип значений домена и ограничение на эти значения.
С учетом того, что домен specDomain создан, определение таблицы Студент можно задать следующим образом:
CREATE TABLE Студент (
10_студентаФИО10_студентаФИОINTEGER PRIMARY KEY,CHAR(20) NOT NULL,
10_студента
ФИО
10_студента
ФИО
Специальность specDomain,
Примечание VARCHAR
Рассмотрим еще один пример. Предположим, в нескольких таблицах вашей базы данных имеется столбец с именем код продукта и типом данных char(6), значения которого должны начинаться с символа ’а1, ’с* или ’х1. Для таких столбцов, раз их несколько, можно предварительно создать общий домен:
CREATE DOMAIN ProdDomain CHAR (6)
CHECK (SUBSTRING (VALUE, 1, 1) IN (’А1, ’С’, ’X’));
После определения домена можно создать таблицу, использующую этот домен:
CREATE TABLE Товары (
Код_продукта ProdDomain,
Описание VARCHAR
);
Ограничения для таблиц
Ограничения на вводимые данные можно назначить не только для отдельных столбцов, но и для таблицы в целом. Это удобно в тех случаях, когда необходимо для нескольких столбцов назна-
чить одинаковые ограничения. Кроме того, если первичный ключ составной (т. е. состоит из нескольких столбцов), то указать его можно только как ограничение для таблицы, а не для столбца. Все определение ограничения для таблицы указывается после определений столбцов и состоит из ключевого слова constraint, за которым следует выражение, определяющее непосредственно само ограничение. В табл. 7.2 приведены основные ограничения для таблицы.
Таблица 7.2. Ограничения для таблицы
Определение
Описание
UNIQUE
(списокСтолбцов^
Значения в столбцах, указанных в списке, должны быть уникальными
PRIMARY KEY
(списокСтолбцов^
В каждой таблице должен быть только один первичный ключ, который определен либо как ограничение для столбца, либо как ограничение для таблицы. Если первичный ключ составной, то он должен быть определен как ограничение для таблицы. Справа от ключевых слов primary KEY в круглых скобках указывается список столбцов, определяющих составной первичный ключ
CHECK (условие)
Позволяет производить проверку условия при вводе данных. Значение будет сохранено, если условие выполняется, в противном случае — нет. В отличие от ограничения для столбца, здесь можно использовать условия, оперирующие значениями различных столбцов таблицы
FOREIGN KEY ...
REFERENCES . . .
Ограничение типа "внешний ключ" (см. разд. 7.1.3)
В следующем Примере создается таблица Студент (Фамилия, Имя, Отчество, Специальность, примечание). Предполагается, что комбинация значений первых трех столбцов должна однозначно
идентифицировать запись в таблице, т. е. являться первичным ключом.
CREATE TABLE Студент (
ФамилияCHAR(20),
ИмяCHAR(15),
ОтчествоCHAR(20),
Специальность INTEGER,
ПримечаниеVARCHAR,
CONSTRAINT PRIMARY KEY (Фамилия, Имя, Отчество) ) ;
Следующие два запроса на добавление записей будут выполнены, поскольку комбинации значений столбцов, определяющих первичный ключ, не содержат null и отличаются друг от друга:
INSERT INTO Студент (Фамилия, Имя, Отчество, Специальность) VALUES (•Петров1, •Петр1, •Петрович1, 5);
INSERT INTO Студент (Фамилия, Имя, Отчество, Специальность) VALUES (•Петров*, •Петр1, •Иванович*, 5);
Внешние ключи
Одна из важнейших разновидностей ограничений связана с определением внешних ключей. Внешний ключ — это столбец или группа столбцов, соответствующих первичному ключу другой таблицы. Чтобы понять синтаксис выражения, определяющего внешний ключ, рассмотрим пример.
Пусть в базе данных имеются две таблицы:
Заказы (ю_заказа, Ю клиента) — содержит сведения О ТОМ, какие заказы сделал тот или иной клиент;
О Клиенты (ю_клиента, Имя, Адрес, Телефон) — сведения О клиентах (справочник).
В таблице клиенты столбец ю_клиента является первичным ключом, т. е. его значения отличны от null и уникальны. В таблице Заказы столбец ю_клиента не обязан иметь уникальные значения, поскольку один и тот же клиент может сделать несколько заказов. Вместе с тем любому значению столбца Заказы. Ю клиента соответствует единственное значение столбца клиенты.ю клиента. При описанных условиях столбец
1П_клиента таблицы Заказы ЯВЛЯеТСЯ ВНСШНИМ КЛЮЧОМ, ССЫлающимся на первичный КЛЮЧ 10_клиента таблицы Клиенты.
Внешний ключ определяется как ограничение для таблицы в выражении с ключевыми словами constrain foreign key (ограничение "внешний ключ"):
CONSTRAINT FOREIGN KEY внешнийКлюч REFERENCES внешняя Та блица (первичныйКлюч)
Здесь внешнийКлюч — имя столбца или список столбцов, разделенных запятыми, которые определяют внешний ключ, за ключевым словом references (ссылки) указывается внешняя таблица и ее первичный ключ, на который ссылается внешний ключ.
Для рассмотренного ранее примера таблицу Заказы можно определить следующим образом:
CREATE TABLE Заказы (
ID-Заказа INTEGER,
10_клиента INTEGER,
CONSTRAINT FOREIGN KEY 1П_клиента REFERENCES Клиенты (1В_клиента)
);
Использование внешних ключей обеспечивает сохранение ссылочной целостности базы данных при изменении и удалении записей. Если бы таблицы Заказы и Клиенты не были связаны, ТО при удалении записи ИЗ таблицы Клиенты В таблице Заказы могли остаться ссылки на клиента, о котором уже нет сведений. Этот факт обычно расценивается как аномалия удаления. В случае определения в таблице Заказы внешнего ключа ю_клиента из таблицы клиенты не удастся удалить клиента, если он сделал хотя бы один заказ. Если требуется удалить из базы данных все, что касается определенного клиента, то сначала удаляются записи в таблице Заказы, а ПОТОМ — В таблице Клиенты.
Аналогичная ситуация может произойти и при обновлении данных. Например, в таблице клиенты вы изменили идентификатор клиента, имеющего заказы и, таким образом, внешнему ключу теперь не на что ссылаться. Это аномалия изменения. В данном случае необходимо сначала добавить новую запись в таблицу клиенты, указав в ней необходимое значение 1о_клиента, затем изменить в таблице Заказы все старые значения 1в_клиента на то,
которое вы только что ввели в новой записи таблицы клиенты, а затем удалить из таблицы клиенты запись со старым идентификатором клиента.
Чтобы в таблицах, связанных внешним ключом, не делать модификацию данных в несколько этапов, в выражении constrain foreign key можно использовать дополнительные ключевые слова:
on delete cascade | set null (при удалении каскадировать | установить null);
ON UPDATE CASCADE | SET NULL (при обновлении КаСКЗДИрО- вать | установить null).
Здесь вертикальная черта не является элементом синтаксиса, а лишь разделяет возможные варианты ключевых слов.
Так, при использовании on delete cascade в случае удаления записи со значением первичного ключа, которое имеется во внешнем ключе другой таблицы, соответствующие записи удаляются автоматически из двух таблиц. Например, при удалении из таблицы клиенты записи о клиенте, имеющем заказы, в таблице Заказы также будут удалены все записи, ссылающиеся на данного клиента. Чтобы данная стратегия выполнялась, таблица Заказы должна быть определена следующим образом:
CREATE TABLE Заказы (
10_заказа INTEGER,
10_клиента INTEGER,
CONSTRAINT FOREIGN KEY 10_клиента REFERENCES Клиенты (10_клиента)
ON DELETE CASCADE
Однако на практике обычно предпочитают сначала убедиться, что клиент не имеет заказов, и лишь затем вычеркнуть его из справочника.
Вариант set null обычно используется при обновлении данных. Например:
CREATE TABLE Заказы (
ID—Заказа INTEGER,
1В_клиента INTEGER,
CONSTRAINT FOREIGN KEY 10_клиента REFERENCES Клиенты (ID—Клиента)
ON UPDATE SET NULL
В данном случае при изменении (в том числе и при удалении) в таблице клиенты записи, на которую ссылается внешний ключ таблицы Заказы, значения внешнего ключа устанавливаются в null. Однако этот вариант не сработает, если на столбец Заказы, ю клиента наложено ограничение not null. Обычно так и бывает, поскольку при оформлении заказа клиент должен быть обязательно указан. Поэтому, на всякий случай, лучше использовать ключевые слова on update cascade.
Примечание
Некоторые СУБД (например, PostgreSQL) допускают комбинирование дополнительных ключевых слов. Например:
ON UPDATE SET NULL ON DELETE CASCADE
Удаление таблиц
Удалить таблицу из базы данных можно следующим образом:
DROP TABLE имяТаблицы;
Разумеется, при удалении таблицы теряются и все содержащиеся в ней данные. Во время работы с базой данных нередко создаются таблицы для временного хранения данных, полученных на каком-то промежуточном этапе. Рано или поздно такие таблицы подлежат удалению. Однако можно забыть это сделать. Кроме того, промежуточные таблицы, создаваемые приложениями, могут остаться в базе данных из-за сбоев. Поэтому для создания временных таблиц лучше использовать оператор create TEMPORARY TABLE (СМ. разд. 7.1), а НС CREATE TABLE. НаПОМНЮ, что временная таблица, созданная с помощью create temporary table, автоматически уничтожается по окончании сеанса работы с базой данных.
Модификация таблиц
Разработка базы данных обычно происходит итеративно. Редко когда удается сразу оптимально определить структуру входящих в нее таблиц, чтобы потом их не переделывать. Кроме того, уже готовая база данных со временем может пополняться новыми таблицами, которые связываются с уже имеющимися. А установка новых связей требует коррекции параметров в старых таблицах. Модификация таблицы — это изменение ее структуры, т. е. добавление, удаление и переименование столбцов, а также изменение их типов и размеров.
Язык SQL обладает специальными средствами модификации таблиц. Если бы их не было, или если отказаться их применять, то изменение структуры таблицы можно выполнить следующим образом:
Создать новую рабочую таблицу с необходимой структурой.
С помощью оператора insert into вставить в рабочую таблицу данные из исходной таблицы.
Удалить исходную таблицу.
Переименовать рабочую таблицу, присвоив ей имя исходной.
Все предыдущие операции можно выполнить посредством рассмотренных ранее операторов SQL. Переименование таблицы можно сделать средствами программной среды разработки базы данных. Если же ограничиться только операторами SQL, то вместо переименования придется выполнить следующее:
Создать новую таблицу с именем исходной и с такой же структурой, как у рабочей таблицы.
С помощью оператора insert into вставить в новую таблицу данные из рабочей таблицы.
Удалить рабочую таблицу.
Как видите, изменение структуры таблицы обычными операторами SQL довольно трудоемко. Значительно проще изменить структуру таблицы с помощью оператора alter table (изменить таблицу). С помощью дополнительных ключевых слов можно выполнить следующие операции:
add column — добавить столбец;
drop column — удалить столбец;
alter column — изменить тип, размер и ограничение столбца;
rename column — переименовать столбец;
rename то — переименовать таблицу.
Типичной задачей изменения структуры таблицы является добавление столбца. Это можно сделать с помощью SQL-выражения с таким синтаксисом:
ALTER TABLE имяТаблицы ADD COLUMN имяСтолбца тип (размер)
Например:
ALTER TABLE Студенты
ADD COLUMN Адрес CHAR(25);
Добавленный столбец оказывается последним в таблице, т. е. занимает крайнюю правую позицию. Иногда это неудобно. Пусть, например, столбец имя в таблице занимает первую позицию, а ранее забытый и позднее добавленный столбец Фамилия —- десятую позицию. Чтобы выборка данных из этой таблицы выглядела привычным образом, приходится специально указывать необходимое расположение столбцов в операторе select, например, SELECT Имя, Фамилия FROM ...
Выражение add column может использоваться в выражении alter table несколько раз — для каждого отдельного столбца. При определении параметров добавляемого столбца можно указать ограничение для него. Следующее SQL-выражение добавляет в таблицу Студенты столбец id_ct и объявляет его первичным ключом:
ALTER TABLE Студенты
ADD COLUMN ID_CT INTEGER PRIMARY KEY;
He следует забывать, что первичный ключ в таблице может быть только один. Если в таблице уже есть первичный ключ, то добавление столбца как первичного ключа не будет выполнено. Если вам требуется переназначить первичный ключ, то сначала необходимо скорректировать соответствующим образом параметры уже имеющегося столбца, объявленного как первичный ключ, а затем добавить новый столбец с параметром primary key. Подробнее об ограничениях было рассказано в разд. 7.1. Коррекцию столбца можно выполнить так: либо сначала удалить
его, а затем добавить новый столбец с требуемыми параметрами либо изменить параметры существующего столбца с помощью оператора alter column.
Чтобы удалить столбец из таблицы, достаточно выполнить SQL- выражение со следующим синтаксисом:
ALTER TABLE имяТаблицы DROP COLUMN имяСтолбца;
При удалении столбца удаляются все содержащиеся в нем данные. Если удаляемый столбец является первичным ключом, на который ссылается внешний ключ из другой таблицы, то удаление не будет выполнено. В этом случае вам придется сначала скорректировать данные в другой таблице.
Например, следующее выражение удаляет из таблицы студенты столбец Примечание:
ALTER TABLE Студенты
DROP COLUMN Примечание;
Для изменения параметров существующего столбца, таких как тип, размер и ограничение, применяются ключевые слова alter column:
ALTER TABLE имяТаблицы ALTER COLUMN имяСтолбца тип (размер) [ограничение];
Здесь квадратные скобки указывают, что заключенное в них выражение не является обязательным.
В следующем примере в таблице студент уже существующий столбец фио приобретает новые параметры. А именно, будучи символьным, он получает увеличение длины до 50 символов и становится первичным ключом:
ALTER TABLE Студенты
ALTER COLUMN ФИО CHAR(50) PRIMARY KEY;
Разумеется, если в таблице уже имеется первичный ключ (например, столбец хв студента), то сделать первичным ключом еще один столбец не удастся. Кроме того, не следует забывать, что при преобразовании типов могут быть потеряны данные. Так, если вы преобразуете столбец символьного типа, содержащий фамилии или адреса, в числовой тип, то все данные будут потеряны. При уменьшении размера символьного столбца его значения могут оказаться обрезанными справа. Таким образом,
следует очень внимательно изменять параметры столбцов, содержащих некоторые данные.
Переименовывать столбцы приходится редко, поскольку в выборках и представлениях всегда можно присвоить им нужные псевдонимы. Тем не менее, переименовать столбцы и таблицу можно с помощью оператора alter table с ключевыми словами rename column ... то (переименовать столбец ... в).
Для переименования столбца используется следующий синтаксис:
ALTER TABLE имяТаблицы RENAME COLUMN имяСтолбца ТО новоеИмяСтолбца;
При переименовании столбца его прежний тип, размер и ограничение сохраняются.
Для переименования таблицы используется похожий синтаксис:
ALTER TABLE имяТаблицы RENAME ТО новоеИмяТаблицы;
Изменение структуры таблицы является небезопасной операцией с точки зрения возможности потери данных. Если вы не вполне уверены, что получите требуемый результат, то лучше использовать резервное копирование данных, поступая следующим образом:
Создайте временную таблицу с помощью оператора create TEMPORARY TABLE.
Скопируйте во временную таблицу данные из таблицы, структуру которой вы собираетесь модифицировать. Это можно сделать с помощью оператора insert into.
Далее можно поступить двумя способами:
изменить структуру исходной таблицы так, как вам требуется. Этот способ лучше применять при коррекции структуры, не связанной с изменениями существующих ограничений на столбцы и/или на таблицу в целом;
удалить исходную таблицу и создать новую под тем же именем и с требуемой структурой. Я предпочитаю использовать этот способ, если требуется изменить ограничения.
Скопируйте данные из временной таблицы в таблицу с новой структурой, используя оператор insert into. Больше временная таблица не нужна, она будет автоматически удалена по окончании сеанса работы с базой данных.
Рассмотрим Пример. Пусть Имеется Таблица Студенты (Ю студента, фио, Специальность), в которой первичным ключом является ФИО. Требуется изменить первичный ключ, а именно сделать первичным ключом столбец ю студента. Кроме того, необходимо в столбце фио запретить неопределенные значения, а также добавить столбец примечание неограниченной длины. Чтобы решить эту задачу, можно выполнить следующую последовательность запросов:
CREATE TEMPORARY TABLE stud_tmp (
1В_студента INTEGER,
ФИО CHAR(20) ,
Специальность CHAR(15)
) г
INSERT INTO stud_tmp
SELECT * FROM Студенты;
DROP TABLE Студенты;
CREATE TABLE Студенты (
1В_студента INTEGER PRIMARY KEY,
ФИО CHAR(20) NOT NULL,
Специальность CHAR(15),
Примечание VARCHAR
);
INSERT INTO Студенты
SELECT * FROM stud_tmp;
Представления
Что такое представление
База данных состоит из таблиц — объектов, содержащих данные и существующих в долговременной памяти компьютера, например, на жестком диске. Это могут быть отдельные файлы или части одного файла (как, например в Microsoft Access). С помощью запросов на выборку (SQL-выражений, начинающихся с ключевого слова select) создаются виртуальные таблицы, являющиеся временными и доступными только тому, кто выполнил данный запрос.
Представления — это тоже виртуальные таблицы, но они могут быть доступны многим пользователям и существуют в базе данных до тех пор, пока не будут принудительно удалены. Они во всем похожи на обычные таблицы базы данных, за исключением того, что не являются физическими объектами хранения данных. Данные в представлениях, подобно в ответах на запрос select, просто выбираются из таблиц базы данных, т. е. представляются в том или ином виде. Представления создаются с помощью оператора create view (создать вид, представление), но в действительности за ними стоят скрытые SQL-запросы. Эти запросы или параметры представления хранятся в разделе метаданных базы. Работать с представлением можно как с обычной таблицей. Однако любой запрос к представлению в действительности инициирует скрытый запрос, который комбинируется с пользовательским.
Зачем нужны представления? Обычно они применяются, чтобы скрыть от пользователя некоторые столбцы, скомбинировать из нескольких таблиц одну, которая часто нужна пользователю, а запрос для ее получения довольно сложен. Так, например, база данных может содержать ряд служебных таблиц, с помощью которых она приобретает полезные свойства. Смысл этих таблиц может быть неясен пользователю, но для получения требуемых данных они должны использоваться в запросах. Это обстоятельство может существенно затруднить формирование SQL- выражения запроса. Таким образом, представления используются как надстроечные средства для адаптации базы данных к различным категориям пользователей.
Рассмотрим в качестве примера простую базу данных, состоящую из трех таблиц:
Товары (1О_товара, Наименование, Цена, Описание);
Клиенты (ю_клиента, Имя, Адрес, Телефон);
Продажи (10_товара, Количество, Ю_клиента).
Примеры этих таблиц показаны на рис. 7.1. С точки зрения организации хранения данных такая база неплохо спроектирована. Однако менеджера по продажам могут не интересовать идентификаторы товаров и клиентов. Скорее всего, ему нужна будет ТОЛЬКО ОДНа таблица, например, Обзор_продаж (Товар, Количество,
Цена, Стоимость, Клиент). Разумеется, эту таблицу можно получить из исходных трех таблиц с помощью следующего запроса:
SELECT Товары.Наименование AS Товар, Продажи.Количество, Товары.Цена, Продажи.Количество*Товары.Цена AS Стоимость, Клиенты.Имя || 1 . Адрес: 1 I | Клиенты.Адрес | | ’. тел. ’ || Клиенты.Телефон AS Клиент
FROM Продажи, Товары, Клиенты
WHERE Продажи.10_Клиента = Клиенты.10_клиента AND
Продажи.1В_товара = Товары. ID_TOBapa;
/Mw/AWWAW.vMv.v/v.w/Avwz/Av/Xvz/.ww.w/wv.v/AWAWAWiz.vvrtMvv.v.v/w.wAv.v.VA
Ю^товара] Наименование] Цена |
V^^^^riA^^^^V^^^^^r^Mw^f^^^^^^^^^V^^^^^^^V^iifi^^^it •.
Описаниеa
g
1 Хлеб
2 Молоко
3 Пиво
25,50 Батон
15,00:Луговое, пакет 1л
24,00 Балтика 7. банка 0 5л
^VVV/ZAARj
4 М пись; [ И.Д ^
?V'^' $Y^j i
MMWtMtWZAZVV/AWAXW
Ю^клиента
ясо
170,00 Г
"овядина, 1 кг|vi;
ИЗ 4 "
viwv^zwv/><^z. 6--•«•=•»■, .-.•.V4WWV>Z#W<VZWVWV,XWVZAIWWAWV***^^^4S<4«
]АдресA:
сч>';?$Г/»5
Имя ;
»Z*W*WZwXw/Wrt #viAAWiN
Телефон
1
2
3
4
пись ;. [И,] ^
>MmwW^^^vvw<<w>>W№*v#Kw
Ю_товара |
:Иванов
; Петров Сидоров
\ Федоров 111
>^''<i;^
Количество
111-1111: Санкт-Петербург IJh
111-2222 Москва
222-3333 Санкт-Петербург
444-4444 Миргороду!
И5 ^
x^x^Zv :к:х:;:;»№> ’^f:^?
v^AAWMMwXwwXrzw^vvXwz^^^yg^^ SSSS><<<>S6><S^<^^<^^'^*^<S\!MS|^
1и_клиента Й^'^8^^8^$^В A
; За
с
►
1
2
2
1
3
2
4
20
150
50
200
3
40
2
2$ОВ|В|МЙ| ■
1 #^ЖW. .J:
з ^^w^smW
i l^flfc ^W'^^xx^^S
4■ v
из 7
Запись: [И | 4
I 1 шсш
Рис. 7.1. Исходные таблицы базы данных
Примечание
Операция склейки строк в Microsoft Access обозначается символом (+), а не (| |).
Рассмотренный запрос выполняет естественное соединение трех таблиц и добавляет столбцы стоимость и клиент, значения которых вычисляются на основе имеющихся столбцов. На рис. 7.2 показана таблица, возвращаемая рассмотренным запросом.
J Обзор продаж : запрос на выборкуj.
Товар
. Количество
Цена f
Стоимость IJ Клиент ■ /[
5 ►
Пиво
3
24,00
72 Иванов. Адрес:Санкт-Петербург, тел 111-1111|
Молоко
50
15,00
750: Иванов. Адрес:Санкт-Петербург. тел.111-11111
Молоко
40
15,00
600 Иванов. Адрес:Санкт-Петербург. тел 111-11111
Молоко
150
15,00
2250= Петров. Адрес:Москва, тел.111-2222!
Хлеб
20
25,50
510: Петров. Адрес:Москва. тел.111-2222I
Хлеб
200
25,50
5100-Сидоров. Адрес: Санкт-Петербург, тел.222-3333 1
Мясо
2
170,00
340 Федоров. Адрес:Миргород. тел.444-4444]
. ЗаписСИП' ^
113 7
Рис. 7.2. Результат запроса с естественным соединением трех таблиц
Если бы таблица, представленная на рис. 7.2, была не результатом запроса типа select, а представлением, то можно было бы обращаться к ней с запросами, как к обычной таблице базы данных. Более того, в большинстве случаев можно было бы забыть о том, что база данных состоит из трех таблиц, и работать только с одним представлением.
Создание представлений
Оператор создания представления имеет следующий вид:
CREATE VIEW имяПредставления AS sanpocSELECT;
Как и обычная таблица базы данных, представлению присваивается имя, которое не должно совпадать ни с одним именем таблиц. За ключевым словом as следует SQL-выражение запроса на выборку данных.
Рассмотрим в качестве примера простую базу данных, состоящую из трех таблиц:
Товары (lD_TOBapa, Наименование, Цена, Описание);
Клиенты (1В_клиента, Имя, Адрес, Телефон);
Продажи (lD_TOBapa, Количество, 1В_клиента).
Чтобы создать представление, показанное на рис. 7.2, достаточно выполнить следующее SQL-выражение:
CREATE VIEW 0бзор_продаж AS
SELECT Товары.Наименование AS Товар, Продажи.Количество,
Товары.Цена,
Продажи.Количество*Товары.Цена AS Стоимость,
Клиенты.Имя | | 1 . Адрес: • I | Клиенты.Адрес | | ’. тел. 1
11 Клиенты. Телефон AS Клиент
FROM Продажи, Товары, Клиенты
WHERE Продажи.1Б_Клиента = Клиенты.10_клиента AND
Продажи.ID_TOBapa = Товары.1В_товара;
В результате будет создана виртуальная таблица обзор_продаж, к которой можно обращаться с запросами, как к обычной таблице базы данных. Так, например, следующий запрос выбирает из представления записи, в которых столбец товар имеет значе- нис 1 молоко1:
SELECT * FROM 0бзор_продаж
WHERE Товар = ’Молоко1;
Обратите внимание, что условие выборки в запросе оперирует со столбцом, который существует только в представлении (его нет ни в одной из исходных таблиц базы данных).
Рассмотренное представление является многотабличным, поскольку создано на основе не одной, а трех таблиц базы данных. На практике часто используются более простые однотабличные представления, в которых скрываются некоторые столбцы и/или добавляются столбцы, значения которых вычисляются. Например, чтобы создать представление таблицы Клиенты, В КОТОрОМ нет столбца го_ клиента, достаточно выполнить следующее выражение:
CREATE VIEW Список_клиентов AS
SELECT Имя, Телефон, Адрес FROM Клиенты;
Очевидно, что набор данных, содержащихся в этом представлении, можно получить и с помощью запроса:
SELECT Имя, Телефон, Адрес FROM Клиенты;
Однако относительно полученного набора данных вы нс сможете задать какой-либо запрос, поскольку этот набор данных является виртуальной таблицей, отличной от представления.
Оператор create view допускает еще и такую форму синтаксиса:
CREATE VIEW имяПредставления (столбец!, столбец2, . . . , столбецМ) AS sanpocSELECT;
Здесь необязательный список столбцов содержит имена столбцов представления, которые могут отличаться от имен столбцов оператора select. Данная форма позволяет явно установить соответствие между столбцами представления и столбцами виртуальной таблицы, возвращаемой оператором select. Рассмотренный ранее запрос на создание представления Список клиентов можно сформулировать другим образом:
CREATE VIEW Список_клиентов (ФИО, Тел, Адрес) AS
SELECT Имя, Телефон, Адрес FROM Клиенты;
В принципе, вы можете представить всю базу данных с помощью одного или нескольких представлений, скрыв исходные таблицы. Действительно, если база данных состоит более чем из 5—7 связанных таблиц, формулирование запросов становится непростым делом: необходимо помнить, в каких таблицах какие столбцы находятся.
Некоторые разработчики приложений считают, что создавать представления следует для как можно большего числа таблиц. В ряде случаев это хороший прием. Однако не следует забывать, что если представление построено на основе многих таблиц с использованием сложного SQL-выражения, работа с ним может оказаться существенно менее эффективной, чем работа непосредственно с таблицами базы данных. Например, если пользователю нужен всего лишь один столбец, а он вынужден работать с представлением из многих таблиц, то будет выполнено все сложное SQL-выражение, создающее это представление и выделяющее в нем требуемый столбец. Таким образом, представления следует применять, исходя из компромисса между удобством пользователя и производительностью системы.
Представления удаляются из базы данных так же, как и таблицы:
DROP VIEW имяПредставления;
В отличие от обычных таблиц при удалении представления данные сохраняются без изменений. Напомню еще раз, что представление не является объектом хранения данных.
Изменение данных в представлениях
Некоторые современные СУБД поддерживают операции изменения содержимого представлений — вставку, удаление и изменение записей. Однако при этом могут возникнуть проблемы. Дело в том, что при получении запроса на модификацию данных в представлении СУБД должна выполнить соответствующие операции над таблицами базы данных, на основе которых создано это представление. Напомню, что представление лишь вызывает и представляет, но не хранит данные. При этом могут возникнуть неоднозначные ситуации.
Если представление многотабличнос, то как узнать, какие таблицы следует модифицировать?
Если в определении представления используются выражения, относящиеся к оператору select, то как использовать эти выражения для изменения представления?
Рассмотрим представление ToBapbi_view таблицы Товары (ю_товара, Наименование, Цена, Описание), В КОТОрОМ значения столбца цена увеличены на 50. Это представление создастся с помощью следующего выражения:
CREATE VIEW ToBapH_view AS
SELECT Наименование, Цена + 50 AS Новая_цена
FROM Товары;
Попытку обновить в этом представлении значение столбца Новая цена можно выполнить с помощью выражения:
UPDATE ToBapH_view SET Новая_цена = Новая_цена*2;
Однако эта попытка не удастся, поскольку в таблице товары нет столбца Новая_цена. А раз таблица базы данных нс будет обновлена, то не будет обновлено и представление.
Внимание
Если столбец в представлении не соответствует столбцу таблицы базы данных, то обновить этот столбец нельзя.
Допустим, база данных состоит из трех таблиц:
О Товары (lD_TOBapa, Наименование, Цена, Описание);
Клиенты (lD_клиента, Имя, Адрес, Телефон);
Продажи (lD_TOBapa, Количество, Ю_клиента).
Создадим на основе этих таблиц представление Обзор продаж:
CREATE VIEW Обзор__продаж (Наименование, Количество, Цена, Клиент) AS
SELECT Товары.Наименование, Продажи.Количество, Товары. Цена, Клиенты.Имя AS Клиент
FROM Продажи, Товары, Клиенты
WHERE Продажи. 1П_Клиента ~ Клиенты. 1П_клиента
AND
Продажи.ID_tobара = Товары.ID_tobара;
Содержимое данного представления можно изменять (если СУБД поддерживает изменение представлений), поскольку имеется однозначное соответствие между столбцами представления и столбцами таблиц базы данных.
Задачи
Задача 7.1
Пусть база данных содержит следующие три таблицы:
Студенты (lD—Студента, Имя, Телефон, Адрес, ID_Kypca) — данные о студентах, в том числе и о курсах, которые они изучают;
О Курсы (lD_Kypca, Наименование) — данные О курсах;
Преподаватели (1В_преподавателя, Имя, Телефон, Адрес, iD_Kypca) — данные о преподавателях, в том числе и о курсах, занятия по которым они проводят.
В таблице студенты каждый студент может быть записан на несколько курсов. Аналогично, один и тот же преподаватель может проводить занятия по нескольким курсам. Таким образом, столбцы iD_Kypca в этих таблицах не обязаны содержать уникальные значения. В таблице курсы, напротив, столбец iD_Kypca содержит только уникальные значения.
Сформулируйте SQL-выражения для создания указанных таблиц так, чтобы таблицы студенты и преподаватели были связаны с таблицей курсы.
Задача 7.2
Для базы данных, описанной в задаче 7.1, сформулируйте запрос, возвращающий таблицу, которая содержит:
наименования курсов и имена преподавателей, которые проводят занятия по данным курсам;
наименования курсов и имена студентов, которые изучают данные курсы;
имена студентов и имена преподавателей, которые проводят занятия с данными студентами.
Задача 7.3
Для базы данных, описанной в задаче 7.1, сформулируйте запрос, возвращающий представление, которое содержит наименования курсов, преподавателей и студентов. Каждая запись в представлении должна содержать наименование некоторого курса, имя преподавателя, который проводит занятия по данному курсу, и имя студента, который его изучает.
Задача 7.4
Пусть в базе данных, описанной в задаче 7.1, имеется таблица Контакты (Имя, Адрес, Телефон, Примечания). Сформулируйте запрос, добавляющий в нее соответствующие данные из таблиц Студенты и преподаватели. Решите эту же задачу, записывая при ЭТОМ В Столбец Примечания СЛОВО 1 Студент1 ИЛИ ’ Преподаватель’, в зависимости от того, из какой таблицы берутся данные ДЛЯ вставки В таблицу Контакты.
Глава 8
Транзакции
При работе с базой данных всегда следует иметь в виду, что существует хотя и малая, но отличная от нуля вероятность испортить или потерять данные. Эта вероятность тем больше, чем больше база данных и сложнее запросы к ней. В СУБД имеются специальные настраиваемые средства защиты данных, однако в ряде случаев можно применить дополнительные меры, предоставляемые SQL.
Предположим, что требуется перевести деньги с одного счета на другой. Для этого необходимо выполнить несколько операторов SQL. Если в ходе их выполнения произойдет какой-нибудь аппаратный или программный сбой, из-за которого не выполнится один или несколько операторов, то может произойти так, что деньги будут сняты с одного счета, но не поступят на другой. Чтобы этого не произошло, предпринимаются специальные меры. Их суть состоит в том, что несколько SQL-операторов определяются как единый блок, называемый транзакцией (от англ. transaction — дело, сделка). В транзакции либо все операторы выполняются успешно, либо ни один из них не выполняется, а база данных возвращается в исходное состояние, в котором она находилась до начала выполнения операторов транзакции. Другими словами, если какой-то оператор в транзакции по какой-нибудь причине не выполнился, то отменяется действие всех уже выполненных операторов в этой транзакции — происходит так называемый откат.
Неприятности могут возникнуть и при работе с базой данных в многопользовательском режиме, даже в условиях абсолютной
надежности оборудования, СУБД и программ приложения. Если несколько пользователей пытаются одновременно использовать одну и ту же таблицу, то может возникнуть так называемая коллизия одновременного доступа. Представьте себе ситуацию с покупкой железнодорожного билета. Первый покупатель запрашивает билет на некоторый поезд в купейном вагоне на определенную дату. Кассир выполняет соответствующий запрос к базе данных и получает сведения о всех свободных местах. Пока покупатель выбирает подходящее ему место в вагоне, другой покупатель (подойдя к другой кассе) может оказаться более проворным и купить билет на тот же поезд, вагон и дату. Но первый покупатель, как и его кассир, еще не знает этого и покупает билет на то же самое место. Иначе говоря, он приобретает билет на уже занятое место. Возникает коллизия одновременного доступа к одной и той же записи в базе данных. В реальной жизни такого не должно происходить (по крайней мере, теоретически), потому что при выполнении запросов к базе данных предпринимаются специальные меры. Запросы к базе данных железнодорожных билетов, состоящие из нескольких SQL- операторов, заключаются в транзакцию, а для различных транзакций определяются так называемые уровни изоляции, чтобы исключить нежелательное взаимодействие между ними.
Примечание
Приложение, работающее с базой данных, может использовать и другие средства ограничения одновременного доступа, а также выполнять другие виды транзакций. В данной книге рассматриваются только транзакции SQL.
Как устроена транзакция
Транзакция состоит из любых операторов SQL, которые могут изменить базу данных. В SQL:2003 не предусмотрен специальный оператор начала транзакции. Однако некоторые реализации SQL требуют наличия такого оператора. Он может выглядеть по- разному, например, begin work, begin tran или begin. В системах, совместимых с SQL:2003, если никакая транзакция еще не начата, а на выполнение отправляется оператор SQL, то автоматически создается новая транзакция. Например, операторы
CREATE TABLE (создать Таблицу), UPDATE (обновить) И SELECT (выбрать) выполняются только в транзакции.
Итак, оператора начала транзакции может и не быть. Однако для ее завершения предусмотрены два оператора:
commit — завершить выполнение. Выполнение всех операторов транзакции происходит последовательно в едином блоке;
rollback — откат. Происходит отмена действия всех операторов транзакции, и база данных возвращается в исходное состояние, в котором она находилась до начала транзакции.
Оператор commit применяется тогда, когда все SQL-операторы транзакции предположительно выполнены (восприняты СУБД и нс вызвали ошибок) и требуется подтвердить изменения, внесенные в базу данных. Если при выполнении оператора commit произойдет системный сбой или ошибка, можно выполнить откат транзакции, а затем попытаться выполнить се снова.
Примечание
После ключевого слова commit допускается необязательное ключевое слово work (работа): commit work.
Оператор rollback применяется тогда, когда необходимо отменить изменения, внесенные транзакцией, и восстановить базу данных в прежнем состоянии. Если при выполнении оператора rollback произойдет системный сбой, то после перезагрузки оператор rollback можно выполнить снова, и он должен восстановить базу данных. Таким образом, rollback является отказоустойчивым оператором.
Приложение может состоять из нескольких транзакций. Если не указан явный оператор начала транзакции (например, begin work), то первая транзакция начинается в самом начале приложения. Последняя транзакция заканчивается в конце приложения. Для указания конца транзакции используется оператор commit или rollback. Каждый такой оператор завершает одну транзакцию и начинает следующую. Приложение с несколькими транзакциями имеет следующий вид:
Начало приложения
SQL-операторы транзакции 1;
COMMIT или ROLLBACK;
SQL-операторы транзакции 2;
COMMIT или ROLLBACK;
SQL-операторы транзакции N; Конец приложения
Определение параметров транзакции
Для транзакции можно установить параметры — режим и уровень изоляции. С этой целью применяется оператор:
SET TRANSACTION
Режим,
ISOLATION LEVEL уровеньИзоляции;
Режим может принимать следующие два значения:
read write (чтение-запись) — значение по умолчанию;
read only (только чтение).
Уровень изоляции транзакции может принимать четыре значения:
serializable (последовательное выполнение) — значение по умолчанию;
repeatable read (повторяющееся чтение);
read commited (подтвержденное чтение);
read uncommited (неподтвержденное чтение).
Транзакция имеет параметры по умолчанию: read write и serializable. Они обычно подходят большинству пользователей и основаны на допущениях, что база данных будет обновляться и что лучше как можно больше себя обезопасить. Режим read write позволяет вносить изменения в базу данных, а уровень изоляции serializable является наиболее безопасным. Если же необходимо задать другие значения, то следует использовать оператор set transaction. При этом он записывается либо в начале приложения, определяя первую транзакцию, либо после оператора commit или rollback, определяя следующую тран-
закцию. Если же после commit или rollback не вызвать оператор set transaction, то следующая транзакция будет иметь параметры, принятые по умолчанию.
Например:
SET TRANSACTION
READ ONLY,
ISOLATION LEVEL READ COMMITED;
Уровни изоляции транзакций
Наибольшая безопасность достигается при самом высоком уровне изоляции транзакций, однако за это приходится платить снижением общей производительности работы системы в многопользовательском режиме. Так, если транзакции различных пользователей базы данных полностью изолированы друг от друга, то пока не будет выполнена уже начавшаяся транзакция, другие ждут ее завершения. В связи с этим возникает задача поиска компромисса между необходимым уровнем безопасности и производительностью системы в целом. Рассмотрим вопросы, связанные с выбором различных уровней изоляции, начав с самого низкого.
Неподтвержденное чтение
Самый низкий (слабый) уровень изоляции — read uncommited (неподтвержденное чтение или чтение неподтвержденных данных). При данном уровне изменения, внесенные одним пользователем, могут быть прочитаны другим, даже если первый пользователь еще не подтвердил их с помощью оператора commit. Проблема возникнет в случае, если первый пользователь выполнил откат своей транзакции. Тогда все последующие действия второго пользователя оказываются основанными на неправильных (неактуальных) данных.
Таким образом, уровень read uncommited не следует применять, когда требуются точные результаты. Он более или менее подходит в том случае, когда достаточно получить приблизительные результаты, например, при статистической обработке мало изменяющихся данных.
Очевидно, что при уровне изоляции read uncommited обе транзакции могут выполняться одновременно.
Подтвержденное чтение
Следующим по силе является уровень изоляции read commited (подтвержденное чтение или чтение подтвержденных данных). В этом случае изменения, произведенные другой транзакцией, остаются невидимыми в вашей транзакции до тех пор, пока другой пользователь не завершит ее оператором commit. Хотя до завершения другой транзакции внесенные ею изменения вам не видны, обе транзакции могут выполняться совместно.
По сравнению с read uncommited данный уровень изоляции дает лучшие результаты, но не освобождает от неприятностей так называемого неповторяющегося чтения. Рассмотрим следующую ситуацию.
Допустим, первый пользователь собирается сначала прочитать данные, а затем использовать их при внесении изменений в другую таблицу. Например, он хочет сначала узнать, сколько имеется некоторого товара на складе, а затем оформить заказ на этот товар. Может случиться так, что этот пользователь прочтет устаревшие данные, которые уже изменены вторым пользователем, но еще не подтверждены им с помощью оператора commit. Например, второй пользователь уже "забрал" весь запас данного товара, а первый пользователь, не зная об этом, оформил заказ на отсутствующий товар. Таким образом, проблема заключается в том, что результаты чтения данных первым пользователем до и после выполнения вторым пользователем оператора commit могут различаться. Это так называемая проблема неповторяющегося чтения.
Повторяющееся чтение
Уровень изоляции repeatable read (повторяющееся чтение) исключает проблему неповторяющегося чтения, рассмотренную ранее. Тем не менее, данный уровень не устраняет другую неприятность, называемую фиктивным чтением. Она возникает тогда, когда пользователь читает данные, изменяемые в результате выполнения другой транзакции. При этом изменения дан-
ных происходят во время их чтения. Рассмотрим в качестве примера следующую ситуацию.
Предположим, первый пользователь выполняет оператор выборки данных (select) с некоторым условием (where или having). В результате он выбирает какое-то множество записей. Второй пользователь сразу же за этим изменяет данные в одной или нескольких записях, которые выбрал первый пользователь. Более того, второй пользователь завершает свою транзакцию оператором commit. Таким образом, получается, что записи, ранее удовлетворявшие условию выборки, теперь перестали ему соответствовать. Не исключено, что появились новые записи, ранее не удовлетворявшие условию, а теперь соответствующие ему. В это время первый пользователь, еще не завершив свою транзакцию, не знает о произошедших изменениях и отправляет на выполнение еще один SQL-оператор с таким же условием выборки, что и в начале. Однако этот оператор будет работать не с теми же записями, что предыдущий оператор. Данная проблема и является проблемой фиктивного чтения.
Последовательное выполнение
Избежать неприятностей, связанных с ранее рассмотренными уровнями изоляции, позволяет уровень serializable (последовательное выполнение). Транзакции с уровнем изоляции serializable всегда выполняются не параллельно, а последовательно. Если одна из них уже начата, то другая будет ожидать ее окончания. Аппаратные и программные отказы могут привести к невыполнению транзакции, однако при данном уровне изоляции можно быть уверенным, что результаты работы с базой данных будут корректными.
Предоставляя наибольшую надежность, уровень изоляции serializable максимально снижает общую производительность системы.
Субтранзакции
Начиная с SQL: 1999 транзакции могут состоять из нескольких субтранзакций. Субтранзакции отделяются друг от друга так называемыми точками отката, которые задаются с помощью one-
ратора savepoint (точка сохранения, отката). Он может применяться вместе с оператором rollback. Если ранее roollback применялся для отмены всей транзакции, то теперь его можно использовать для отмены только той части транзакции, которая расположена между rollback и точкой отката.
Субтранзакции используются не для того, чтобы частично восстановить базу данных в случае возникновения ошибки (в этом случае необходимо выполнить откат всей транзакции). Бывают ситуации, когда выполняется сложная последовательность действий с множеством вариантов выбора. На каком-то этапе по полученным результатам вы можете решить, что следует пойти по другому пути, а для этого придется вернуться на несколько шагов назад. Если бы не субтранзакции, то пришлось бы отменять всю транзакцию целиком.
Точка отката помечает с помощью следующего оператора:
SAVEPOINT имяТочкиОтката;
Откат транзакции к этой точке выполняется с помощью оператора:
ROLLBACK ТО SAVEPOINT имяТочкиОтката;
Ограничения в транзакциях
Ограничения, накладываемые на столбцы, таблицы и связи между ними (см. разд. 7.1), оказывают влияние на выполнение транзакций. Например, таблица имеет столбец, на который наложено ограничение not null (значения столбца не могут быть неопределенными), и требуется добавить новые данные. Как правило, в этом случае сначала добавляют новую пустую запись, а затем заполняют ее значениями. Однако данный способ не осуществим, даже если попытаться его выполнить до оператора завершения транзакции commit. Дело в том, что ограничение not null не позволит создать строку с неопределенными значениями. В SQL:2003 для решения данной проблемы можно наделить ограничение дополнительными свойствами:
deferable (допускающий задержку). Ограничения с данным свойством могут быть заданы как immediate (немедленные) или как defered (задержанные);
not deferable (не допускающий задержку). Ограничения с этим свойством выполняются немедленно.
Если ограничение типа deferable задано как immediate, то оно действует немедленно, как и ограничение типа not deferable. Если ограничение типа deferable задано как defered, то его действие может быть задержано.
Для добавления пустых записей и выполнения других операций, которые могут нарушить ограничения типа deferable, применяется следующий оператор:
SET CONSTRAINS ALL DEFERED;
(установить все ограничения как задержанные).
Этот оператор определяет все ограничения типа deferable как defered, а на ограничения типа not deferable он не действует. После выполнения всех операций, которые могут нарушить ограничения, и достижения таблицей состояния, в котором их нарушить уже нельзя, все ограничения можно возобновить:
SET CONSTRAINS ALL IMMEDIATE;
(установить все ограничения как немедленные).
Если ограничения, ранее заданные как defered, вы не задаете явно как immediate, то при попытке завершить свою транзакцию оператором commit будут активизированы все задержанные ограничения. Если к этому моменту выполняются не все ограничения, транзакция будет отменена с сообщением об ошибке.
Итак, ограничения защищают базу данных от неправильного ввода данных или от их отсутствия. Однако у вас есть возможность внутри транзакции временно отменить мешающие вам ограничения.
Рассмотрим пример, иллюстрирующий использование отсрочки ограничений. Предположим, в базе данных имеются следующие таблицы:
Отделы (Номер—отдела, Название, Оплата);
Сотрудники (Номер, Имя, Номер_Отдела, Зарплата).
В таблице Отделы столбец номер отдела является первичным ключом, а столбец оплата содержит суммы зарплат всех сотрудников соответствующего отдела.
8 Зак. 120
В таблице Сотрудники столбец Номер отдела ЯВЛЯСТСЯ внешним КЛЮЧОМ, который ссылается на таблицу Отделы.
Каждый раз, когда изменяется значение столбца Зарплата в таблице Сотрудники, необходимо изменить и значение в столбце оплата таблицы Отделы. Чтобы обеспечить правильность значений, в определение таблицы отделы можно вставить следующее ограничение:
CREATE TABLE Отделы (
Номер_отдела CHAR (3) PRIMARY KEY,
Название CHAR (20),
ОплатаNUMERIC (12, 2),
CHECK (Оплата = (SELECT SUM(Зарплата) FROM Сотрудники
WHERE Сотрудники.Номер__отдела=Отделы.Номер_отдела)) );
Пусть необходимо увеличить на 1000 зарплату сотрудника с номером 15. Для этого необходимо обновить обе таблицы:
UPDATE Сотрудники
SET Зарплата ~ Зарплата + 1000
WHERE номер = 15;
UPDATE Отделы
SET Оплата = Оплата +1000
WHERE Отделы.Номер_отдела =
(SELECT Сотрудники.Номер_отдела FROM Сотрудники
WHERE Сотрудники.Номер_сотрудника = 15);
При выполнении этих операторов возникнет некоторая трудность. На практике реализации SQL проверяют выполнение только тех ограничений, которые относятся к измененным значениям. После выполнения первого из приведенных операторов update проверяются все ограничения, которые ссылаются на измененные им значения. К таким ограничениям относится и ТО, которое определено ДЛЯ таблицы Отделы. Действительно, это ограничение касается столбца Зарплата таблицы сотрудники,
а значение этого столбца изменяется оператором update. Данное ограничение после выполнения первого оператора update оказалось нарушенным. Так, если до его выполнения каждое значение столбца оплата было равно сумме зарплат всех сотрудников соответствующего отдела, то после выполнения этого оператора равенство нарушилось.
Данная ситуация исправляется вторым оператором update, но в тот момент, когда первый оператор выполнился, а второй еще нет, ограничение нарушено. С помощью оператора set constraints defered можно временно отключить все или только некоторые ограничения. Действие этих ограничений будет отложено до тех пор, пока не выполнится оператор set constraints immediate или не будет вызван один из двух операторов завершения транзакции — COMMIT ИЛИ ROLLBACK.
Итак, для обновления таблиц следует применить следующие операторы:
SET CONSTRAINTS DEFERED;
UPDATE Сотрудники
SET Зарплата = Зарплата + 1000
WHERE номер = 15;
UPDATE Отделы
SET Оплата = Оплата +1000
WHERE Отделы. Номер_от дела =
(SELECT Сотрудники.Номер_отдела
FROM Сотрудники
WHERE Сотрудники.Номер_сотрудника = 15) ;
SET CONSTRAINTS IMMEDIATE;
В данном SQL-коде временно отменяются все ограничения. Так, при добавлении новой записи в таблицу отделы не будет проверяться и первичный ключ (ограничение типа primary key), что, очевидно, плохо. Поэтому следует указывать конкретно, какие ограничения необходимо задержать. С этой целью при создании ограничений им следует назначать имена:
CREATE TABLE Отделы
(
Номер_отдела CHAR (3) PRIMARY KEY,
НазваниеCHAR (20),
ОплатаNUMERIC (12, 2),
CONNSTRAINT PayEq
CHECK (Оплата = (SELECT SUM(Зарплата)
FROM Сотрудники
WHERE Сотрудники. Номер_отдела=Отделы. Номер_отдела)) );
Здесь PayEq — имя ограничения. На поименованные ограничения можно ссылаться в операторе set constraints имяограничения.
SET CONSTRAINTS PayEq DEFERED;
UPDATE Сотрудники
SET Зарплата = Зарплата + 1000
WHERE номер = 15;
UPDATE Отделы
SET Оплата = Оплата +1000
WHERE Отделы. Номер_отдела =
(SELECT Сотрудники.Номер__отдела FROM Сотрудники
WHERE Сотрудники.Номер_сотрудника = 15);
SET CONSTRAINTS PayEq IMMEDIATE;
Временно отменять ограничения можно только внутри транзакций. По завершении транзакции все задержанные ограничения сразу же вновь вступают в силу. Правильное использование этой возможности не позволит транзакции создать данные, нарушающие ограничения.
Глава 9
Курсоры и применение SQL в приложениях
Как вы уже могли заметить, SQL-запросы обеспечивают обработку данных одновременно всех строк (записей) таблицы. SQL- запрос на выборку данных возвращает таблицу, которая содержит одну, несколько или ни одной записи. Результат запроса можно просмотреть или скомбинировать с результатом другого запроса с помощью операторов SQL, т. е. сформировать новый комбинированный запрос, который должен вернуть таблицу. При этом допустимы некоторые преобразования данных для представления в результатной таблице, но далеко не все, которые часто требуются на практике. Иначе говоря, нередко возникает потребность обработать данные так, как SQL не позволяет или же делает это не эффективно.
Свободу в обработке табличных данных предоставляют процедурные языки программирования, такие как С, C++, Visual Basic, Pascal, FORTRAN и др. Однако применительно к базам данных они имеют особенности, главная из которых состоит в том, что обработка табличных данных происходит обычно в цикле по отдельным строкам (записям) таблиц. Приложения получают доступ к отдельной записи и обрабатывают ее поля подобно обычным переменным. При необходимости они переходят к той или иной записи и снова обрабатывают ее поля. Иначе говоря, традиционной технологией работы с базой данных в приложениях является обработка полей одиночных записей таблиц базы данных.
А поскольку многие операции по обработке табличных данных более эффективно выполняются средствами SQL, перечисленные
процедурные языки допускают вставки SQL-кодов. Иначе говоря, программы на процедурных языках, работающие с базами данных, допускают вставки фрагментов, написанных на языке SQL. Для того чтобы адаптировать основную часть SQL к особенностям обработки табличных данных программами на процедурных языках, в SQL были введены понятие курсора и поддерживающий его механизм. Курсор позволяет сделать обработку одной или нескольких записей таблицы с помощью SQL- выражений и, таким образом, упрощает совместное использование SQL с другими языками программирования.
Курсоры применяются в ситуациях, когда требуется выбрать одну или несколько записей из таблицы, проверить их содержимое и по результатам этой проверки выполнить некоторые операции. В данном случае SQL используется совместно с процедурным языком, на котором написано приложение, работающее с базой данных. Выборка записей производится с помощью SQL- запроса, а проверка их содержимого — с помощью кода на процедурном языке. Процедурные языки имеют и собственные средства поиска требуемых записей. Однако если обработку результата запроса можно выполнить средствами SQL, то именно так и рекомендуют делать, а процедурный язык стоит использовать для построчной обработки данных только тогда, когда нельзя обойтись обычным SQL.
Примечание j
В разд. 10.3.4 будет кратко описан способ применения курсоров без обращения к кодам на процедурном языке.
Курсоры оказываются очень полезными, если требуется обработать результаты SQL-запроса, вернувшего слишком большой объем данных. Результат запроса может содержать десятки тысяч записей, что может занять значительную часть памяти компьютера и/или пропускной способности сети. Получение такого объема данных в один прием с помощью обычного оператора select может привести к ощутимым временным затратам. Например, если требуется просто вывести какие-либо данные на монитор, то разумнее выбирать и выводить их по частям, скажем, по 15—25 записей (столько строк, сколько помещается
в области отображения). Подготовку различного рода отчетов, предназначенных для вывода табличных данных постранично на печать, также целесообразно выполнять по частям.
В большинстве случаев мы заранее нс знаем, сколько записей вернет обычный запрос на выборку данных — ноль, одну или несколько. Можно быть уверенным в получении единственной записи, если в операторе select указана итоговая (агрегатная) функция (например, count (), sum () и др.) или же выборка осуществляется по значению первичного ключа. В остальных случаях запрос может вернуть сколько угодно записей, в том числе и ни одной. Использование курсоров представляет собой универсальный способ для обработки заранее неизвестного количества записей, возвращаемых запросом.
Курсор имеет область действия (набор записей), команды перемещения указателя курсора на ту или иную запись в области действия и способ передачи значений столбцов отдельной записи в переменные, доступные приложению, написанному на процедурном языке. Чтобы задать и применить курсор, используются несколько SQL-операторов:
declare cursor (объявить курсор) — определяет имя курсора и область его действия (выборку записей);
open (открыть) — активизирует объявленный курсор, т. е. собирает все записи, соответствующие запросу в операторе DECLARE CURSOR;
fetch (получить, достать) — осуществляет получение данных, т. е. перемещение в области действия курсора и запись значений столбцов в переменные, доступные в приложении;
close (закрыть) — закрывает указанный курсор.
Отчасти курсоры могут использоваться вне всякой связи с программами на процедурных языках. Однако это редко используется на практике. Обычно курсоры применяются как механизм связи SQL-выражений и программ обработки данных на процедурных языках. Тем не менее, мы сначала рассмотрим курсоры сами по себе, т. е. в рамках SQL, а затем их использование в приложениях.
Объявление курсора
Прежде чем использовать курсор, его необходимо объявить для СУБД. Объявление курсора еще не приводит к каким-либо действиям с данными, а лишь задает имя курсора и определяет запрос, с которым он будет работать. Объявление курсора производится с помощью выражения, начинающегося с ключевого слова declare:
DECLARE имяКурсора
[ чувствительность"!
[ перемеща емость ]
CURSOR
[ фиксируемостьА
[ возвраща емость ]
TOR выражениеЗапроса
[ORDER BY порядокСортировкиА
[FOR разрешениеОбновленияА;
Здесь квадратные скобки являются не элементами синтаксиса, а лишь указывают, что заключенный в них элемент не является обязательным. Все SQL-выражение объявления курсора может быть записано в одну или несколько строк. Явным признаком окончания выражения declare cursor, как и любого другого SQL-выражения, является точка с запятой.
Имя курсора задается обязательно, поскольку оно используется в других операторах при работе с курсором. Кроме того, можно объявить несколько курсоров, каждый из которых должен иметь уникальное имя.
Далее приведен пример объявления курсора:
DECLARE phone348 CURSOR FOR SELECT Имя, Адрес, Телефон FROM Клиенты
WHERE Телефон LIKE '348%’;
Чувствительность
Выражение запроса, указанное после ключевого слова for, определяет набор записей, которые составляют область действия курсора. Однако программный код обработки данных может
внести такие изменения данных, что они перестанут удовлетворять условию запроса, т. е. выпадут из области действия курсора. Чувствительность курсора определяет, как в этом случае будет вести себя команда курсора.
Чувствительность курсора может быть задана следующими ключевыми словами:
sensitive (чувствительный);
insensitive (нечувствительный);
asensitive (не обладающий чувствительностью).
Чтобы курсор вел себя с учетом произведенных изменений, т. с. был чувствительным к изменениям, необходимо установить значение sensitive. Команда курсора может обрабатывать записи, которые изначально соответствовали условию запроса, и тогда следует установить значение insensitive. Наконец, курсор может просто игнорировать измененные или удаленные записи. В этом случае устанавливается значение asensitive. Значение asensitive устанавливается по умолчанию, а его смысл зависит от конкретной реализации SQL. Он может совпадать как С SENSITIVE, так И С INSENSITIVE, ПОЭТОМУ СМЫСЛ ASENSITIVE в вашей системе следует уточнить по справочной документации.
Перемещаемость
Перемещаемость курсора может принимать два возможных значения:
scroll (с прокруткой);
NO SCROLL (без прокрутки).
Значение scroll позволяет получить доступ к записям в области действия курсора в любом порядке. Управление перемещением осуществляется с помощью оператора fetch. Значение no scroll запрещает перемещение по записям.
Примечание
Свободное перемещение по записям при установленном значении scroll обеспечивается в SQL-92 и SQL2003. В SQL-86 и SQL-89 разрешалось только последовательное перемещение курсора, начиная с первой записи из области действия курсора, которая определена условием запроса.
Выражение запроса
Выражение запроса, указанное после ключевого слова for, представляет собой любое правильное выражение, начинающееся с ключевого слова select, т. е. запрос на выборку данных. Оператор select задает область действия курсора — множество записей, удовлетворяющих условию запроса. При этом он вернет запись, на которую указывает курсор в данный момент времени. Однако выполнение оператора declare cursor еще не приводит к выполнению самого запроса. Выборка данных происходит только при выполнении оператора open.
Сортировка
Если требуется, чтобы записи обрабатывались в определенном порядке, можно задать их предварительную сортировку (упорядочивание). Это делается с помощью оператора order by (сортировать по). После ключевых слов order by указывается одна или несколько спецификаций сортировки. Если таких спецификаций больше одной, то они разделяются запятыми.
Каждая спецификация сортировки имеет следующий синтаксис:
имяСтолбца [COLLATE BY имяСопоставленияА [ASC | DESC]
Здесь квадратные скобки являются не элементами синтаксиса, а лишь указывают на то, что заключенный в них элемент не является обязательным. Вертикальная черта разделяет допустимые значения (в конкретном выражении может использоваться лишь одно из них).
Имя столбца, по значениям которого следует упорядочить записи, должно присутствовать в списке столбцов, указанном после ключевого слова select. При этом столбец может быть и вычисляемым, т. е. создаваемым на основе реальных столбцов таблицы (или таблиц). В данном случае следует использовать псевдонимы для вычисляемых столбцов.
По умолчанию принята сортировка в алфавитном порядке или по возрастанию. Вместе с тем, можно выбрать и другой порядок (сопоставление). Каждая реализация SQL предусматривает несколько часто используемых сопоставлений. Это следует выяснить в справочной документации. В этом случае применяется оператор COLLATE BY (сОПОСТаВИТЬ С).
Чтобы указать, в каком порядке следует производить сортировку записей, применяются ключевые слова asc (по возрастанию) или desc (по убыванию). Если ни одно из этих значений не указано, то по умолчанию предполагается asc.
Далее приведен пример объявления курсора с сортировкой:
DECIARE name_sum CURSOR FOR
SELECT Имя, Адрес, Сумма_заказа
FROM Клиенты
ORDER BY Имя, Сумма_заказа DESC;
Здесь записи сортируются сначала по именам клиентов в алфавитном порядке, а затем по сумме их заказов в порядке убывания.
В следующем примере применяется сортировка по вычисляемому столбцу:
DECLARE name_sum2 CURSOR FOR
SELECT Имя, SUM (Сумма_заказа) AS Сумма
FROM Клиенты
GROUP BY Имя
ORDER BY Имя, Сумма DESC;
Разрешение обновления
Изменение значений в записях, а также удаление записей в области действия курсора можно разрешить или запретить. В операторе declare cursor разрешение устанавливается с помощью оператора for.
Чтобы разрешить изменения значений в указанных столбцах и удаление записей, используется следующее выражение:
FOR UPDATE OF списокСтолбцов
Здесь списокСтолбцов — список имен столбцов, разделенных запятыми, в которых разрешено изменение (обновление) значений. Столбцы из этого списка должны присутствовать в выражении запроса оператора declare cursor (см. разд. 9.1.3).
Если оператор for отсутствует в declare cursor, то считается, что значения всех столбцов, указанных в выражении запроса, могут быть изменены. В данном случае с помощью оператора update можно изменить значения всех столбцов записи в области
действия курсора, а с помощью оператора delete — удалить запись.
Для запрещения изменения и удаления записей в области действия курсора в операторе declare cursor используется выражение: FOR READ ONLY
Открытие и закрытие курсора
Рассмотренный в разд. 9.1 оператор declare cursor определяет, какие записи следует включить в область действия курсора. Он только объявляет курсор для СУБД, не производя выборку данных или каких-либо других действий.
Активизация курсора производится с помощью оператора открытия курсора:
OPEN имяКурсора;
Пока курсор не открыт, выборка записей из области действия курсора не возможна.
В следующем примере курсор сначала объявляется, а затем открывается:
DECLARE name_sum CURSOR FOR
SELECT Имя, Адрес, Сумма_заказа
FROM Клиенты
ORDER BY Имя, Сумма_заказа DESC;
OPEN name_sum;
В SQL-выражениях, применяемых в приложениях на базовом языке, могут использоваться переменные. После открытия курсора значения этих переменных, а также значения функций даты- времени становятся фиксированными. Другими словами, последующее присваивание в программе на базовом языке этим переменным новых значений не повлияет на выбранные курсором записи.
По окончании работы с курсором его рекомендуется закрыть, поскольку открытые курсоры занимают системные ресурсы. Для этого используется оператор:
CLOSE имяКурсора;
Работа с отдельными записями
Имя и область действия курсора задаются с помощью оператора declare cursor. Активизация курсора и выборка записей, удовлетворяющих выражению запроса в declare cursor, выполняются оператором open. При этом открытый курсор может указывать:
на одну из записей в области действия курсора;
или на область, находящуюся перед первой записью области действия курсора;
или на область, находящуюся после последней записи области действия курсора.
На что именно указывает курсор, а также перемещение курсора по записям определяются с помощью оператора fetch:
FETCH [ \.ориентацияА FROM] имяКурсора
INTO списокЦелевыхСпецификации;
Квадратные скобки указывают, что заключенный в них элемент не является обязательным. Все выражение оператора fetch может быть записано в одну или несколько строк. Явным признаком окончания выражения fetch, как и любого другого SQL- выражения, является точка с запятой.
Ориентация курсора, определяющая, на что он указывает, может иметь следующие значения:
next (следующая) — куда бы ни указывал курсор в данный момент, он перемещается на следующую запись в порядке, установленном сортировкой. Если курсор указывает на область перед первой записью, то происходит переход на первую запись и завершение его работы. Если курсор находится на последней записи, то он прекращает свою работу и в системную переменную sqlstate записывается код отсутствия данных. Ориентация next принята по умолчанию;
prior (предыдущая) — перемещает курсор на предыдущую запись. Если курсор находится на первой записи, то он прекращает свою работу и в системную переменную sqlstate записывается код отсутствия данных;
first (первая) — перемещает курсор на первую запись;
last (последняя) — перемещает курсор на последнюю запись;
absolute п — перемещает курсор на запись с номером л, отсчитанным от первой записи, если л — положительное число. Если л отрицательное, отсчет и перемещение начинаются с последней записи в обратном порядке. Так, fetch absolute -1 перемещает указатель на последнюю запись, fetch absolute -2 перемещает его на предпоследнюю запись и т. д. При попытке переместиться за границы области действия курсора генерируется код отсутствия данных. Например, это произойдет в случае выполнения fetch absolute о;
relative л — перемещает курсор на л записей относительно его текущей позиции. Если л — положительное число, то перемещение происходит вниз по выборке, а если отрицательное — вверх, fetch relative о не перемещает курсор, fetch relative 1 действует так же, как и fetch next, a fetch relative -1 — как fetch prior. При попытке переместиться за границы области действия курсора генерируется код отсутствия данных;
О спецификацияПростогоЗначения — перемещает указатель на запись, заданную спецификацией простого значения.
Если ориентация в операторе fetch не указана, то курсор устанавливается на первую запись в области действия. Последующие перемещения курсора могут вывести его за область действия. Поэтому при обработке данных с использованием курсора необходимо следить за значением системной переменной sqlstate.
Оператор fetch обеспечивает не только перемещение курсора по записям области действия, но и присвоение переменным приложения значений столбцов в указанной записи. Значения этих переменных могут быть обработаны приложением на базовом языке.
В операторе fetch после ключевого слова into указывается одна или несколько целевых спецификаций, т. е. описаний, куда будут записываться прочитанные из столбцов данные. Если целевых спецификаций больше одной, то они разделяются запятыми. Целевыми спецификациями могут быть базовые переменные, если курсор определен во встроенном SQL, или параметры, если он определен в модуле. Количество и типы целевых спецификаций
должны соответствовать количеству и типам столбцов, указанных в выражении запроса оператора declare cursor. Так, например, в случае встроенного SQL, если из таблицы выбираются значения трех столбцов, то в выражении into должны быть указаны три переменные с правильно подобранными типами. Перед именами переменных в списке целевых спецификаций ставится двоеточие. Это общее правило использования переменных в SQL-выражениях. Например:
DECLARE name_sum CURSOR FOR
SELECT Имя, Адрес, Сумма_заказа
FROM Клиенты
ORDER BY Имя, Сумма_заказа DESC;
OPEN name_sum;
FETCH LAST FROM name_sum
INTO :name, :address, : summa;
Здесь сначала объявляется курсор (задается имя name sum и область действия, определяемая выражением select), затем он открывается, а после этого происходит перемещение указателя курсора на последнюю запись в области действия и считывание значений столбцов Имя, Адрес И Сумма_заказа таблицы Клиенты в переменные name, address и summa соответственно. Предполагается, что эти переменные определены ранее, причем их типы соответствуют типам соответствующих столбцов. Подробнее о переменных для связи с программой на процедурном языке будет рассказано в разд. 9.4.
Запись, на которую в данный момент указывает курсор, может быть изменена или удалена. При этом используются операторы update и delete с указанием имени курсора.
Для изменения записи, на которую указывает курсор, используется выражение вида:
UPDATE имя Таблицы
SET имяСтолбца = значение [, имяСтолбца 2 = значение2, ...]
WHERE CURRENT OF имяКурсора;
Значение, которое записывается в указанный столбец, может быть выражением, ключевым словом или ключевым словом default (по умолчанию). В случае какой-либо ошибки, возникшей при выполнении оператора update, изменение данных не про-
исходит. Ключевые слова where current of означают, что изменение требуется произвести в текущей записи указанного курсора.
По-русски приведенное ранее выражение выглядит следующим образом: "обновить таблицу имяТаблицы, установив значения Столбцов имяСтолбца = значение [, имяСтолбца2 = значение2, ... ] в текущей записи курсора имяКурсора”.
Для удаления записи, на которую указывает курсор, используется выражение вида:
DELETE имяТаблицы WHERE CURRENT OF имяКурсора;.
Если курсор не указывает на какую-либо запись внутри области действия, то возникает ошибка и удаление не происходит.
По-русски приведенное выражение выглядит следующим образом: "удалить в таблице имяТаблицы текущую запись курсора имяКурсора”.
SQL в приложениях
Одно из самых значительных достоинств SQL проявляется в получении данных. Но в SQL нет средств для форматирования выводимых данных и генерации красивых отчетов. SQL может работать сразу с целой таблицей в том смысле, что не зависимо от количества записей в этой таблице необходимые данные можно извлечь из нее с помощью единственного выражения select. Однако работа с одиночными записями в SQL не столь проста. Для этого придется использовать курсоры, описанные в предыдущих разделах.
В отличие от SQL, процедурные языки, такие как С, C++, Visual Basic, Pascal, FORTRAN и др. довольно легко оперируют отдельными записями таблиц базы данных. Однако разработчик приложения на этих языках должен знать и учитывать, каким образом данные хранятся в таблицах базы данных, как расположены столбцы и записи. Процедурные языки достаточно гибки, чтобы с их помощью можно было организовать довольно сложную обработку и отображение на экране данных, а также генерацию сложных отчетов для вывода на печать.
На практике довольно часто имеет смысл комбинировать SQL- запросы с программными кодами на процедурных языках. При этом важно получить объединение как можно большего количества их достоинств и меньшего числа недостатков.
Существуют три основных способа интеграции SQL с программами на других языках: использование встроенного SQL, модульного языка и инструментов среды визуального проектирования приложений, таких как Delphi, C++ Builder и др. Самый распространенный способ — это применение встроенного SQL. Мы будем рассматривать только использование встроенного SQL.
Многие языки программирования допускают вставки в свой программный код SQL-выражений. Перед компиляцией или интерпретацией программы с такими встроенными фрагментами на SQL пропускаются через препроцессор. Каждому встроенному SQL-выражению должна предшествовать специальная команда (директива), сообщающая препроцессору, что за ней следует выражение не на базовом языке, а на SQL. Обычно это директива EXEC SQL.
В следующем примере показано, как в программу на базовом языке вставить SQL-операторы объявления и открытия курсора:
... // Код на базовом языке
// Объявление курсора
EXEC SQL DECLARE name_sum CURSOR FOR
SELECT Имя, Адрес, Сумма_заказа
FROM Клиенты;
... // Код на базовом языке
// Открытие курсора
EXEC SQL OPEN name_sum;
... И Код на базовом языке
Для передачи данных между программой на базовом языке и фрагментом на SQL служат переменные (их также называют базовыми переменными). Прежде чем их использовать в SQL- выражении, они должны быть объявлены. Объявление переменных располагается в специальном сегменте объявлений перед программным сегментом. Сегмент объявлений начинается с директивы:
EXEC SQL BEGIN DECLARE SECTION;
В конце сегмента объявлений указывается директива:
EXEC SQL END DECLARE SECTION;
В следующем примере на языке С показано, как объявляются переменные и как они могут использоваться в SQL-выражении:
// Секция объявления переменных
EXEC SQL BEGIN DECLARE SECTION;
FLOAT summa;// Объявление числовой переменной summa
EXEC SQL END DECLARE SECTION;
// Программа на языке С
main () {
// Объявление курсора с именем name_sum
EXEC SQL DECLARE name_sum CURSOR FOR
SELECT Сумма_заказа
FROM Клиенты
WHERE Имя = "Иванов Иван Иванович";
// Открытие курсора name_sum
EXEC SQL OPEN name_sum;
/* Чтение значения столбца Сумма_заказа в записи курсора в переменную suinma */ EXEC SQL FETCH FROM name_sum INTO : summa;
if (summa > 20000) {// Обработка данных
M Обновление значения столбца Сумма_заказа
EXEC SQL UPDATE Клиенты
SET Сумма_заказа = :summa*0.9 WHERE CURRENT OF name_sum;
}
EXEC SQL CLOSE name_sum; // Закрытие курсора
}
В приведенном коде выполняется следующее:
В сегменте объявлений объявляется переменная summa типа float (числовой тип с плавающей разделительной точкой).
В главной функции main о объявляется и открывается курсор name_sum.
Из текущей (первой) записи в области действия курсора читается значение столбца Сумма_заказа и записывается в переменную summa.
Если значение переменной summa превышает 20 000, то клиенту делается 10% скидка и новое значение суммы заказа (summa*0.9) записывается в столбец сумма заказа в текущей записи курсора. В противном случае ничего не делается.
Курсор закрывается.
В данном примере предполагается, что соединение с базой данных уже установлено.
Примечание
В операторе select для обозначения всех столбцов таблицы можно использовать символ (*). Но в случае использования встроенного SQL в программах рекомендуется не использовать этот символ, а явно указывать требуемые столбцы. Дело в том, что пользователь или вы, как разработчик приложения, можете добавить или удалить столбцы уже после создания приложения.
В этом случае, если использовался символ (*), приложение может получить не тот набор столбцов, на который оно было первоначально рассчитано.
Глава 10
Постоянно хранимые модули
В предыдущих главах SQL рассматривался как язык манипулирования данными, лишенный каких бы то ни было собственных средств управления вычислениями (операторы условного перехода, цикла и т. п.), которыми обладают все процедурные языки. Поэтому при необходимости организовать довольно сложную обработку данных SQL-выражения вставляются в коды программ на процедурных языках. При этом SQL-коды делают то, что им удается лучше всего (например, выборка данных), а сложную, многоэтапную обработку данных выполняют коды на процедурных языках. Такова обычная практика применения SQL в приложениях баз данных. Вместе с тем, в дополнение к SQL-92 был выпущен SQL-92/PSM (Persistent Stored Modules — постоянно хранимые модули). Это дополнение легло в основу одного из разделов SQL: 1999 и SQL:2003, благодаря чему стало возможно решать многие задачи, не выходя за рамки SQL. А именно в SQL появилась возможность выполнять целые блоки SQL- выражений как одно выражение (составную команду), объявлять и использовать в дальнейшем переменные, применять операторы управления вычислениями, определять процедуры и функции, которые можно вызывать в SQL-запросах. Таким образом, многое стало возможным без привлечения внешних программных средств.
Вы можете написать на языке SQL процедуры и функции, которые могут содержать объявления переменных и составные команды SQL. Коды этих процедур и функций могут содержать управляющие структуры, такие как операторы условного перехода
и циклы, а также обычные SQL-выражения. Функции и процедуры задаются в рамках так называемого модуля, который представляет собой контейнер для их размещения. Модуль создается специальной командой SQL и сохраняется в метаданных базы, т. е. становится ее компонентом, подобно таблицам, индексам и т. д. Таким образом, однажды создав модуль с процедурами и функциями, вы можете затем в частных SQL-запросах использовать их вызовы.
В данной главе мы начнем с того, что так или иначе используется в процедурах, функциях и, в конечном счете, в модулях. К непосредственному рассмотрению модулей мы перейдем лишь в конце главы, а начнем с самого простого — составных команд SQL.
Составные команды
В SQL: 1999 и SQL:2003 можно использовать составные команды, которые включают в себя обычные команды SQL, выполняемые в один прием. Это означает, что все команды, входящие в составную команду, вместе передаются на сервер базы данных, там обрабатываются, в результате чего возвращается один ответ, а не множество ответов на каждую из отдельных команд. Это позволяет снизить нагрузку на сеть.
Все команды, входящие в одну составную команду, обрамляются ключевыми словами begin (начало) и end (конец). Например, чтобы вставить данные в несколько связанных таблиц, следует использовать такой синтаксис:
BEGIN
INSERT INTO Продажи (Товар, 1В_клиента, Сумма_заказа)
VALUES ("Телевизор”, 1024, 5800);
INSERT INTO Клиенты (ID_клиента, Имя)
VALUES (1024, "Иванов Иван Иванович”);
END;
Чтобы вставить данный код в программу, например, на процедурном языке, достаточно перед составной командой написать EXEC SQL:
void main () {// Программа на языке С
EXEC SQL
BEGIN
INSERT INTO Продажи. (Товар, 1В_клиента, Сумма_заказа) VALUES ("Телевизор", 1024, 5800);
INSERT INTO Клиенты (1В_клиента, Имя)
VALUES (1024, "Иванов Иван Иванович");
END;
/* Здесь может быть, например, проверка наличия ошибок выполнения составной SQL-команды */ }
Атомарность
Выполнение обычной (не составной команды) SQL может быть успешным или нет. Если команда по каким-либо причинам не смогла выполниться, то в базе данных не происходит никаких изменений. Таково общее правило, выполняемое СУБД. В случае составных команд это не так.
Обратимся к приведенному ранее примеру. В нем производится запись сведений о клиенте и покупке, которую он совершил. Эти сведения записываются в две таблицы: клиенты (справочник клиентов) и продажи (сведения о приобретенном товаре и идентификаторе клиента). Предположим, что запись в таблицу продажи прошла успешно, а в таблицу клиенты — нет. Тогда получится, что зарегистрирована продажа товара отсутствующему клиенту. Быть может, в данном конкретном примере это не так уж и страшно, однако бывают ситуации, чреватые серьезными последствиями. Чтобы подобное не происходило, составную команду можно сделать атомарной (неделимой). Такая команда либо выполняется целиком, либо не выполняется совсем. Обычные команды являются изначально атомарными: если обычная команда не выполнилась, то с базой данных ничего не происходит. Чтобы придать свойство атомарности составной команде, следует за ключевым словом begin дописать слово atomic, как в следующем примере:
void main () {
EXEC SQL
BEGIN ATOMIC
INSERT INTO Продажи (Товар, Ю_клиента, Сумма_заказа)
VALUES ("Телевизор", 1024, 5800);
INSERT INTO Клиенты (10_клиента, Имя)
VALUES (1024, "Иванов Иван Иванович");
END;
/* Здесь может быть, например, проверка наличия ошибок выполнения составной SQL-команды */ }
Если при выполнении атомарной составной команды произойдет сбой на какой-либо входящей в нее простой команде, то база данных вернется к исходному состоянию (говорят, что произойдет откат).
Переменные
В составных командах можно объявлять переменные и присваивать им значения. Переменные могут затем использоваться в простых командах внутри составной команды, но они уничтожаются после ее завершения. Иначе говоря, переменные в SQL являются локальными переменными той составной команды, в которой они объявлены.
Переменная объявляется с помощью выражения вида:
DECLARE имяПеременной тип;
Например:
DECLARE myvar NUMERIC;
После объявления переменной ей можно присвоить значение.
Для этого используется следующий синтаксис:
SET имяПеременной = значение;
Например:
SET vname = "Иванов Иван Иванович";
SET summa = summa * 1.2;
SET S_circle = 3.14 * :radius * :radius;
В следующем примере составная команда содержит объявления переменной и курсора, операторы открытия курсора, чтения значения столбца таблицы в переменную и закрытия курсора:
BEGIN
DECLARE vname CHARACTER(25);
DECLARE mycusor CURSOR FOR
SELECT Имя FROM Клиенты;
OPEN mycursor;
FETCH FROM mycursor INTO vname;
CLOSE mycursor;
END;
Обратите внимание, что имя переменной в операторе fetch указано без ведущего двоеточия. Двоеточие используется в том случае, если переменная была объявлена не внутри составной SQL- команды, а в программе на процедурном языке (см. разд. 9.4).
Обработка состояния
Результат выполнения команды SQL может быть успешным, неудачным или неопределенным. При выполнении команды сервер базы данных обновляет значение специальной переменной sqlstate (статус или состояние SQL). Эта переменная содержит информацию о успешном или неуспешном выполнении команды. В случае неудачи переменная sqlstate содержит код ошибки.
Переменная sqlstate содержит 5 символов, первые два из которых указывают, выполнена команда SQL успешно, неудачно или результат не ясен. Далее приведен список возможных вариантов:
1 оо1 — успешное завершение;
• 011 — случилось что-то незапланированное, однако не известно, произошла ошибка или нет (sqlwarning — предупреждение);
102 ’ — в результате выполнения команды не получено никаких данных, например, запрос вернул пустую таблицу (not found — не найден);
другое значение — сгенерировано исключение.
Первые два из пяти символов в значении переменной sqlstate называют также классом кода состояния. Любой класс кода, отличный от 'ОО1, • 011 или • 02’, означает, что в программе имеются ошибки. Код ошибки представлен последними тремя символами.
В серьезных программах значение переменной sqlstate обычно контролируется после выполнения каждой команды SQL:
при получении кода 1 ос можно продолжать выполнение программы;
при получении кода '01' или 102' требуется решить, как программа должна на это отреагировать. Возможно, что эти сообщения допустимы, и тогда следует продолжить выполнение программы. В противном случае необходимо перейти к выполнению специальной процедуры, которая обрабатывает подобные ситуации;
получение какого-то другого кода означает наличие ошибок. В данном случае следует перейти к процедуре обработки исключений. Если возможны различные состояния sqlstate, то для каждого из них может потребоваться своя процедура обработки.
Для контроля за состоянием выполнения команд SQL необходимо задать (объявить) так называемый обработчик состояний (процедуру обработки исключений). Его объявление можно разместить в составной команде. В объявлении указывается состояние, при возникновении которого вызывается обработчик, а также действие, которое выполняется обработчиком. Само действие может быть простой или составной командой. После успешного выполнения обработчика выполняется некоторое последействие, называемое эффектом. Теперь рассмотрим задание обработчика более подробно.
Обработчик объявляется следующим образом:
DECLARE имяОбработчика CONDITION
FOR состояние;
Обычно имя обработчика выбирают в соответствии с состоянием, которое он должен обработать (например, constraint error — нарушение ограничения целостности данных).
Далее перечислены состояния, которые можно задать для обработчика:
sqlstate value 'ххууу' — указывается значение переменной sqlstate, состоящей ИЗ ПЯТИ СИМВОЛОВ 1 ххууу 1;
sqlexception — класс переменной sqlstate, отличный от ’00’, '01' или ’ 021 (исключение);
sqlwarning — класс переменной sqlstate 101* (предупреждение);
not found — класс переменной sqlstate ’ 02 * (не найден).
Чтобы указать эффект и действие обработчика, используется следующая конструкция:
DECLARE эффект HANDLER
FOR имяОбработчика действие;
Возможны следующие три эффекта обработчика:
continue (продолжение) — выполнение команды, следующей за командой, которая инициировала обработчик. Данный эффект применяется в случае, когда обработчик может устранить любую проблему, которая его вызвала;
exit (выход) — выход из составной команды, содержащей обработчик, и переход к выполнению следующей команды. Данный эффект применяется в случае, когда обработчик не может исправить положение дел, однако нет необходимости отменять изменения, произведенные составной командой;
undo (отмена, откат) — отмена всех предыдущих команд, входящих в составную команду, и переход к выполнению следующей команды. Данный эффект позволяет вернуться в состояние, которое было до начала выполнения составной команды. При использовании эффекта undo необходимо, чтобы составная команда была атомарной, т. е. после ключевого слова begin должно следовать ключевое слово atomic (см. разд. 10.1.1).
В следующем примере используется обработчик события, связанного с нарушением некоторого ограничения целостности данных, которое может возникнуть при выполнении оператора добавления записи insert. Так, при попытке добавить запись с первичным ключом, уже имеющимся в таблице, в переменную sqlstate записывается значение *23000’. В результате возникнет состояние, на которое реагирует обработчик constraint error. Он отменяет все изменения, произведенные командами insert. Команда resignal (действие) передает управление той процедуре, которая вызвала процедуру обработчика. В случае успешного
выполнения обеих команд insert выполняется команда, следующая за ключевым словом end.
Вот код описанного примера:
BEGIN ATOMIC
DECLARE constraint—error CONDITION
FOR SQLSTATE VALUE *23000*;
DECLARE UNDO HANDLER
FOR constraint_error
RESIGNAL;
INSERT INTO Продажи (Товар, 10_клиента, Сумма_заказа)
VALUES (:article, :customer_id, :summa);
INSERT INTO Клиенты (Ю_клиента, Имя)
VALUES (: customer_id, :name);
END;
Операторы условного перехода
Во всех языках программирования основную управляющую структуру реализует оператор условного перехода. Этот оператор имеет тот или иной синтаксис, в зависимости от конкретного языка, но между их реализациями много общего. В SQL возможны два оператора условного перехода:
□ IF...THEN...ELSE...END IF;
□ CASE...END CASE.
Оператор IF
В SQL синтаксис оператора условного перехода if такой:
IF условие
THEN действие!
ELSE действие2
END IF
Условие в операторе if представляет собой некоторое логическое выражение, которое может быть истинным или ложным.
По-русски этот оператор можно было бы записать следующим образом:
ЕСЛИ условие
ТО действие!
ИНАЧЕ действие2
КОНЕЦ
Если условие ИСТИННО, ТО выполняется действие 1, в противном случае выполняется действие2. Например:
IF vname = ’Иванов Иван Иванович’
THEN
UPDATE Клиенты
SET Имя = ’Иванов Иван Иванович’
WHERE 10_клиента = 12345;
ELSE
DELETE FROM Клиенты
WHERE 10_клиента = 12345;
END IF;
Оператор else не является обязательным.
Оператор САSE... END CASE
Если в зависимости от условия возможны два варианта развития событий, то оператор if подходит наилучшим образом. В случае нескольких вариантов удобнее использовать оператор case. При этом выражение условия в операторе case может быть и не логическим. Существуют два варианта синтаксиса оператора case:
оператор case со значениями;
оператор case с условиями поиска.
Примечание
Оператор case применяется также и в условии выборки данных в SQL-выражениях (см. разд. 4.4). Однако там заключительным ключевым словом является просто end, а не end case. Кроме того, оператор в выражении поиска возвращает некоторое значение, а рассматриваемый здесь оператор — нет. В остальном синтаксис операторов case как части SQL-выражения и как управляющей структуры в модуле совпадает.
Оператор CASE... END CASE co значениями
В данном варианте оператора case ... end case вычисляется некое выражение (в частности, просто рассматривается значение некоторой переменной), и, в зависимости от имеющегося значения, указываются действия, которые следует выполнить. Например:
CASE vname
WHEN ’Иванов Иван Иванович’
THEN
INSERT INTO Клиенты (10_клиента, Имя) VALUES (:custID, vname);
WHEN ’Петров Петр Петрович’
THEN
DELETE FROM Клиенты
WHERE 10_клиента = 12345;
ELSE
INSERT INTO Контакты (10_клиента, Имя, Адрес) VALUES (:custID, vname, :address);
END CASE;
Оператор else не является обязательным. Если он указан в операторе case, то его команды выполняются тогда, когда значение условия (в примере это значение переменной vname) не соответствует ни одному из перечисленных в операторах when. Если же оператор else не указан, а значение условия не соответствует ни одному значению в операторах when, то генерируется исключение, которое можно обработать специальным образом (см. разд. 10.1.3).
Оператор CASE... END CASE с условиями поиска
Данный вариант оператора case ... end case похож на первый. Отличие состоит в том, что используются несколько условных выражений, а не одно. Например:
CASE
WHEN vname
IN (’Иванов Иван Иванович’, ’Петров Петр Петрович’, ’Михайлов Михаил Михайлович’)
THEN
INSERT INTO Клиенты (ID_клиента, Имя)
VALUES (:custID,vname);
WHEN vregion
IN .(’ Северо-Запад ’, 1 Москва ’) THEN
INSERT INTO Клиенты (10_клиента, Имя, Регион) VALUES (:custID,vname, vregion);
ELSE
INSERT INTO Контакты (10_клиента, Имя, Адрес, Регион)
VALUES (:custID, vname, :address, vregion);
END CASE;
Операторы цикла
Циклическое (повторяющееся) выполнение набора команд SQL можно организовать с помощью нескольких специальных операторов под общим названием операторов цикла:
LOOP...END LOOP;
WHILE...DO...END WHILE;
REPEAT...UNTIL...END REPEAT;
FOR...DO...END FOR;
ITERATE.
Оператор LOOP...END LOOP
Оператор loop ... end loop (петля) позволяет многократно выполнить некоторую последовательность команд SQL. После выполнения последней команды в этой последовательности управление снова передается первой команде, и все повторяется сначала.
В следующем примере в таблицу клиенты добавляются новые записи. При этом в каждую новую запись столбца id клиента заносится значение, отличающееся от предыдущего на 1. Поскольку переменная customeriD объявлена в коде SQL, то в one-
раторе insert она используется без ведущего двоеточия. Код данного примера следующий:
DECLARE customerlD NUMERIC;
SET customerlD = 12345;
LOOP
SET customerlD = customerlD + 1;
INSERT INTO Клиенты (10_клиента)
VALUES (customerlD);
END LOOP;
Обратите внимание, что данный цикл является "бесконечным” и будет продолжаться до тех пор, пока остается место на диске. Чтобы прекратить работу цикла, используется оператор leave (оставлять, покидать). Справа от оператора leave указывается метка, например, leave mylabel. Как только вычислительный процесс доходит до этого оператора, управление передается следующей команде, расположенной после команды, помеченной этой меткой.
В следующем примере создаются 100 новых записей в таблице клиенты, после чего цикл завершается:
DECLARE customerlD NUMERIC;
SET customerlD = 0;
mylabel:
LOOP
SET customerlD = customerlD + 1;
IF customerlD > 100
THEN
LEAVE mylabel;
END IF;
INSERT INTO Клиенты (ID_клиента)
VALUES (customerlD) ;
END LOOP mylabel;
Оператор WHILE... DO... END WHILE
В операторе while ... do ... end while (пока ... делать ...) после ключевого слова while указывается условие. Пока оно истинно, будут выполняться выражения, расположенные между
ключевыми словами do и end while. Если условие становится ложным, то выражения внутри оператора цикла не выполняются. Следующий код делает то же, что и рассмотренный в конце разд. 10.3. к
DECLARE customerlD NUMERIC;
SET customerlD = 0;
WHILE customerlD < 100 DO
SET customerlD = customerlD + 1;
INSERT INTO Клиенты (10_клиента) VALUES (customerlD);
END WHILE;
Оператор REPEAT... UNTIL...
END REPEATE
Оператор repeat ... until ... end repeate (повторять ... до тех пор, пока ...) похож на оператор while ... do ... end while. Отличие состоит лишь в том, что условие продолжения цикла проверяется не в начале, а в конце оператора после ключевого слова until. Таким образом, оператор repeat ... until ... end repeate всегда обеспечивает выполнение содержащихся в нем выражений хотя бы один раз.
Следующий код делает то же, что и рассмотренные примеры в предыдущих разделах:
DECLARE customerlD NUMERIC;
SET customerlD = 0;
REPEATE
SET customerlD = customerlD + 1;
INSERT INTO Клиенты (10_клиента)
VALUES (customerlD);
UNTIL customerlD < 100;
END REPEATE;
Оператор FOR... DO... END FOR
В SQL оператор цикла for ... do ... end for (для ... делать ...) не аналогичен оператору цикла со счетчиком количества повто-
9 Зак. 120
рений в процедурных языках. В SQL этот оператор позволяет построчно обрабатывать данные без использования процедурного (базового) языка. Точнее, он выполняет следующие операции:
объявление и открытие курсора;
выполнение команд тела цикла, указанных между ключевыми словами do и end for, для каждой записи курсора;
закрывает курсор.
В следующем примере происходит обновление каждой записи таблицы Клиенты Путем ввода В ее столбец Примечание строки
1 Новый клиент’:
FOR customerlD AS mycursor CURSOR FOR
SELECT 1и_клиента FROM Клиенты
DO
UPDATE Клиенты
SET Примечание = ’Новый клиент’
WHERE CURRENT OF mycursor;
END FOR;
Обратите внимание, что данный способ применения курсоров отличается от методов, описанных в гл. 9.
Другой вариант применения оператора for приведен далее:
BEGIN ATOMIC
DECLARE prod CHAR VARYING (400)
DEFAULT '';
DECLARE x ROW;
FOR x AS SELECT * FROM Продажи T
WHERE Т.1П_Клиента = 12345
DO
IF x.Товар <> ’’
THEN
SET prod=prod || ’, ’;
END IF;
SET prod=prod || x.Товар;
END FOR;
UPDATE Клиенты
SET Примечание = prod
WHERE 1В_клиента = 12345;
END;
В данном примере создается список всех товаров, приобретенных клиентом с идентификатором 12345. Этот список представляется в виде строки, в котором наименования товаров разделены запятой. Затем происходит обновление таблицы клиенты: в столбец примечание для клиента с идентификатором 1234 5 заносится список всех приобретенных им товаров.
Оператор ITERATE
Оператор iterate (повторить) применяется в теле циклов, заданных операторами цикла, которые были рассмотрены ранее. Он позволяет изменить последовательность выполнения команд в теле цикла. Если условие в операторе цикла истинно или не задано, оператор iterate начинает новую итерацию цикла (управление передается в начало цикла). Если же условие ложно или неопределенно, выполняются выражения, следующие за оператором iterate, и цикл прекращается. Оператор iterate применяется вместе с меткой для указания точки перехода (подобно оператору leave в разд. 10.3.Г).
Например:
DECLARE vresult CHAR (10);
DECLARE customerlD NUMERIC;
SET vresult = ’ ’;
SET customerlD = 0;
mylabel:
WHILE customerlD < 100 DO
SET customerlD = customerlD + 1;
INSERT INTO Клиенты (10_клиента)
VALUES (customerlD);
ITERATE my label;
SET vresult = ’Готово’;
END WHILE;
В данном примере цикл выполняется до тех пор, пока переменная customerlD не станет равной 99 (оператор iterate передает
управление в начало оператора while, точнее точке с меткой myiabei). При этом в цикле выполняется только оператор добавления записи insert. В следующей итерации переменная customeriD станет равной 100 и тогда оператор iterate прекратит выполнение цикла, переменной vresuit будет присвоено значение 'готово1, а затем вычислительный процесс перейдет к следующей за циклом команде.
Хранимые процедуры и функции
Процедуры и функции являются подпрограммами, т. е. некими блоками выражений SQL, которые можно многократно вызывать в SQL-запросах и/или из других процедур и функций тогда, когда это понадобится. Между процедурами и функциями много общего. И те, и другие выполняют определенные действия и могут принимать параметры, т. е. информацию из внешних программных единиц, которая может использоваться кодом этих процедур и функций. Отличие между процедурами и функциями заключается в способе их вызова. Выражение вызова функции, в отличие от выражения вызова процедуры, может участвовать в других выражениях. Так, например, вызов функции sin(x), вычисляющей синус значения х и возвращающей результат этого вычисления, может участвовать в качестве элемента в более сложном выражении, например, 5 + sin (120). Точнее говоря, в выражении может присутствовать вызов функции. Процедура, в отличие от функции, не может участвовать в выражениях.
Хранимые процедуры и функции сохраняются на сервере базы данных вместе с другими метаданными. Когда вы вызываете процедуру или функцию, сервер базы данных выполняет ее код и, если это задано, возвращает результат клиенту. Процедуры и функции должны быть сначала определены и лишь после этого они могут быть использованы в запросе или приложении путем их вызовов.
Определение процедуры в SQL имеет следующий синтаксис:
CREATE PROCEDURE имяПроцедуры
(IN имяПараметра! типПараметраХ,
IN имяПараметра2 типПараметра2,
IN имяПараметраМ типПараметраМ, OUT ВыходнойПараметр')
BEGIN [ATOMIC]
Операторы SQL
END;
Здесь ключевое слово in обозначает, что следующее за ним выражение является входным параметром^ а ключевое слово оит — выходным (возвращаемым) параметром. Процедуры могут и не иметь входных и/или выходных параметров, т. е. выражения in и оит не являются обязательными. Параметры могут задаваться своими именами. Если процедура должна вернуть в вызывающую программу какое-то значение как значение переменной (параметра), то эта переменная должна быть объявлена во внешней программе. После указания входных и/или выходных параметров следует код тела процедуры, который заключается в ключевые слова begin и end. Если этот код представляет собой составную команду, содержащую несколько команд SQL, то после ключевого слова begin следует добавить ключевое слово atomic (см. разд. 10.1.1).
В следующем примере создается процедура, которая обновляет таблицу клиенты для некоторых клиентов. Процедура не должна возвращать никакого значения.
CREATE PROCEDURE updateCustomer
(IN vcustomerid INTEGER,
IN vNameCHAR(20),
IN vNoteCHAR(120))
BEGIN ATOMIC
UPDATE Клиенты
SET Имя = vName, Примечание = vNote
WHERE 10_клиента = vcustomerid;
END;
Чтобы вызвать процедуру, применяется команда call. Например, следующее выражение вызывает рассмотренную процедуру
updatecustomer, передавая ей параметры, указывающие, какие значения следует обновить в таблице клиенты:
CALL updatecustomer(1025, 1Иванов Иван Иванович1, ’Очень важный клиент’);
Хранимая функция создается аналогично процедуре. Особенность заключается в том, что входные параметры непосредственно указываются в круглых скобках справа от имени функции, а выходной параметр (точнее, его тип) — после ключевого слова returns.
Вот пример определения функции products:
CREATE FUNCTION products (vcustomerid)
RETURNS CHAR VARYING (400)
BEGIN
DECLARE prod CHAR VARYING (400)
DEFAULT ’';
DECLARE x ROW;
FOR x AS SELECT * FROM Продажи T
WHERE T.ID—Клиента = vcustomerid
DO
IF x.Товар <> ’’
THEN SET prod = prod || ', ’;
END IF;
SET prod = prod I| x.Товар;
END FOR;
RETURN prod;
END;
Данная функция создает список всех товаров, приобретенных клиентом с указанным идентификатором (vcustomerid), и возвращает его в виде строки, в которой наименования товаров разделены запятыми.
В отличие от процедуры, функция может участвовать в SQL- выражении. Следующий код содержит вызов функции products. Он обновляет таблицу клиенты, занося в столбец примечание для клиента с идентификатором 12345 список всех приобретенных им товаров:
SET vcustomerid = 12345;
UPDATE Клиенты
SET Примечание = ’Приобретено:’ || products(vcustomerid) WHERE ID клиента = vcustomerid;
Хранимые модули
Хранимые модули могут содержать множество определений процедур и/или функций. Вообще говоря, процедуры и функции, входящие в один модуль, могут быть и не связаны между собой. Язык SQL предоставляет возможность задавать различным пользователям базы данных определенные полномочия (см. гл. 11). Расширение SQL, поддерживающее постоянно хранимые модули (SQL/PSM), добавляет к уже существующим правам доступа полномочие на выполнение модуля. Оно распространяется на все процедуры и функции, которые содержатся в этом модуле. Поэтому распределение процедур и функций между модулями целесообразно производить с учетом полномочий различных пользователей.
Для создания постоянно хранимого модуля служит команда SQL:
CREATE MODULE имяМодуля
END MODULE;
Вот пример модуля, содержащего описания процедуры и функции:
CREATE MODULE mylibrary
CREATE PROCEDURE updateCustomer
(IN vcustomerid INTEGER,
IN vNameCHAR(20),
IN vNoteCHAR(120))
BEGIN ATOMIC
UPDATE Клиенты
SET Имя = vName, Примечание = vNote
WHERE 1В_клиента = vcustomerid;
END;
CREATE FUNCTION products (vcustomerid)
RETURNS CHAR VARYING (400)
BEGIN
DECLARE prod CHAR VARYING (400)
DEFAULT '’;
DECLARE x ROW;
FOR x AS SELECT * FROM Продажи T
WHERE T.ID—Клиента = vcustomerid DO
IF x.Товар <> 11
THEN SET prod = prod || ', ’;
END IF;
SET prod = prod || x.Товар;
END FOR;
RETURN prod;
END;
END MODULE;
Глава 11
Управление правами доступа
У базы данных может быть несколько пользователей с различными правами доступа к ее объектам. Так, одним пользователям разрешено только просматривать некоторые таблицы, другим разрешено только просматривать и добавлять новые записи, но запрещено их удалять и т. п. Некоторые пользователи могут устанавливать права доступа к объектам базы данных для других пользователей.
Разграничение прав доступа является важным средством защиты базы данных от неправильного использования содержащейся в ней информации различными категориями пользователей.
Пользователи
Пользователи баз данных могут иметь разные привилегии (полномочия, права доступа) и по объему этих привилегий разделяются на несколько категорий;
администратор базы данных;
владелец объектов базы данных;
привилегированный пользователь, имеющий право предоставлять привилегии;
привилегированный пользователь, не имеющий права предоставлять привилегии;
рядовые пользователи.
Администратор базы данных
Наибольшими привилегиями обладает администратор базы данных. Он имеет все права на любые действия с базой данных, в том числе и право предоставлять и аннулировать привилегии для других пользователей. Разумеется, при этом он несет и бульшую ответственность, поскольку легко может испортить как данные, так и саму базу, или нарушить уже налаженные правила работы с ней.
При установке СУБД на компьютер требуется ввести регистрационное имя или учетную запись пользователя (login) и пароль (password). В первый раз следует воспользоваться сведениями из руководства по установке. Зарегистрировавшись под данным именем, вы автоматически становитесь администратором базы данных, т. е. привилегированным пользователем с полным объемом прав. Теперь вы можете изменить пароль, чтобы никто, кроме вас, не мог стать ни администратором, ни даже обычным пользователем базы данных, если вы не сообщите ему пароль. Если же вы передадите пароль другому пользователю, то он может войти в систему в качестве администратора и, изменив пароль, лишить вас возможности быть администратором. Обычно у базы данных есть несколько администраторов на случай, если один из них заболеет или уволится.
Итак, администратор, имея все привилегии, первым делом "создает" пользователей базы данных, определяя для них имена и права. Он может создать и максимально привилегированного пользователя, т. е. еще одного администратора. В любой момент администратор может изменить ранее установленные права любого пользователя.
Если администратору необходимо выполнить какую-то работу, для которой не требуются все привилегии, то ему лучше войти в систему под именем пользователя с минимальными привилегиями, которые позволяют решить задачу. Это обычная мера защиты от тяжелых последствий возможных ошибок.
Владелец объектов базы данных
Любой пользователь, который создал некий объект базы данных (таблицу или представление), становится владельцем (ower)
этого объекта, т. е. привилегированным пользователем, либо может назначить другого владельца.
Владелец таблицы обладает всеми привилегиями относительно этой таблицы, включая управление доступом к ней. Представление, являясь виртуальной таблицей, всегда создается на основе уже имеющихся таблиц базы данных. Причем владелец представления может и не быть владельцем исходных таблиц. Поэтому владелец представления получает относительно него привилегии, аналогичные имеющимся у него относительно исходных таблиц. Это делается для того, чтобы, создав представление, нельзя было обойти защиту исходной таблицы, владельцем которой является другой пользователь.
Другие пользователи
Администраторы и владельцы объектов базы данных могут предоставлять привилегии другим пользователям. Среди этих привилегий есть и право предоставления привилегий. Пользователи, не являющиеся администраторами или владельцами, а также те, кому привилегированные пользователи специально не предоставили права доступа, называются публикой (public). Если привилегированный пользователь предоставляет права доступа типа public, то их получают все пользователи базы данных.
Создание пользователей
Обычно к СУБД прилагается ряд утилит, в число которых входит и утилита для создания пользователей и предоставления им прав. С ее помощью администратор может создать пользователя и наделить его определенными привилегиями.
В SQL для создания пользователя служит оператор create user:
CREATE USER имяПользователя
[WITH
[SYSID идентификаторПользователя1
[PASSWORD 'пароль1])
[CREATEDB | NOCREATEDB]
[CREATEUSER | NOCREATEUSER]
[IN GROUP имяГруппы [, ... ] ]
[VALID UNTIL * время’];
Здесь в квадратных скобках указаны необязательные компоненты, а вертикальной чертой разделены альтернативные варианты значений. Данный оператор позволяет создать имя пользователя, задать ему пароль (password), дать особые права на создание базы данных (createdb) и пользователей (createuser), распределить по группам (in group) и ограничить время, в течение которого данный пользователь будет актуален (valid until).
Чтобы удалить пользователя из системы, можно применить такой оператор:
DROP USER имяПользователЯ;
11.2. Предоставление привилегий
Как уже отмечалось ранее, администратор базы данных имеет все привилегии на все объекты базы данных; владелец объекта также имеет на него все права. Произвольный пользователь не имеет прав до тех пор, пока они не будут предоставлены ему специально тем пользователем, у которого уже есть эти права и который обладает также правом предоставлять права другим пользователям. Это рекурсивное определение возможностей назначать привилегии. На практике эта рекурсия "раскручивается” следующим образом. Сначала администратор создает пользователей (точнее, их имена) и предоставляет им некоторые права. Если кто-то из уже созданных пользователей имеет привилегию передавать права, то он может, создав таблицу или представление, передать права на нее другим пользователям. И так далее.
Предоставление привилегий осуществляется с помощью оператора grant (предоставлять, разрешать), который имеет такой синтаксис:
GRANT списокПрав
ON объект
ТО списокПользова телей
[WITH GRANT OPTION];
Здесь необязательные (в квадратных скобках) ключевые слова with grant option (с правом передачи) применяются, если пользователям предоставляется право передавать права.
Права в списке прав оператора grant разделяются запятыми. Если необходимо предоставить все права, то вместо списка можно указать ключевые слова all privileges (все привилегии).
В SQL:2003 права могут принимать следующие значения:
select — право просмотра;
delete — право удаления записей;
insert [ (списокСтолбцов1) ] — право добавления новых записей с установкой значений для указанных столбцов; если имена столбцов не указаны, то для всех столбцов;
update [ (списокСтолбцов) ] — право изменения значений указанных столбцов; если имена столбцов не указаны, то предполагаются все столбцы;
references [ (списокСтолбцов') ] — право доступа к таблице, на которую ссылается данная таблица;
usage — право на домены, наборы символов, сопоставления и трансляции;
under — право на структурированные типы данных;
trigger — право на использование триггеров;
execute — право на выполнение внешней программы.
Объект в операторе grant может принимать следующие значения:
[TABLE] имяТаблицы",
DOMAIN имяДомена",
COLLATION имяСопоставления*,
CHARACTER SET имяСимвольногоНабора*,
TRANSLATION имя Трансляции*,
TYPE схема тически_обозначенный_пользователъский_тип\
SEQUENCE имяГенератораПоследовательности специфика торУка за теляШа блона.
Список пользователей в операторе grant состоит из регистрационных имен пользователей, разделенных запятыми. Вместо списка можно указать ключевое слово public (публика). В этом случае права, указанные в операторе grant, получают все пользователи базы данных.
Роли и группы
Если пользователей базы данных немного, то при предоставлении им прав можно использовать простые списки их имен. В противном случае назначение и контроль за правами становятся довольно трудоемкими. Удобным выходом из положения является использование ролей или групп пользователей. В SQL:2003 используется понятие роли (role), однако его нет в основном стандарте, а потому может не быть в некоторых реализациях. Перед использованием ролей необходимо убедиться, что их поддерживает ваша СУБД. Многие СУБД применяют понятие группы пользователей, которое аналогично понятию роли.
При использовании роли (группы) права назначаются для роли (группы), а затем для каждого пользователя указывается, какую роль он играет или в какую группу входит. Как роль, так и группа характеризуются именем.
Для создания роли используется такой синтаксис:
CREATE ROLE имяРоли;
После создания роли она назначается пользователям:
GRANT имяРоли ТО списокПользова телей;
Назначение прав для роли производится с помощью следующего выражения:
GRANT списокПрав
ON объект
ТО имяРоли
[WITH GRANT OPTION];
Если реализация SQL поддерживает понятие не роли, а группы, то синтаксис оператора для создания группы может быть следующим:
CREATE GROUP имяГруппы WITH списокПользователей;
В следующих разделах рассмотрим несколько примеров предоставления различных привилегий.
Право просмотра данных
Для предоставления права просмотра таблицы или представления, например, продажи всем пользователям базы данных можно воспользоваться следующим оператором:
GRANT SELECT
ON Продажи
ТО PUBLIC;
Таблица продажи может иметь столбец, например, цена, который нежелательно предоставлять всем пользователям. В этом случае рекомендуется на основе таблицы продажи создать представление, в котором нет столбцов, просмотр которых вы хотели бы запретить:
CREATE VIEW Продажи_вид1 AS
SELECT Товар, Количество, Описание
FROM Продажи;
GRANT SELECT
ON Продажи__вид1
ТО PUBLIC;
Право изменять данные
Предположим, менеджеру по продажам (регистрационное имя SaiesManager) разрешено изменять значения цена в таблицах товары и продажи. Чтобы предоставить ему такое право, достав точно выполнить следующий оператор:
GRANT UPDATE (Цена)
ON Товары, Продажи
ТО SaiesManager;
Если менеджеру нужно разрешить изменять значения нескольких столбцов, то в операторе grant следует перечислить их имена через запятую, а если нужно разрешить изменять все столбцы, то стоит использовать такой оператор:
GRANT UPDATE
ON Товары, Продажи
ТО SaiesManager;
Для предоставления права добавлять записи вместо ключевого слова update.нужно использовать ключевое слово insert. В следующем примере предоставляются права на изменение и добавление записей во все столбцы:
GRANT UPDATE, INSERT
ON Товары, Продажи ТО SalesManager;
Право удалять записи
Право на удаление записей предоставляется очень просто, например: GRANT DELETE
ON Клиенты, Сотрудники, Товары ТО SuperManager;
Право на использование ссылок
Теперь рассмотрим пример предоставления прав на использование ссылок одних таблиц на другие. Так, две таблицы оказываются связанными, если в одной таблице задан внешний ключ, ссылающийся на первичный ключ в другой таблице (см. разд. 7.1.3). В этом случае если у пользователя есть права доступа к первой таблице, то он может получить некоторые сведения и из второй, даже если у него нет права доступа к этой таблице. Предположим, пользователю стало известно о появлении в базе данных особой таблицы Сотрудники секретно, содержащей отдельный список сотрудников, например, представленных к премии или, наоборот, к увольнению. Ему также стало известно, что первичным ключом в этой таблице является столбец id. Однако пользователь не имеет никаких прав на эту таблицу, даже права на просмотр. Тогда он может узнать, кто попал в секретный список, следующим образом.
Сначала он создает таблицу mytab со столбцом id, который определяет как внешний ключ со ссылкой на таблицу сотрудни- ки_секретно:
CREATE TABLE mytab (
ID INTEGER REFERENCES Сотрудники_секретно
) r
Затем пользователь пытается добавить новые записи в свою таблицу mytab, подбирая значения столбца id. Если выбранное им значение принимается таблицей mytab, то сотрудник с данным идентификатором находится в секретном списке, а в противном случае —- нет. Узнав таким способом список идентификаторов сотрудников секретного списка, шпион может по другой таблице (например, Сотрудники) узнать фамилии тех, кто был занесен в секретный список.
Поэтому согласно стандарту SQL:2003 права на использование ссылок должны устанавливаться явно:
GRANT REFERENCES (ID)
ON Сотрудники__секретно
TO PersonManager;
Это не позволит использовать описанный прием взлома защиты.
Право на домены
При создании таблиц нередко используют определения доменов. Домены позволяют определить однотипные столбцы с одинаковыми ограничениями, находящиеся в различных таблицах (см. разд. 7.1.1).
Например, можно определить домен Bonus (премия) с типом данных decimal (9,2) и ограничением, согласно которому премия не может быть отрицательной и больше 15 000:
CREATE DOMAIN Bonus
DECIMAL (9, 2)
CHECK (Bonus >= 0 AND Bonus <= 15000);
Тогда этот домен можно применить для определения столбцов в одной или нескольких таблицах. Например:
CREATE TABLE Платежная_ведомость (
ID INTEGER,
Имя VARCHAR (25) ,
Оклад DECIMAL (9,2), Премия Bonus
);
Однако при использовании доменов могут возникнуть вопросы, связанные с безопасностью. Так, верхняя граница премии может
являться секретом для большинства пользователей. Однако любой пользователь может создать таблицу со столбцом, который определен через домен Bonus. Постепенно увеличивая значения в этом столбце, пока они принимаются таблицей, можно узнать максимальное значение (попытка ввести большее значение будет отвергнута СУБД).
Чтобы не допустить такой возможности, владелец домена (тот, кто его создал) должен явно предоставить права на его использование, например:
GRANT USAGE
ON DOMAIN Bonus
TO PersonManager, President;
Право предоставлять права
На практике нередко делают так, чтобы права доступа мог предоставлять только администратор и владельцы объектов базы данных. При этом владельцы предоставляют права только на объекты, которыми владеют. Третьи лица, получившие права доступа от администратора и/или владельцев, уже не могут предоставлять права. Во многих случаях это вполне разумное ограничение, позволяющее администратору и владельцам сохранить достаточно полный контроль над системой. Вместе с тем встречаются ситуации, в которых желательно делегировать право предоставлять свои собственные права другим пользователям (например, своим сменщикам, на период отпуска и т. п.). С этой целью в операторе grant используются ключевые слова with GRANT OPTION.
В следующем примере право обновлять и добавлять записи в таблице сотрудники предоставляется менеджеру по продажам с именем SaiesManager. Причем ему предоставляется право передавать полученные права доступа другому пользователю.
GRANT UPDATE, INSERT
ON Сотрудники
TO SaiesManager
WITH GRANT OPTION;
Теперь менеджер по продажам может передать свои права помощнику С именем AssistantManager:
GRANT UPDATE, INSERT
ON Сотрудники
TO AssistantManager;
11.3. Отмена привилегий
Co временем обязанности пользователей базы меняются, кто-то из них может перейти на сторону конкурента и, наконец, некоторые ранее предоставленные привилегии могут оказаться просто ошибочными с точки зрения общей защиты базы данных. В этих случаях появляется необходимость отменить привилегии.
Отмена привилегий выполняется с помощью оператора revoke (отмена, аннулирование):
REVOKE [GRANT OPTION FOR] списокПрав
ON объект
FROM списокПользователей [RESTRICT | CASCADE] ;
Здесь в квадратных скобках указаны необязательные элементы, а вертикальной чертой разделены альтернативные варианты значений. Не трудно заметить, что синтаксис оператора revoke аналогичен синтаксису оператора grant.
С помощью оператора revoke можно отменить указанные в списке права для перечисленных пользователей. При этом можно аннулировать все права или только некоторые. Рассмотрим значения необязательных ключевых слов:
cascade (каскадно) — отменяются указанные права у перечисленных пользователей, а также у тех, кому эти пользователи успели предоставить данные права;
restrict (ограничить) — отменяются права у пользователей, которые никому другому их не предоставляли. Однако если пользователь уже успел предоставить права, указанные в этом операторе revoke, то оператор не будет выполнен и появится сообщение об ошибке;
grant option for (право предоставления для) — применяется, чтобы отменить у пользователя право передавать указанные
права, но оставить эти права за пользователем. Если оператор REVOKE содержит GRANT OPTION FOR И CASCADE, ТО ОТМСНЯЮТСЯ все права, предоставленные пользователем, а также право этого пользователя на предоставление прав. Если в revoke есть grant option for и restrict, то возможны следующие два варианта:
если пользователь еще нс предоставил другому пользователю права, которые вы у него отменяете, оператор revoke выполняется, отменяя также право этого пользователя предоставлять права;
если пользователь уже предоставил кому-нибудь хотя бы одно из отменяемых прав, то права не отменяются и возвращается код ошибки.
Следует иметь в виду, что пользователь может получить одни и те же права от различных привилегированных пользователей. Если один из них отзовет предоставленные права, у пользователя они все равно могут остаться, поскольку продолжают действовать права, предоставленные кем-то другим. При передаче прав от одного пользователя другому возникают зависимости прав: права одного пользователя зависят от того, продолжает ли ими обладать другой.
Рассмотрим еще одно применение оператора revoke, связанное с целевой задачей не отмены, а наоборот, предоставления прав. Очень часто предоставление прав многим пользователям на многочисленные объекты связано с большими объемами SQL- кода. Комбинируя операторы grant и revok, предоставляя сначала широкие права для многих, а затем ограничивая их для некоторых пользователей, можно сократить общий объем SQL- кода.
Предположим, что пользователи Иванов и Петров имеют права просматривать, добавлять, удалять и обновлять записи в таблице Клиенты (lD_клиента, Имя, Адрес, Телефон, Сумма_заказов, Примечания), но не могут обновлять значения столбца ю клиента. Все остальные пользователи могут только просматривать записи. Данное распределение прав доступа к таблице можно выполнить с помощью следующих операторов:
GRANT SELECT
ON Клиенты
ТО PUBLIC;
GRANT INSERT, DELETE
ON Клиенты
TO Иванов, Петров;
GRANT UPDATE
ON Клиенты (Имя, Адрес, Телефон, Сумма_заказов, Примечания)
ТО Иванов, Петров;
Эквивалентный результат, но без длинного перечня имен столбцов получается с помощью такого кода: GRANT SELECT
ON Клиенты
ТО PUBLIC;
GRANT INSERT, DELETE, UPDATE
ON Клиенты
TO Иванов, Петров;
REVOKE UPDATE
ON Клиенты (10_клиента)
TO Иванов, Петров;
Во втором варианте сначала предоставляется право обновлять все столбцы, а затем отзывается право обновлять столбец ID клиента.
Приложение
Зарезервированные слова SQL
ABS
CARDINALITY
COVAR_SAMP
ALL
CASCADED
CREATE
ALLOCATE
CASE
CROSS
ALTER
CAST
CUBE
AND
CEIL
CUME_DIST
ANY
CEILING
CURRENT
ARE
CHAR
CURRENT_COLLATION CURRENT_DATE
ARRAY
CHAR-LENGTH
CURRENT_DEFAULT_
AS
CHARACTER
TRANSFORM—GROUP
ASENSITIVE
CHARACTER-LENGTH
CURRENT_PATH
ASYMMETRIC
CHECK
CURRENT_TIME
AT
CLOB
CURRENT_TIMESTAMP
ATOMIC
CLOSE
CURRENT—TRANSFORM—
AUTHORIZATION
COALESCE
GROUP-FOR-TYPE
AVG
COLLATE
CURRENT_USER
BEGIN
COLUMN
CURRENTJROLE
BETWEEN
COMMIT
CURSOR
BIGINT
CONDITION
CYCLE
BINARY
CONNECT
DAFAULT
BLOB
CONSTRAINT
DATE
BOOLEAN
CONVERT
DAY
BOTH
CORR
DEALLOCATE
BY
CORRESPONDING
DEC
CALL
COUNT
DECIMAL
CALLED
COVAR_POP
DECLARE
DEFAULT
FUSION
MAX
DELETE
GET
MEMBER
DENSE_RANC
GLOBAL
MERGE
DEREF
GRANT
METHOD
DESCRIBE
GROUP
MIN
DETERMINISTIC
GROUPING
MINUTE
DISCONNECT
HAVING
MOD
DISTINCT
HOLD
MODIFIERS
DOUBLE
HOUR
MODULE
DROP
IDENTITY
MONTH
DYNAMIC
IN
MULTISET
EACH
INDICATOR
NATIONAL
ELEMENT
INNER
NATURAL
ELSE
INOUT
NCHAR
END
INSENSITIVE
NCLOB
END-EXEC
INSERT
NEW
ESCAPE
INT
NO
EVERY
INTEGER
NONE
EXCEPT
INTERSECT
NORMALIZE
EXEC
INTERSECTION
NOT
EXECUTE
INTERVAL
NULL
EXISTS
INTO
NULLIF
EXP
IS
NUMERIC
EXTERNAL
JOIN
OCTET_LENGHT
EXTRACT
LANGUAGE
OF
FALSE
LARGE
OLD
FETCH
LATERAL
ON
FILTER
LEADING
ONLY
FLOAT
LEFT
OPEN
FLOOR
LIKE
OR
FOR
LN
ORDER
FOREIGN
LOCAL
OUT
FREE
LOCALTIME
OUTER
FROM
LOCALTIMESTAMP
OVER
FULL
LOWER
OVERLAPS
FUNCTION
MATCH
OVERLAY
PARAMETER
ROW
TIME
PARTITION
ROW_NUMBER
TIMESTAMP
PERCENT_RANK
ROWS
TIMEZONE_HOUR
PERCENTILE_DISC
SAVEPOINT
TIMEZONE_MINUTE
POSITION
SCOPE
TO
POWER
SCROLL
TRAILING
PRECISION
SEARCH
TRANSLATE
PREPARE
SECOND
TRANSLATION
PRIMARY
SELECT
TREAT
PROCEDURE
SENSITIVE
TRIGGER
RANGE
SESSIONJJSER
TRIM
RANK
SET
TRUE
READS
SIMILAR
UNION
REAL
SMALLINT
UNIQUE
RECURSIVE
SOME
UNKNOWN
REF
SPECIFIC
UNNEST
REFERENCES
SPECIFICTYPE
UPDATE
REFERENCING
SQL
UPPER
REGR_AVGX
SQLEXEPTION
USER
REGR_AVGY
SQLSTATE
USING
REGR_COUNT
SQLWARNING
VALUE
REGR_INTERCEPT
SQRT
VALUES
REGR_R2
START
VAR_POP
REGR_SLOPE
STATIC
VAR_SAMP
REGR_SXX
STDDEV_POP
VARCHAR
REGR_SXY
STRDDEV_SAMP
VARYING
REGR_SYY
SUBMULTISET
WHEN
RELEASE
SUBSTRING
WHENEVER
RESULT
SUM
WHERE
RETURN
SYMMETRIC
WIDTH_BUCKET
RETURNS
SYSTEM
WINDOW
REVOKE
SYSTEM_USER
WITH
RIGHT
TABLE
WITHING
ROLLBACK
TABLESAMPLE
WITHOUT
ROLLUP
THEN
YEAR
Предметный указатель
А
ABS 124
ABSOLUTE 230
ADD COLUMN 197
ALL 107, 137
ALL PRIVILEGES 261
ALTER COLUMN 197
ALTER TABLE 196
AND 113
ANY 107
ASC 112
ASENSITIVE 225
ATOMIC 239
AVG 118
В
BEGIN 240
BEGIN... END 238
BEGIN ATOMIC 244
BETWEEN 104
BIGINT 78
BOOLEAN 80
C
CALL 254
CARDINALITY 124
CASE 129, 130
CASE ... END CASE 245
CEIL 125
CHARACTER 76
CHARACTER-LENGTH 124
CHECK 188
CLOB 76
CLOSE 228
COALESCE 132
COMMIT 211
CONDITION 242
CONSTRAINT 186
CONTINUE 243
COUNT 117
CREATE DISTINCT TYPE 86
CREATE DOMAIN 189,190,265
CREATE FUNCTION 254
CREATE GROUP 262
CREATE MODULE 255
CREATE PROCEDURE 252
CREATE ROLE 262
CREATE TABLE 185
CREATE TYPE 88
CREATE USER 259
CREATE VIEW 203
CURRENT-DATE 125
CURRENT-TIME 125
CURRENT_TIMESTAMP 125
CURSOR 250
D
DATA 80
DECIMAL 78
DECLARE 240
DECLARE SECTION 233,234
DEFAULT 188
DELETE 176
DESC 112
DISTINCT 108
DOUBLE PRECISION 79
DROP COLUMN 197
DROP TABLE 195
DROP USER 260
E
EXCEPT 153
EXEC SQL 233, 238
EXISTS 107
EXIT 243
EXP 124
EXTRACT 124
F
FETCH INTO 229
FINAL 89
FIRST 229
FLOAT 79
FLOOR 125
FOR ... DO ... END FOR 249
FOR UPDATE 227
FOREIGN KEY 193
FOREIGN
KEY ... REFERENCES 191
FROM 99
FULL JOIN 162
G
GRANT 260
GRANT USAGE 266
GROUP BY 99, 109
H
HANDLER 243
HAVING99, 111
I
IF 244
IN и NOT IN 104
INSENSITIVE 225
INSERT 174
INTEGER 78
INTERSECT 152
INTERVAL 83
IS NULL 106
ITERATE 251
J
JOIN... ON 157
JOIN... USING 157
L
LAST 230
LEFT OUTER JOIN 159
LIKE и NOT LIKE 105
LN 124
LOOP ... END LOOP 247
LOWER 122
M
MATCH 108, 183
MAX 118
MIN 119
MOD 124
N
NATURAL JOIN 155
NEXT 229
NO SCROLL 225
NOT 113
NOT FINAL 88
NOT FOUND 243
NOT NULL 187
NULL 90
NULLIF 132
NUMERIC 78
О
ON DELETE 194
ON UPDATE 194
OPEN 228
OR 113
ORDER BY 99, 112
OVERLAPS 108
Р
POSITION 123
POWER 124
PRIMARY KEY 61, 187
PRIOR 229
PUBLIC 259
R
REAL 78
RELATIVE 230
RENAME COLUMN 197
RENAME TO 197
REPEAT ... UNTIL ... END
REPEATE 249
RESIGNAL 243
RETURN 254
RETURNS 254
REVOKE 267
RIGHT OUTER JOIN 161
ROLLBACK 211
S
SCROLL 225
SELECT ... FROM 95
SENSITIVE 225
SET TRANSACTION 212
SMALLINT 78
SOME 107, 137
SQLEXCEPTION 242
SQLSTATE 241
SQLWARNING 243
SQRT 124
SUBSTRING 121
SUM 118
T
TIME WITH TIME ZONE 81
TIME WITHOUT TIME ZONE 81
TIMESTAMP WITH TIME
ZONE 82
TIMESTAMP WITHOUT TIME
ZONE 82
TOP 97
TRIM 122
U
UNDO 243
UNION 150
UNION JOIN 162
UNIQUE 108, 187
UPDATE 178
UPPER 122
V
VARCHAR 76
W
WHERE 96, 98, 102
WHILE ... DO ...
... END WHILE 248
WIDTH RECURSIVE 165
WIDTH-BUCKET 125
WITH GRANT
OPTION 260, 266
A
Администратор базы данных 257
Аномалия модификации 62
Арность отношения 33
Атомарность 239
Атрибут 34
В
Владелец объектов 257
Выражения:
вычисляемые 126
условные 129
Г
Группа пользователей 262
Группировка 109, 111
д
Декартово произведение: таблиц 147 множеств 30
Декомпозиция: корректная 43 некорректная 44 отношений 42
Добавление записей 173 Домен 34, 189, 265 Дополнение множества 24
3
Зависимость: многозначная 49 правила вывода 55 транзитивная 67 тривиальная 48, 58 функциональная 45
Законы Моргана 24
И, К
Изменение данных 178
Кванторы 27, 137
Ключ 57
внешний 192 первичный 187
Курсор 222, 250 закрытие 228 область действия 226 объявление 224 ориентация 229 открытие 228 перемещаемость 225 разрешение обновления 227
О
Объединение множеств 23
Ограничения:
столбцов 187
таблиц 190
целостности 30
целостности отношений 60
Операторы:
арифметические 126
для уточнения запроса 98
логические 113
условного перехода 244
цикла 247
Операция:
внешнее соединение 159
вычитание 153
декартово произведение 147 естественное
соединение 39, 155
объединение 149
пересечение 152
проекции 38
селекции 35
соединение по именам
столбцов 157
сужения 35
условное соединение 157
Отношение 29
включения 22
принадлежности 22
структура 34 унарное, бинарное,
тернарное 31
чувствительность zz?п
МПеременные240
Множество 17Пересечениемножеств 23
МодификацияПодзапросы136
таблиц 196Права доступа 257
Модули хранимые 255Предикат 21
Предикаты:
для вложенных
запросов 106
сравнения 103
Представление 200
Преобразование типов 91
функция CAST() 92
Привилегированные
пользователи 257
Процедуры хранимые 252
Псевдоним 120, 141, 146
Р
Работа с отдельными записями 229
Разность множеств 23
Рекурсивные запросы 164
Роль 262
Рядовые пользователи 257
С
Сегмент объявлений 233
Сложные запросы 135
Создание
базы данных:
в Enterprise Manager 8
в Microsoft Access 2003 11
группы 262
домена 189
отдельного типа данных 86 пользователя 259 представления 203
роли 262
структурированного типа
данных 88
таблицы 185
Сортировка 112, 226
Составные команды 238
Статус SQL 241
Т
Типы данных 73 дата-время 80 запись 84 интервал 83 логический 80 массив 85 отдельные 86 пользовательские 86 строковые
(символьные) 76 структурированные 87 числовые 77
Транзакция 209
Удаление: записей 176 таблиц 195
Универсум 24
Управление правами доступа 257
Ф
Функции: даты-времени 125 итоговые 117 обработки значений 121 строковые 121 хранимые 252 числовые 123
Функция CAST( ) 92
ц
Целостность: доменная 61 семантическая 60 ссылочная 61
Транзакции и курсорыТранзакции и курсорыISBN 5-94157-823-7ISBN 5-94157-823-7ДУНАЕВ ВАДИМ ВЯЧЕСЛАВОВИЧ, к. т. н_, специалист в области компьютерных информационных технологий. Автор нескольких популярных книг по разработке Web-приложений и компьютерной графике. «Сам себе Web-мастер». «Сам себе Web-дизайнер-. «Графика для WEB HTML, скрипты и стили и др.ДУНАЕВ ВАДИМ ВЯЧЕСЛАВОВИЧ, к. т. н_, специалист в области компьютерных информационных технологий. Автор нескольких популярных книг по разработке Web-приложений и компьютерной графике. «Сам себе Web-мастер». «Сам себе Web-дизайнер-. «Графика для WEB HTML, скрипты и стили и др.
Транзакции и курсоры
Транзакции и курсоры
ISBN 5-94157-823-7
ISBN 5-94157-823-7
ДУНАЕВ ВАДИМ ВЯЧЕСЛАВОВИЧ, к. т. н_, специалист в области компьютерных информационных технологий. Автор нескольких популярных книг по разработке Web-приложений и компьютерной графике. «Сам себе Web-мастер». «Сам себе Web-дизайнер-. «Графика для WEB HTML, скрипты и стили и др.
ДУНАЕВ ВАДИМ ВЯЧЕСЛАВОВИЧ, к. т. н_, специалист в области компьютерных информационных технологий. Автор нескольких популярных книг по разработке Web-приложений и компьютерной графике. «Сам себе Web-мастер». «Сам себе Web-дизайнер-. «Графика для WEB HTML, скрипты и стили и др.
Просто и доступно о языке структурированных запросов
БАЗЫ ДАННЫХ
ЯЗЫК SQL
ДЛЯ СТУДЕНТА
Книга посвящена языку структурированных запросов для взаимодействия с базами данных—SQL. начиная с доступного изложения теории отношений (реляционной теории) и заканчивая вопросами администрирования СУБД с помощью запросов. На практических примерах подроб ню рассмотрены основные конструкции языка, а такж? основные типы запросов. Материал сопровождается задачами для самостоятельного решения.
Простые и сложные запросы, содержащие подзапросы
Вычисление в запросах с помощью аф эгатных функций и условных выражений
Оп рации с наборами записей, сое динение таблиц и р- курсивные запросы
Пре дставления, хранимые процедуры и функции
194354. уя, Есенина, 56
E-mail man6bhv.ru Internet: www.bhv ru
Тел./факс. (812)591-6243