- •4.5. Упражнения 67
- •Глава 6. Устройство Informix Dynamic Server 165
- •Глава 7. Эксплуатация информационных систем 177
- •Глава 1 Обзор основных архитектур баз данных
- •1.1. Архитектура на основе разделяемых файлов
- •1.2. Архитектура “Хост-терминал”
- •1.3. Архитектура “Клиент-Сервер”
- •1.4. Архитектура с использованием сервера приложений (трехзвенная архитектура)
- •1.5. Упражнения
- •Глава 2 Модели данных
- •2.1. Уровни восприятия данных
- •2.2. Иерархическая модель данных
- •2.3. Сетевая модель данных
- •2.4. Реляционная модель данных
- •2.5. Объектно-реляционная модель данных
- •Глава 3 Реализация информационных систем на основе продуктов Informix Software
- •3.1. Обзор продуктов Informix
- •3.2. Варианты построения систем
- •Internet/Intranet-конфигурация
- •3.3. Выбор оптимальной конфигурации
- •Глава 4 Математические основы реляционных субд
- •4.1. Основные понятия
- •4.2. Ключи
- •4.3. Основные операции над таблицами и их интерпретация
- •4.4. Нормализация
- •4.5. Упражнения
- •Глава 5 Язык sql
- •5.1. Типы данных, доступные в sql
- •5.3. Основные sql-операторы для доступа и модификации данных
- •5.4. Управление транзакциями
- •5.5. Продвинутые варианты оператора поиска
- •5.5.1. Поиск по нескольким таблицам
- •5.5.2. Устранение повторения данных в операторе select
- •5.5.3. Вычисления внутри оператора select
- •5.5.4. Логические выражения в условии sql-операторов
- •5.5.5. Слияние двух выборок
- •5.5.6. Сортировка выборки
- •5.5.7. Вставка в таблицу нескольких строк одновременно
- •5.6. Использование sql в языках программирования
- •5.7. Программирование сервера базы данных
- •5.7.1. Динамический sql
- •5.7.3. Хранимые процедуры
- •5.7.4. Триггеры
- •5.8. Ограничители (задание целостности на уровне схемы)
- •5.9. Разграничение в sql прав пользователей
- •5.9.1. Права доступа
- •5.9.2. Права на уровне базы данных
- •5.9.3. Права на таблицы
- •5.9.4. Права на хранимые процедуры
- •5.9.5. Кто и как следит за соблюдением прав
- •5.9.6. Механизм ролей
- •5.9.7. Псевдотаблицы (view)
- •5.9.7. Синонимы
- •5.10. Управление одновременным доступом к данным
- •5.10.1. Что бывает, когда несколько человек одновременно пытаются обновить одни и теже данные
- •5.10.2. Открытие базы данных только для себя
- •5.10.3. Блокирование таблицы
- •5.10.4. Механизм блокирования записей и уровни изоляции
- •5.10.5. Управление ожиданием снятия блокировок
- •5.10.6. Тупиковые ситуации
- •5.11. Повышение скорости обработки запросов.
- •5.11.1. Индексы
- •5.11.2. Буферизация журнала транзакций
- •5.11.3. Блокировка на уровне записей и страниц
- •5.11.4. Эффективное построение запросов
- •5.11.5. Сортировка и поиск по коротким полям. Классификаторы
- •5.12. Объектное расширение sql в Informix ds/Universal Data Option
- •5.12.1. Зачем нужна поддержка объектов в серверах бд?
- •5.12.3. Внедрение объектно-ориентированной технологии
- •5.12.4. Реализация объектного подхода в Informix
- •Informix ds/Universal Data Option - объектно-реляционная субд
- •5.12.5. Итак…
- •Глава 6. Устройство Informix Dynamic Server
- •6.1. Внутренняя архитектура dsa
- •6.2. Механизм хранения данных
- •6.3. Инсталляция продукта
- •6.4. Запуск и останов сервера
- •6.5. Работа с русским языком
- •Глава 7. Эксплуатация информационных систем
- •Администрирование серверов баз данных
- •7.2. Обеспечение сохранности данных.
- •7.2.1. Технологии постоянного дублирования
- •7.2.2. Архивация
- •7.2.3. Так как же обеспечить сохранность данных?
- •7.3. Архивирование и восстановление данных
- •7.3.1. Что нужно архивировать
- •7.3.2. Утилиты архивации и восстановления
- •7.3.3. Создание архивов утилитой ontape
- •7.3.4. Восстановление из архивов утилитой ontape
- •7.3.5. Как узнать “когда”?
- •7.3.6. Практические советы
- •7.4. Средства контроля за доступом
- •7.4.1 Как работает аудитинг?
- •7.4.2. Конфигурирование списков протоколируемых событий
- •7.4.3. Задание файлов, запуск и остановка механизма аудитинга
- •Анализ протокола
- •7.4.5. Практические советы или Что делать, если вы хотите…
- •7.5. Реагирование на чрезвычайные ситуации
- •7.6. Мониторинг текущего состояния сервера базы данных
- •7.6.1. Кто работает с сервером базы данных
- •7.6.2. Сколько памяти использует сервер бд
- •7.6.3. Сколько свободного места имеется у сервера бд
- •7.7. Достижение требуемой производительности
- •7.7.1. Как узнать, что ждет некоторый запрос
- •7.7.2. Как выяснять причины падения производительности
- •2. Общие принципы предлагаемой технологии
- •3. Как портировать приложение
5.5.7. Вставка в таблицу нескольких строк одновременно
В случае, когда надо добавить в какую-то таблицу сразу несколько записей, то существует вариант оператора INSERT, который добавляет в таблицу сразу целую выборку. Эта выборка строится оператором SELECT, который вложен в оператор INSERT. Синтаксис такого оператора INSERT выглядит так:
INSERT INTO <имя таблицы> (имя поля1, имя поля2, ...) <оператор SELECT>
Например, мы хотим построить таблицы, в которой перечисленв все фирмы, ничего не поставляющие. Пусть такая таблица называется strange_companies и имеет структуру, идентичную таблице companies. Тогда соответствующий оператор INSERT будет выглядеть так:
INSERT INTO strange_companies(name, address) SELECT name, address FROM companies WHERE company_id NOT IN (SELECT UNIQUE company FROM items)
Если же Вы часто делаете одну и ту же выборку, и при этом Вы уверены, что в течении работы Вашей программы она останется неизменной (другие пользователи не изменят ее), то имеет смысл создать временную таблицу. Временная таблица отличается от обычной тем, что, во-первых, несколько пользователей могут создать разные временные таблицы с одинаковым именем, а, во-вторых, временная таблица будет уничтожена не только в результате оператора DROP TABLE, но и автоматически при завершении программы или закрытии текущей базы данных.
Для создания временной таблицы одноврееменно с заполнением ее значениями используется оператор SELECT, в конец которого помещен раздел INTO TEMP с именем временной таблицы. Например, если для хранения информации о фирмах, ничего не поставляющих, мы решили завести не обычную, а временную таблицу strange_companies, то для этого надо выполнить оператор
SELECT name, address FROM companies WHERE company_id NOT IN (SELECT UNIQUE company FROM items) INTO TEMP strange_companies
Оператор SELECT ... INTO TEMP автоматически создает таблицу, причем эта таблица имеет столько полей, сколько выражений указано в разделе SELECT. Если в разделе SELECT выражение есть просто значение поля (указно имя поля), то формируемая временная таблица будет иметь поле такого же типа с таким же именем. Если в разделе SELECT стоит составное выражение, то имя поля формируется автоматически по некоторым правилам (здесь они не рассматриваются). В нашем примере, временная таблица strange_companies будет иметь два поля - name и address. Типы этих полей будут идентичны типам полей name и address таблицы companies, то есть CHAR(40) и CHAR(60) соответственно.
Поммо повышения производительности, использование временных таблиц во многих случаях позволяет упростить программу.
Кроме оператора SELECT ... INTO TEMP временные таблицы могут быть созданы с помощью оператора создания таблицы - в этом случае он записывается как CREATE TEMP TABLE. Например:
CREATE TEMP TABLE strange_companies ( name CHAR(40), address CHAR(60) )
5.6. Использование sql в языках программирования
SQL не является языком программирования в привычном для нас смысле. В SQL определены операторы доступа к базе данных, но не описан ни способ показа данных, ни ввода их пользователем, в SQL нет привычных управляющих конструкций типа циклов или оператора перехода.
SQL обычно встраивается в другие языки. Например, существуют продукты Informix ESQL/C, /COBOL, /Ada, /FORTRAN, которые представляют собой препроцессор и библиотеки соответственно для С/С++, COBOL, Ada и FORTRAN. На ESQL/C, например, SQL-оператор, встраиваемый в программу, должен начинаться с символа '$' или со слов 'exec sql’. Пример фрагмента программы на ESQL/C:
$database test; for (i=0; i++; i<10) { $insert into my_table values ($i); $select name into $buf from my_2nd_table where key = i; printf ("key = %d, name = %s\n", i, buf); }
Препроцессор переведет приведенный выше фрагмент в "чистую" С-программу, где вместо SQL-операторов будут стоять вызовы фунций, а затем вызовет внешний С-компилятор. С-компилятор соберет конечную программу, используя библиотеку функций, реализующих SQL-запросы.
Другой вариант построения программы, работающий с SQL-сервером - это непосредственное встраивание SQL в язык программирования. Именно по такой схеме построены Informix 4GL и NewEra SQL-операторы являются одними из возможных операторов этого языка. Если приведенную выше программу переписать на Informix-4GL, то она будет выглядеть так:
DATABASE test FOR I=0 TO 9 INSERT INTO my_table VALUES (i) SELECT name INTO buf FROM my_2nd_table WHERE key = i DISPLAY "KEY = ", i, " NAME = ", buf END IF
Когда конечный пользователь работает с какой-то конкретной прикладной программой, он, безусловно, может и не знать ничего про SQL. Но подобные средства со "скрытым" SQL существуют и для разработчиков, а не только для конечных пользователей. Например, построитель форм и отчетов Informix ViewPoint предоставляет чисто визуальные средства разработки, когда
программист (или конечный пользователь) "рисует" форму или отчет. О связи с SQL-сервером позаботится оболочка, которая будет заполнять форму или отчет данными, автоматически посылая SQL-запросы на сервер.
Курсоры
Другой вопрос, который надо решить для того, чтобы можно было реально программировать на SQL - это как обработать набор записей, возвращаемых оператором SELECT. Ведь человеку привычней обрабатывать данные последовательно, ряд за рядом. Конечно, можно было бы реализовать запись всех выбранных значений в массив или список, но это означает, что надо все записи, полученные в результате работы оператора SELECT переслать на компьютер-клиент, а затем переслать обратно сделанные исправления. Но это не очень хорошо по двум причинам. Первое перегружается сеть, если клиент и сервер находятся на разных компьютерах. Второе - надо решить задачу согласования изменений и блокировок, чтобы два пользователя не смогли одновременно изменять одну и ту же запись.
Для решения этой задачи в SQL имеется механизм курсоров. Курсор представляет собой некоторую выборку из данных (активное множество). Эта выборка по структуре аналогична таблице, то есть состоит из набора записей. Каждая запись состоит из набора именованный полей. Но, в отличие от таблицы, в выборке, связанной с курсором, зафиксирован некоторый порядок. То есть
всегда можно сказать, какой номер от начала выборки какая запись имеет. Например, нам надо выдать на экран имена и фамилии из таблицы persons в алфавитном порядке. Воспользуемся для этого курсором, а уж потом дадим точный синтаксис описания курсора и правила работы с ним (используем Informix 4GL для описания перемнных и оператора вывода):
{ определяем переменные для считывания данных из курсора } DEFINE ln LIKE persons.lname DEFINE fn LIKE persons.fname { определяем курсор } DECLARE my_cursor CURSOR FOR SELECT lname, fname FROM persons ORDER BY lname, fname {последовательно перебираем все значения из курсора и печатаем их} FOREACH my_cursor INTO ln, fn DISPLAY ln, fn END FOREACH
Результат работы данного программного фрагмента мог бы быть примерно следующим:
Антонов Антон Антонов Сергей Бендер Остап Шапокляк Алексей
В приведенном простом примере три оператора работают с курсором - DECLARE, FOREACH и END FOREACH. С помощью оператора DECLARE мы объявили курсор, то есть связали его с некоторой выборкой из базы. Затем, с помощью цикла FOREACH ... END FOREACH 10) мы перебрали все записи из выборки, соответсвующей курсору и сделали с ними то, что хотели - вывели на экран. С таким же успехом можно было бы вывести эту информацию в отчет или провести статистическую обработку. Причем, в данной выборке мы уже имеем упорядоченность - вначале по фамилии, а для одинаковых фамилий - по имени. Упорядоченность была указана в SELECT-запросе, связанном с данным курсором (раздел ORDER BY).
Теперь перечислим все операторы работы с курсором и дадим их точный ситаксис. Прежде любого использования, курсор должен быть описан. Описание курсора производитя оператором DECLARE.
Если при работе с курсором предполагается последовательный доступ к записям из выборки, то есть известно, что каждая запись будет выбрана один раз и в том порядке, который сформирован связанным оператором SELECT, то курсор может быть определен следующим вариантом оператора DECLARE:
DECLARE <имя курсора> CURSOR FOR <SELECT-оператор>
Оператор DECLARE связывает курсор с указанным оператором SELECT. Можно представить себе, что курсор - это некоторая структура данных, содержащая, в частности, позицию текущего ряда в выборке, сформированной оператором SELECT.
Например:
{курсор для перебора названий и адресов фирм} DECLARE cursor1 CURSOR FOR SELECT name, address FROM companies ORDER BY name
{курсор для перебора названий товаров и фирм, их производящих} DECLARE dialog_c CURSOR FOR SELECT items.name, company.name FROM companies,items WHERE companies.company_id = items.company ORDER BY items.name
После того, как курсор описан, с ним можно начинать работу. Простейший пример использования курсора был приведен выше цикл FOREACH <имя курсора> .... END FOREACH. Внутри этого цикла производится последовательный перебор записей из выборки.
Последовательный курсор хорош для случаев, когда не надо организовывать диалог с пользователем - например, для формирования отчетов. Если же на основе курсора планируется организовать диалог с пользователем, то последовательным курсором здесь не обойтись - человеку привычно просматривать содержимое базы данных в любых направлениях (как вперед, так и назад). Для таких задач используется скроллируемый курсор, который описывается так:
DECLARE <имя курсора> SCROLL CURSOR FOR <SELECT-оператор>
Как можно заметить, разница в операторах описания скроллируемого и последовательного курсора заключается в слове SCROLL, указываемом после имени курсора.
Если курсор является скроллируемым, то к нему, как и к последовательному, можно применять цикл FOREACH ... END FOREACH. Но для того, чтобы организовать произвольный доступ к записям из выборки, связанной со скроллируемым курсором, служат другие операторы.
Для того, чтобы открыть курсор, то есть подготовить сервер для обработки доступа к данным через курсор и сформировать соответствующий курсору набор записей (активное множество), служит оператор
OPEN <имя курсора>
Для доступа к набору записей, связанных с курсором, служит оператор перемещения по выборке FETCH. В зависимости от того, какую запись Вы хотите выбрать (сделать текущей), можно использовать следующие варианты этого оператора:
FETCH NEXT <имя курсора> FETCH PREVIOUS <имя курсора> FETCH FIRST <имя курсора> FETCH LAST <имя курсора> FETCH CURRENT <имя курсора> FETCH RELATIVE <смещение> <имя курсора> FETCH ABSOLUTE <номер записи> <имя курсора>
Смысл ключевых слов NEXT, PREVIOUS и т.д. и, соответственно, выполняемых ими действий, понятен из их перевода с английского:
NEXT - перейти на следующую запись в выборке;
PREVIOUS - перейти на предыдущую запись в выборке;
FIRST - перейти на первую запись выборки;
LAST - перейти на последнюю запись выборки;
CURRENT - никуда не перемещаясь, перечитать текущую запись;
RELATIVE <смещение> - сместиться от текущей записи на <смещение> записей, причем если смещение больше нуля, то сместиться "вниз" - к последней записи, а если меньше - то вверх, к первой записи;
ABSOLUTE <номер записи> - перейти на запись, имеющую в данной выборке указанный порядковый номер, причем записи нумеруются с единицы.
После оператора OPEN, кстати, текущей записи нет (текущей записью является несуществующая запись с номером ноль). То есть, сразу после открытия курсора оператор FETCH CURRENT выполнять нельзя.
Слово NEXT в операторе FETCH можно опускать - оно принимается по умолчанию. Вместо ключевого слова PREVIOUS можно использовать синоним PRIOR.
В конкретных языках программирования для задания переменных можно (как и для оператора SELECT) использовать расширение INTO со списком переменных:
... INTO <переменная>, <переменна> ...
Например, если в программе на INFORMIX-4GL нам надо прочитать очередную запись в переменные, то соотвествующий оператор будет выглядеть так:
FETCH NEXT my_cursor INTO my_var1, my_var2
Или для программы на INFORMIX ESQL/C:
$fetch next $my_cursor into $my_var1, $my_var2
После завершения работы с выборкой для данного курсора надо закрыть курсор оператором
CLOSE <имя курсора>
Если же Вы хотите полностью освободить все ресурсы, связанные с объявленным курсором, то для этого используется оператор
FREE <имя курсора>
Рассмотрим на примерах перемещение по выборке с помощью оператора FETCH. Предположим, что таблица persons состоит из следующих записей:
----------------T-------------¬ ¦ lname ¦ fname ¦ +---------------+-------------+ ¦ Антонов ¦ Сергей ¦ ¦ Шапокляк ¦ Алексей ¦ ¦ Антонов ¦ Антон ¦ ¦ Бендер ¦ Остап ¦ L---------------+--------------
Рассмотрим, какие записи будут выбираться с использованием следующей последовательности операторов FETCH. Вначале определим скроллируемый курсор для выборки с упорядочиванием по фамилии и имени и откроем его:
DECLARE my_scroll_cursor SCROLL CURSOR FOR SELECT lname, fname FROM persons ORDER BY lname, fname OPEN my_scroll_cr
После этих операторов сформирован набор записей в следующем порядке (порядок уже определен разделом ORDER BY оператора SELECT):
---------T-----------T----------¬ ¦ No п/п ¦ lname ¦ fname ¦ +--------+-----------+----------+ ¦ 1 ¦ Антонов ¦ Антон ¦ ¦ 2 ¦ Антонов ¦ Сергей ¦ ¦ 3 ¦ Бендер ¦ Остап ¦ ¦ 4 ¦ Шапокляк ¦ Алексей ¦ L--------+-----------+-----------
Теперь рассмотрим последовательность операторов FETCH и запись, ими выбираемая (порядок выполнения операторов FETCH, очевидно, существенен):
---------------------T------------------------------------¬ ¦ Оператор FETCH | Выбранная запись No (lname, fname) ¦ +--------------------+------------------------------------+ ¦ FETCH NEXT ¦ 1 (Антонов Антон ) ¦ ¦ FETCH NEXT ¦ 2 (Антонов Сергей ) ¦ ¦ FETCH CURRENT ¦ 2 (Антонов Сергей ) ¦ ¦ FETCH NEXT ¦ 3 (Бендер Остап ) ¦ ¦ FETCH RELATIVE -2 ¦ 1 (Антонов Антон ) ¦ ¦ FETCH LAST ¦ 4 (Шапокляк Алексей ) ¦ ¦ FETCH ABSOLUTE 3 ¦ 3 (Бендер Остап ) ¦ ¦ FETCH PREVIOUS ¦ 2 (Антонов Сергей ) ¦ ¦ FETCH FIRST ¦ 1 (Антонов Антон ) ¦ ¦ FETCH RELATIVE 2 ¦ 3 (Бендер Остап ) ¦ L--------------------+-------------------------------------
Еще раз отметим разницу между курсором и возможностью сформировать некоторую выборку оператором SELECT во внутренних переменных языка программирования - курсор обрабатывается сервером базы данных, пересылка записей программе-клиенту производится по одной записи, контроль за совместным использованием одних и тех же записей производится сервером. То есть использование курсора увеличивает производительность и повышает надежность.
Область взаимодействия SQLCA
Сервер базы данных для каждого пользователя (точнее, для каждого процесса) заводит так называемую область взаимодействия SQLCA (SQL Communication Area). Эта область фактически является глобальной структурой данных, состощей из нескольких именованных полей (переменных). Каждая переменная этой структуры содержит ту или иную характеристику последнего выполненного оператора или состояние сервера в целом. Поля структуры SQLCA перечислены в таблице:
-----------T------------T----------------------------------------¬ ¦ название ¦ тип ¦ описание ¦ +----------+------------+----------------------------------------+ ¦ SQLCODE ¦ целое ¦ Содержит признак завершения оператора. ¦ ¦ ¦ ¦ Может принимать следующие значения: ¦ ¦ ¦ ¦ 0 - признак успешного завершения; ¦ ¦ ¦ ¦ 100 - признак того, что запрос завер- ¦ ¦ ¦ ¦ шен нормально, но не было найдено ¦ ¦ ¦ ¦ ни одной записи; ¦ ¦ ¦ ¦ отрицательное значение - признак неу- ¦ ¦ ¦ ¦ дачного завершения; содержит код ¦ ¦ ¦ ¦ ошибки. ¦ +----------+------------+----------------------------------------+ ¦ SQLERRM ¦ строка из ¦ Содержит текстовую строку с описанием ¦ ¦ ¦ 71 символа ¦ ошибки в случае, если поле SQLCODE ¦ ¦ ¦ ¦ меньше нуля. ¦ +----------+------------+----------------------------------------+ ¦ SQLERRD ¦ массив из ¦ Описывает результат выполнения послед- ¦ ¦ ¦ 6 целых ¦ него оператора SQL: ¦ ¦ ¦ ¦ 1-й элемент - внутренняя информация; ¦ ¦ ¦ ¦ 2-й элемент - содержит сгенерированное ¦ ¦ ¦ ¦ сервером значение поля типа SERIAL ¦ ¦ ¦ ¦ для оператора INSERT, либо допол- ¦ ¦ ¦ ¦ нительный код ошибки; ¦ ¦ ¦ ¦ 3-й элемент - равен количеству обрабо- ¦ ¦ ¦ ¦ танных записей; ¦ ¦ ¦ ¦ 4-й элемент - примерная "стоимость" ¦ ¦ ¦ ¦ выполнения данного оператора; ¦ ¦ ¦ ¦ 5-й элемент - смещение ошибки в текс- ¦ ¦ ¦ ¦ товой записи оператор SQL ¦ ¦ ¦ ¦ 6-й элемент - внутренняя информация; ¦ L----------+------------+-----------------------------------------
Таблица: Поля области взаимодействия (SQLCA) 11)
Предположим, мы хотим снизить на 20% цену на все товары, описанные в таблице items. А заодно, сообщить пользователю, что цены снижены на столько-то товаров. С ипользованием Informix ESQL/C и области взаимодействия SQLCA фрагмент программы будет выглядеть так:
$update items set price = 0.8*price; printf ("Цены снижены на %d наименований.\n", sqlca.sqlerrd[2]);
В данном примере (на ESQL/C) количество обработанных записей содержится в sqlca.sqlerrd[2], так как в языке C элементы массива нумеруются с 0 и 3-й элемент массива sqlca.sqlerrd как раз и будет иметь индекс 2.
Теперь рассмотрим пример вставки связанных значений в две таблицы. Например, мы подписали договор с новым поставщиком на поставку некоторого товара. Нам надо вставить информацию о поставщике в таблицу companies, а о товаре в таблицу items. Причем, в таблице items есть поле company, которое указывает на запись в таблице companies. Эта запись определяет фирму-поставщика данного товара (см. описание структуры базы данных в предыдущем номере). Можно, конечно, вставить информацию о фирме, по ее имени найти ее ключ, и это значение использовать для ссылки. Но это плохо - выполняется лишняя операция поиска и нигде не сказано, что имя компании уникально. С помощью области взаимодействия SQLCA данная задача решается достаточно просто (пример на Informix 4GL или NewEra):
DEFINE new_serial INTEGER ............ BEGIN WORK INSERT INTO companies(name) VALUES ("Наш новый партнер") LET new_serial = SQLCA.SQLERRD[2] -- ключ (company_id) для новой фирмы INSERT INTO items (name, company) VALUES ("Новый товар", new_serial) COMMIT WORK
В данном примере, кстати, объединять два оператора в одну транзакцию (BEGIN WORK ... COMMIT WORK) очень желательно. Если этого не сделать и произойдет какой-то сбой между двумя операторами INSERT, то в базе окажется информация о компании, но не будет информации о товаре. В случае использования транзакции этого можно не опасаться.
Другое очень важное использование области взаимодействия SQL это проверка на то, что в результате оператора выборки (FETCH или SELECT) была найдена хотя бы одна запись. Для этого служит поле SQLCODE области взаимодействия. Данное поле также устанавливается оператором открытия курсора OPEN. Например, если мы хотим после открытия курсора выдать либо первую запись, либо сообщить о том, что записей не найдено, то соответствующий фрагмент программы будет выглядеть примерно так (пример на Informix ESQL/C):
#define NOTFOUND 100 ........ $declare x scroll cursor for select name, address from companies; $open x; /* теперь проверим на наличие хотя бы одной записи в выборке */ if (sqlca.sqlerrd == NOTFOUND) { /* нет ни одной записи - сообщить об этом */ printf("Записей не найдено.\n"); } else { /* показать первую запись и перейти к диалогу */ $fetch x into $name, $address; printf("Фирма %s расположена по адресу %s.\n", name, address); ....... /* диалог с пользователем */ } $close x; /* закрыте курсора по завершению работы */
Примером проверки ситуации, когда пользователь, выбрав последнюю запись, захотел выбрать еще и следующую (несуществующую), может служить следующий фрагмент программы на языке Informix 4GL:
FETCH x INTO name, address { не вышли ли мы за границы выборки? } IF (SQLCA.SQLERRD = NOTFOUND) THEN { больше записей нет - сообщить об этом } ERROR "Записей больше нет." END IF
И, естественно, область взаимодействия SQL активно используется для контроля за ошибками и состоянием базы данных. Для этого надо проверять сотояние сервера SQL после выполнения какого-либо оператора. Надо отметить, что можно управлять реакцией прикладной программы на возникновение ошибок. Например, программа может автоматически завершаться при возникновении ошибки, а может и продолжать работу, сама обрабатывая ошибочную ситуацию (см. следующий параграф). Предположим, что программа работает в режиме, когда обработкой ошибок занимется сама программа и при возникновении ошибки выполнение программы продолжается. Приведем фрагмент программы, проверяющий наличие базы данных, и, если таковая отсутствует, создающий ее (Informix ESQL/C):
$database my_base; if (sqlca.sqlerrd < 0) { /* база данных отсутствует, пытаемся ее создать */ $create database my_base; if (sqlca.sqlerrd < 0) { /* ошибка при создании базы */ printf("Базы данных нет и она не может быть создана!\n"); exit(0); } }
Управление ошибками
В зависимости от того, как ваша программа должна реагировать на возникающие в процессе ее работы ошибки, можно использовать тот или иной вариант оператора WHENEVER. С помощью данного оператора вы можете указать программе на необходимость аварийного прекращения работы в случае возникновения ошибки в SQL-операторе. Другой вариант этого оператора позволяет программе самостоятельно обрабатывать ошибки в SQL-операторах.
Указание программе прекращать выполнение при возникновении ошибке задается так:
WHENEVER SQLERROR STOP
Для перехватывания ошибок и обработки их внутри программы служит следующий вариант оператора WHENEVER:
WHENEVER SQLERROR CONTINUE
или
WHENEVER SQLERROR CALL <имя функции>
В случае CONTINUE при возникновении ошибки управление будет передано на следующий оператор, а в случае CALL - вначале будет вызвана указанная функция. Отслеживание возникновения ошибки в этом случае производится тестированием поля SQLCODE области взаимодействия SQL.
С помощью оператора WHENEVER можно отлавливать и обрабатывать не только возникновение ошибок, но и другие события. Например, оператор WHENEVER можно использовать для отслеживания события "записей не найдено" или при выдаче сервером предупреждений. Соответствующие варианты оператора WHENEVER выглядят так:
WHENEVER NOT FOUND { CONTINUE или STOP или <имя функции> }
WHENEVER SQLWARNING { CONTINUE или STOP или <имя функции> }
Для примера приведем простую программу на Informix-4GL с тремя операторами WHENEVER. Первый оператор задает режим продолжения работы (CONTINUE) при вознкновении ошибок, второй - задает реакцию на ненахождение выборки, а третий - говорит о необходимости завершить программу при возникновении ошибки. Любая ошибка между первым и третьим опреатором WHENEVER будет проигнорирована, а ошибка после третьего оператора WHENEVER приведет к аварийному завершнию программы.
MAIN DEFINE char_num INTEGER DATABASE test WHENEVER SQLERROR CONTINUE -- первый оператор WHENEVER DISPLAY "Пытаемся выполнить первый оператор INSERT" INSERT INTO test_table(color) VALUES ("красный") IF SQLCA.SQLCODE < 0 THEN DISPLAY "Ошибка при выполнении оператора вставки:", SQLCA.SQLERRM END IF WHENEVER NOT FOUND CONTINUE -- второй оператор WHWNEVER WHENEVER SQLERROR STOP -- третий оператор WHENEVER DISPLAY "Пытаемся выполнить второй оператор INSERT" INSERT INTO test_table(color) VALUES ("зеленый") CLOSE DATABASE DISPLAY "Программа выполнена" END MAIN
После первого оператора INSERT мы можем проверить, был ли он реально выполнен путем сравнения SQLCA.SQLCODE с нулем. После второго оператора INSERT данная проверка бессмыслена, так как был выполнен оператор WHENEVER ERROR STOP и любая ошибка приведет к завершению программы.
