Дейт К. Д27 Руководство по реляционной субд db2/ Пер с англ и предисл. М. Р. Когаловского
Вид материала | Руководство |
- Рич Р. К. Политология. Методы исследования: Пер с англ. / Предисл. А. К. Соколова, 6313.17kb.
- Рич Р. К. Политология. Методы исследования: Пер с англ. / Предисл. А. К. Соколова, 6313.29kb.
- Сорокин П. А. С 65 Человек. Цивилизация. Общество / Общ ред., сост и предисл., 11452.51kb.
- The guilford press, 6075kb.
- The guilford press, 6075.4kb.
- Курс лекций "Базы данных и субд" Ульянов В. С. Лекция. Целостная часть реляционной, 213.79kb.
- Дэвид Дайчес, 1633.42kb.
- Mathematics and the search for knowledge morris kline, 498.28kb.
- Лекция №1: Стандарты языка sql, 1420.56kb.
- Курс лекций "Базы данных и субд" Ульянов В. С. Лекция. Манипулирование реляционными, 276.31kb.
ГЛАВА 10
ПРИКЛАДНОЕ ПРОГРАММИРОВАНИЕ I:
ВСТРОЕННЫЙ ЯЗЫК SQL
10.1. ВВЕДЕНИЕ
Как разъяснялось в главе 1, SQL используется в системе DB2 и как интерактивный язык запросов, и как язык программирования в среде базы данных. Однако до настоящего времени мы более или менее игнорировали аспекты SQL, связанные с программированием, и там, где это имело какое-либо значение, молчаливо предполагалось, что язык используется в интерактивном режиме. Теперь мы особо обратим внимание на эти аспекты языка, связанные с программированием. В настоящей главе обсуждаются идеи, лежащие в основе «встроенного SQL», как его обычно называют. В следующей главе рассматривается концепция обработки транзакций. Наконец, в главе 12 предлагается (только) введение в несколько более сложный вопрос, а именно «динамический SQL». Но сначала о первом.
Основной принцип, лежащий в основе встроенного SQL, который можно было бы назвать двухрежимным принципом, заключается в том, что любое предложение SQL, которое может использоваться на терминале, может использоваться также, в прикладной программе. Как уже указывалось в главе 1, имеется, конечно, ряд различий между данным интерактивным предложением SQL и соответствующей его встроенной формой. В частности, в среде программирования предложения SELECT требуют в значительной степени расширенной интерпретации (см. раздел 10.4). Но указанный принцип тем не менее вполне справедлив в отличие, между прочим, от обратного ему утверждения. Иначе говоря, существуют, как мы увидим позднее, многие предложения SQL, которые могут использоваться только в программе, но не в интерактивном режиме.
Можно, очевидно, отметить, что двухрежимный принцип относится к полному языку SQL, а не только к операциям манипулирования данными. Хотя на самом деле в контексте программирования наиболее часто используются, несомненно, операции манипулирования данными, не будет ошибкой использование в программе, например, встроенных предложений CREATE TABLE, если это имеет смысл для рассматриваемой прикладной задачи.
Языками программирования, в которые можно встраивать SQL в системе DB2,— так называемыми «включающими» языками — являются ПЛ/1, Кобол, Фортран и ассемблер IBM/370. В разделе 10.2 рассматривается механика встраивания SQL в эти языки. Далее, в разделах 10.3 и 10.4 представлены, в частности, основные идеи, на которых базируется встраивание предложений манипулирования данными языка SQL. Наконец, в разделе 10.5 приводится исчерпывающий пример, связанный с программированием.
Примечание. Для определенности все приведенные далее примеры программ записываются на языке ПЛ/1. Большинство иллюстрируемых в них идей переносится в другие языки лишь с небольшими изменениями. Различия, возникающие при переходе от одного языка к другому, излагаются подробно в случае, если они являются важными.
10.2. ПРЕДВАРИТЕЛЬНЫЕ ЗАМЕЧАНИЯ
Прежде чем мы сможем перейти к рассмотрению предложений встроенного языка SQL самих по себе, необходимо предварительно обсудить некоторые детали. Большинство из них иллюстрируется фрагментом программы, показанным на рис. 10.1. ( Здесь и далее в переводе мы отступаем от синтаксиса языка ПЛ/1, не предусматривающего использование букв русского алфавита в идентификаторах. Это же замечание относится, впрочем, и к языку SQL.— Примеч. пер.)
Рассматривая этот пример, нетрудно установить следующее:
1. Предложениям встроенного SQL всегда предшествует ЕХЕС SQL, так что их можно легко отличить от предложений включающего языка, а завершаются они следующим образом:
в ПЛ/1 — точка с запятой
в Коболе — END ЕХЕС
в Фортране — отсутствие символа продолжения в колонке 6
в языке Ассемблера — отсутствие символа продолжения в колонке 72.
2. Исполняемые предложения SQL (далее для краткости слово «встроенный» обычно опускается) могут использоваться во всех тех случаях, когда могут использоваться исполняемые предложения включающего языка. Обратим внимание здесь на уточнитель «исполняемые». В отличие от интерактивного SQL встроенный SQL включает некоторые предложения, которые являются чисто декларативными, неисполняемыми. Например, DECLARE TABLE (объявить таблицу) не является исполняемым предложением, так же как и DECLARE CURSOR (объявить курсор),—см. раздел 10.4.
3. Предложения SQL могут содержать обращения к переменным включающего языка. Перед такими обращениями ставится двоеточие с тем, чтобы отличать их от имен полей SQL. Переменные включающего языка могут появляться в предложениях манипулирования данными языка SQL только в следующих местах:
— фраза INTO в предложении SELECT (результирующая величина, которой присваивается значение, выбираемое из базы данных)
DCL ЗАДАННЫЙ_НОМЕР CHAR (5);
DCL РАНГ FIXED BIN (15);
DCL ГОРОД CHAR (15);
DCL АЛЬФА . . . ;
DCL БЕТА . . . ;
ЕХЕС SQL DECLARE S TABLE
(НОМЕР_ПОСТАВЩИКА CHAR (5)
NOT NULL,
ФАМИЛИЯ CHAR (20),
СОСТОЯНИЕ SMALLINT,
ГОРОД CHAR (15));
ЕХЕС SQL INCLUDE SQLCA;
. . . . . . . . . . . . . . . . . . . . . . .
IF АЛЬФА > БЕТА THEN
GETSTC:
ЕХЕС SQL SELECT СОСТОЯНИЕ, ГОРОД
INTO :РАНГ,: ГОРОД
FROM S
WHERE НОМЕР_ПОСТАВЩИКА = : ЗАДАННЫЙ_НОМЕР;
. . . . . . . . . . . . . . . . . . . . . . .
PUT SKIP LIST (РАНГ, ГОРОД);
Рис. 10.1. Фрагмент программы на языке ПЛ/1 с предложениями встроенного SQL
— фраза SELECT (значение, выборку - которого нужно произвести)
— фраза WHERE в предложениях SELECT, UPDATE, DELETE (значение, которое следует сравнивать)
— фраза SET в предложении UPDATE (источник для обновляемого значения)
— фраза VALUES в предложении INSERT (источник для вставляемого значения)
— элемент арифметического выражения во фразах SELECT, WHERE или SET, но не VALUES, где в результате вычисления этого выражения в свою очередь определяется значение, которое подлежит выборке, сравнению или обновлению. Они могут появляться также в некоторых предложениях, относящихся только к встроенному языку (подробности приведены ниже). Они не могут появиться в каких-либо других предложениях SQL.
4. Любые используемые в программе таблицы (базовые таблицы или представления) должны быть объявлены при помощи предложения ЕХЕС SQL DECLARE для того, чтобы сделать программу в большей степени самодокументируемой и дать возможность прекомпилятору выполнять некоторые синтаксические проверки манипулятивных предложений.
5. После того как было выполнено любое предложение SQL, информация обратной связи возвращается программе в область, называемую областью связи SQL (SQLCA — SQL Communication Area). В частности, в поле области SQLCA, называемое SQLCODE, возвращается числовой индикатор состояния. Нулевое значение SQLCODE означает, что данное предложение выполнено успешно. Положительное значение означает, что предложение все же выполнено, но предупреждает, что имела место некоторая исключительная ситуация. Например, значение +100 указывает, что не было найдено никаких данных, удовлетворяющих запросу. Наконец, отрицательное значение указывает, что имела место ошибка, и данное предложение не было успешно выполнено. Поэтому в принципе за каждым предложением SQL в программе должна следовать проверка значения SQLCODE и должно предприниматься соответствующее действие, если это значение оказывается не тем, которое ожидалось. Но этот шаг не показан на рис. 10.1. Как указывается в разделе 10.5, на практике такое явное тестирование значений SQLCODE, возможно, не является необходимым. Область связи SQL включается в программу с помощью предложения
ЕХЕС SQL INCLUDE SQLCA;
6. Как уже упоминалось, предложение SELECT встроенного языка должно содержать фразу INTO, специфицирующую переменные включающего языка, которым должны быть присвоены значения, найденные в базе данных. Переменные во фразе INTO могут быть скалярными переменными (элементами) или структурами. Структура рассматривается просто как краткая запись списка элементов, составляющих эту структуру. Структуры могут использоваться также во фразе VALUES предложения INSERT.
7. Переменные включающего языка должны иметь типы данных, совместимые с типами данных языка SQL тех полей, с которыми они должны сравниваться, значения которых им должны быть присвоены, или которым должны быть присвоены значения этих переменных. Совместимость типов данных определяется следующим образом:
а) литерные данные SQL совместимы с литерными данными включающего языка, независимо от их длины и независимо от того, является ли какая-либо из этих длин переменной;
б) числовые данные SQL совместимы с числовыми данными включающего языка, независимо от основания системы счисления (десятичная или двоичная), способа представления (с фиксированной или плавающей точкой) и точности (число цифр). Система DB2 выполнит все необходимые преобразования. Если при присваивании значения в программе либо в предложении языка SQL имеет место потеря значащих цифр или литер, связанная с тем, что поле, принимающее значение, слишком мало, программе возвращается информация об ошибке.
8. Отметим, что переменные включающего языка и поля базы данных могут иметь одни и те же имена. Переменная включающего языка может быть элементом структуры. Например:
DCL 1 ДАНО,
2 НОМЕР_ПОСТАВЩИКА CHAR(5),
2 . . .;
ЕХEС SQL SELECT . . .
. . . . . . .
WHERE НОМЕР_ПОСТАВЩИКА =
:ДАНО. НОМЕР_ПОСТАВЩИКА;
Заметим, что в предложениях SQL используется уточнение имен в стиле ПЛ/1, а не Кобола (:ДАНО.НОМЕР_ПОСТАВЩИКА, а не НОМЕР_ПОСТАВЩИКА OF ДАНО), даже когда включающим языком фактически является Кобол.
Это все, что касается предварительных замечаний. В остальной части данной главы мы сосредоточим внимание, главным образом, на операциях манипулирования данными SELECT, UPDATE, DELETE и INSERT. Как уже указывалось, к большинству из этих операций можно обратиться довольно простым образом, т. е. лишь с небольшими изменениями в их синтаксисе. Однако предложения SELECT требуют особого рассмотрения. Проблема заключается в том, что исполнение предложения SELECT порождает таблицу—таблицу, которая, в общем случае, содержит множество записей, а такие языки, как Кобол и ПЛ/1, просто не обладают хорошими средствами, позволяющими оперировать одновременно более чем одной записью. По этим причинам необходимо обеспечить своего рода мост между уровнем множеств языка SQL и уровнем записей включающего языка. Такой мост обеспечивают курсоры. Курсор — это новый вид объекта языка SQL, относящийся только к встроенному SQL, поскольку интерактивный SQL, конечно же, в нем не нуждается. Курсор состоит, по существу, из некоторого рода указателя, который может использоваться для просмотра множества записей. Поочередно указывая каждую запись в данном множестве, он обеспечивает, таким образом, возможность обращения к этим записям по одной одновременно. Отложим, однако, детальное обсуждение курсоров до раздела 10.4, а сначала рассмотрим (в разделе 10.3) те предложения, для которых они не требуются.
10.3. ОПЕРАЦИИ, НЕ ТРЕБУЮЩИЕ ИСПОЛЬЗОВАНИЯ КУРСОРОВ
Следующие предложения манипулирования данными не требуют использования курсоров:
— «единичное SELECT»
— UPDATE, за исключением формы CURRENT (см. раздел 10.4)
— DELETE, опять-таки за исключением формы CURRENT (см. раздел 10.4)
— INSERT.
Приведем поочередно примеры каждого из этих предложений.
10.3.1. ЕДИНИЧНОЕ SELECT
Выдать состояние и город для поставщика, номер которого задается переменной включающего языка ЗАДАННЫИ_НОМЕР.
ЕХЕС SQL SELECT СОСТОЯНИЕ, ГОРОД
INTO :РАНГ, :ГОРОД
FROM S
WHERE НОМЕР_ПОСТАВЩИКА = :ЗАДАННЫЙ_НОМЕР;
Термин «единичное SELECT» используется здесь для обозначения предложения SELECT, которое продуцирует таблицу, содержащую не более одной строки. В данном примере, если существует в точности одна запись в таблице S, удовлетворяющая условию WHERE, то значения СОСТОЯНИЕ и ГОРОД из этой записи будут присвоены, как требовалось в запросе, переменным включающего языка РАНГ и ГОРОД, a SQLCODE будет установлено в нуль. Если же никакая запись в S не удовлетворяет условию WHERE, поле SQLCODE примет значение +100. Если, наконец, существует более одной такой записи, возникает ошибка, и значение SQLCODE будет отрицательным. В последних двух случаях значения переменных включающего языка РАНГ и ГОРОД останутся неизменными.
В связи с приведенным примером возникает другой вопрос. Что произойдет, если предложение SELECT в действительности выберет в точности одну запись, но значение поля СОСТОЯНИЕ (или поля ГОРОД) в ней окажется неопределенным? Как показано выше, в таком случае будет иметь место ошибка, и полю SQLCODE будет присвоено некоторое отрицательное значение. Если существует шанс, что выбираемое значение поля может быть неопределенным, пользователь должен предусмотреть индикаторную переменную для этого поля во фразе INTO, а также обычную целевую переменную, как показано в следующем примере:
ЕХЕС SQL SELECT СОСТОЯНИЕ, ГОРОД
INTO :РАНГ: ИНД_РАНГА, :ГОРОД: ИНД_ГОРОДА
FROM S
WHERE НОМЕР_ПОСТАВЩИКА = :ЗАДАННЫЙ_НОМЕР;
IF ИНД_РАНГА < 0 THEN / » значение поля состояние было неопределенным */. . .;
IF ИНД_ГОРОДА < 0 THEN / * значение поля ГОРОД было неопределенным */. . .;
Если поле, выборка которого осуществляется, имеет неопределенное значение, и была специфицирована индикаторная переменная, то этой индикаторной переменной будет присвоено соответствующее отрицательное значение, а обычная целевая переменная останется неизменной. Индикаторная переменная специфицируется, как показано в приведенном примере, т. е. она следует за соответствующей обычной целевой переменной и отделяется от этой целевой переменной двоеточием. Индикаторные переменные следует объявлять как 15-битовые двоичные целые со знаком.
Примечание. Индикаторные переменные не могут использоваться во фразе WHERE. Например, следующий фрагмент программы некорректен:
ИНД_РАНГА = 1;
ЕХЕС SQL SELECT ГОРОД
INTO :ГОРОД
FROM S
WHERE СОСТОЯНИЕ = :РАНГ : ИНД_РАНГА;
Правильный способ выборки городов при неопределенном значении поля СОСТОЯНИЕ иллюстрируется ниже:
ЕХЕС SQL SELECT ГОРОД
INTO :ГОРОД
FROM S
WHERE СОСТОЯНИЕ IS NULL;
10.3.2. ПРЕДЛОЖЕНИЕ UPDATE
Увеличить состояние всех поставщиков из Лондона на величину, заданную переменной включающего языка ПРИРОСТ:
ЕХЕС SQL UPDATE S
SET СОСТОЯНИЕ = СОСТОЯНИЕ + :ПРИРОСТ
WHERE ГОРОД = 'Лондон';
Если записей, удовлетворяющих условию WHERE, нет, для поля SQLCODE будет установлено значение +100. Справа от знака присваивания во фразе SET может быть использована индикаторная переменная. Например, предложение:
ИНД_РАНГА = 1;
ЕХЕС SQL UPDATE S
SET СОСТОЯНИЕ = :РАНГ :ИНД_РАНГА
WHERE ГОРОД = 'Лондон';
установит состояние всех лондонских поставщиков в неопределенное значение. То же самое можно, конечно, сделать с помощью предложения:
ЕХЕС SQL UPDATE S
SET СОСТОЯНИЕ = NULL
WHERE ГОРОД = 'Лондон';
10.3.3. ПРЕДЛОЖЕНИЕ DELETE
Удалить все поставки тех поставщиков, которые находятся в городе, заданном переменной включающего языка ГОРОД.
ЕХЕС SQL DELETE
FROM SP
WHERE :ГОРОД=
(SELECT ГОРОД
FROM S
WHERE S.HOMEP_ПОСТАВЩИКА =
SP.НОМЕР_ПОСТАВЩИКА);
И снова, если никакие записи не удовлетворяют условию WHERE, для поля SQLCODE будет установлено значение +100.
10.3.4. ПРЕДЛОЖЕНИЕ INSERT
Вставить в таблицу Р новую деталь. При этом номер детали, название и вес заданы соответственно переменными включающего языка НОМ_ДЕТАЛИ, НАЗВ_ДЕТАЛИ и ВЕС_ДЕТАЛИ, а цвет и город неизвестны.
ЕХЕС SQL INSERT
INTO Р (НОМЕР_ДЕТАЛИ, НАЗВАНИЕ, ВЕС)
VALUES (:HOM_ДЕТАЛИ, :НАЗВ_ДЕТАЛИ,
:ВЕС_ДЕТАЛИ);
Опять здесь можно использовать индикаторные переменные. Например, если ЦВЕТ_ДЕТАЛИ и ГОР_ДЕТАЛИ — две следующие переменные включающего языка, ИНД_ЦВЕТА и ИНД_ГОРОДА — соответствующие индикаторные переменные, то последовательность
ИНД_ЦВЕТА = -1;
ИНД_ГОРОДА = -1;
ЕХЕС SQL INSERT
INTO Р (НОМЕР_ДЕТАЛИ, НАЗВАНИЕ, ЦВЕТ, ВЕС,
ГОРОД)
VALUES (:HOM_ДЕТАЛИ, :НАЗВ_ДЕТАЛИ,
:ЦВЕТ_ДЕТАЛИ: ИНД_ЦВЕТА,
:ВЕС_ДЕТАЛИ,
:ГОР_ДЕТАЛИ: ИНД_ГОРОДА);
дает тот же результат, что и приведенное выше предложение INSERT.
Для простоты в дальнейшем (в этой и двух следующих главах) будем в большинстве случаев игнорировать индикаторные переменные и возможность неопределенных значений.
10.4. ОПЕРАЦИИ, ТРЕБУЮЩИЕ ИСПОЛЬЗОВАНИЯ КУРСОРОВ
Вернемся теперь к случаю предложения SELECT, которое продуцирует целое множество записей, а не только одну запись. Как уже было объяснено в разделе 10.2, здесь необходим механизм, обеспечивающий последовательный доступ к записям в этом множестве. Такой механизм обеспечивают курсоры. В общих чертах процесс доступа иллюстрируется в примере, приведенном на рис. 10.2, в котором предусматривается выборка деталей (полей НОМЕР_ДЕТАЛИ, НАЗВАНИЕ и СОСТОЯНИЕ) для всех поставщиков, находящихся в городе, заданном переменной включающего языка Y.
ЕХЕС SQL DECLARE X CURSOR FOR / * определить курсор X * /
SELECT НОМЕР_ПОСТАВЩИКА, НАЗВАНИЕ,
СОСТОЯНИЕ
FROM S
WHERE ГОРОД = >У;
ЕХЕС SQL OPEN X; / * исполнить запрос * /
DO WHILE (пока еще есть записи);
ЕХЕС SQL FETCH X INTO :НОМЕР_
ПОСТАВЩИКА, :НАЗВАНИЕ, :СОСТОЯНИЕ;
/ * выбрать следующего поставщика * /
END;
ЕХЕС SQL CLOSE X; / * дезактивировать курсор Х * /
Рис. 10.2. Выборка множества записей
Пояснение. Предложение DECLARE X CURSOR... определяет курсор, названный X, и ассоциированный с ним запрос, специфицированный с помощью предложения SELECT, которое образует часть этого предложения DECLARE. Указанное предложение SELECT не исполняется в данный момент, так как DECLARE CURSOR это чисто декларативное предложение. Оно исполняется, когда открывается курсор в процедурной части программы. Предложение FETCH ... INTO ... (выбрать ... в ...) используется для выборки записей результирующего множества и присваивает найденные значения переменным включающего языка в соответствии со спецификациями фразы INTO в этом предложении. Для простоты в приведенном примере переменным включающего языка даны те же самые имена, что и соответствующим полям базы данных. Заметим, что предложение SELECT в объявлении курсора не содержит фразы INTO. Поскольку в результате будет получено множество записей, предложение FETCH будет обычно входить в некоторый цикл (в языке ПЛ/1—DO... END). Этот цикл будет повторяться до тех пор, пока в этом результирующем множестве еще существуют непросмотренные записи. При выходе из цикла курсор Х закрывается (дезактивируется) с помощью соответствующего предложения CLOSE (закрыть).
Давайте теперь рассмотрим курсоры и операции над курсорами более подробно. Прежде всего нужно отметить, что курсор объявляется с помощью предложения DECLARE CURSOR (объявить курсор), которое имеет следующий общий формат:
ЕХЕС SQL DECLARE имя — курсора CURSOR
FOR подзапрос [UNION подзапрос] . . .
[FOR UPDATE OF имя — столбца [ ,имя — столбца]. . .
[фраза — упорядочить — по];
Для примера обратимся к рис. 10.2. Как указывалось ранее, предложение DECLARE CURSOR является декларативным, а не исполняемым. Оно объявляет курсор с заданным именем, с которым постоянно связан специфицированный подзапрос или множество подзапросов, соединенных операций UNION. Заметим, что эти подзапросы могут включать ссылки на переменные включающего языка. Если курсор будет использоваться в предложениях UPDATE CURRENT (обновить текущую) (См. ниже в этом разделе.— Примеч пер), то его объявление должно включать фразу FOR UPDATE (для обновления), специфицирующую все поля, которые будут обновляться через этот курсор. Если курсор не используется для обновления с помощью указанного предложения, то его объявление факультативно может включать фразу ORDER BY (упорядочить по), как в обычном предложении SELECT. Эта фраза будет управлять порядком, в котором строки результата выбираются с помощью предложения FETCH. Заметим поэтому, что невозможно осуществлять выборку множества записей через курсор в некотором специфицированном порядке и в то же время обновлять (операция UPDATE) некоторые из этих записей через тот же самый курсор.
Программа может включать произвольное число предложений DECLARE CURSOR, которые, конечно, объявляют различные курсоры.
Для выполнения операций над курсорами специально обеспечиваются три исполняемых предложения: OPEN, FETCH и CLOSE.
1. Предложение
ЕХЕС SQL OPEN имя — курсора;
открывает или активизирует специфицированный курсор, который в этот момент не должен быть открыт. Фактически исполняется предложение SELECT, связанное с этим курсором. При этом используются текущие значения для всех переменных включающего языка, на которые имеются ссылки в этом предложении SELECT. Таким образом, идентифицируется некоторое множество записей, которое становится активным множеством для данного курсора. Курсор идентифицирует также позицию в этом множестве, а именно позицию непосредственно перед первой записью данного множества. Активные множества всегда рассматриваются как упорядоченные, так что концепция позиции имеет смысл. При этом имеется в виду упорядочение, определяемое фразой ORDER BY, либо системой в отсутствие этой фразы.
2.Предложение
ЕХЕС SQL FETCH имя — курсора INTO мишень [, мишень] . . .;
где каждая «мишень» имеет формат
переменная — включающего — языка [:переменная — включающего — языка]
как в единичном SELECT, и где идентифицированный курсор должен быть открыт, продвигает этот курсор к следующей записи в активном множестве, а затем присваивает значения полей из этой записи переменным включающего языка, в соответствии с фразой INTO. Как уже указывалось, предложение FETCH обычно исполняется в программном цикле (см. рис. 102). Если при исполнении FETCH не существует следующей записи, то выборка данных не производится и SQLCODE принимает значение +100.
Отметим, между прочим, что «выбрать следующую» представляет собой единственную операцию перемещения курсора. Невозможно переместить курсор, например, «вперед на три позиции» или «назад на две позиции» и т. п.
3. Предложение
ЕХЕС SQL CLOSE имя — курсора;
закрывает или дезактивирует специфицированный курсор, который в этот момент должен быть открыт. Теперь этот курсор не имеет соответствующего активного множества. Его можно, однако, теперь снова открыть. В этом случае с ним будет связано другое активное множество, вероятно, не в точности то же самое, что и ранее, особенно если значения переменных включающего языка, упоминаемые в предложении SELECT, тем временем изменились. Заметим, что изменение значений этих переменных в то время, когда курсор открыт, не оказывает влияния на активное множество.
Два следующих предложения могут включать ссылки на курсоры. Имеются формы CURRENT для предложений UPDATE и DELETE. Если, например, курсор Х в настоящее время позиционирован на конкретную запись в базе данных, то можно обновить (UPDATE) или удалить (DELETE) «текущую X», т. е. запись, на которую позиционирован X. Синтаксис этих предложений таков:
ЕХЕС SQL UPDATE имя — таблицы
SET имя — поля = выражение
[, имя — поля == выражение] . . .
WHERE CURRENT OF имя — курсора;
ЕХЕС SQL DELETE
FROM имя — таблицы
WHERE CURRENT OF имя — курсора;
Пример:
ЕХЕС SQL UPDATE S
SET СОСТОЯНИЕ = СОСТОЯНИЕ + :ПРИРОСТ
WHERE CURRENT OF X;
Использование предложений UPDATE CURRENT и DELETE CURRENT не допускается, если предложение SELECT в объявлении курсора включает UNION или ORDER BY, или если это предложение SELECT определяет необновляемое представление и является частью предложения CREATE VIEW (см. раздел 8.4). Как пояснялось ранее, в случае UPDATE CURRENT предложение DECLARE должно включать фразу FOR UPDATE, идентифицирующую все поля, которые входят как мишени во фразу SET предложения UPDATE CURRENT для этого курсора.
10.5. ИСЧЕРПЫВАЮЩИЙ ПРИМЕР
Эту главу мы завершим надуманным, но исчерпывающим примером (рис. 10.3), позволяющим проиллюстрировать ряд дополнительных моментов. Рассматриваемая программа получает четыре значения входных данных: номер детали (ЗАДАННАЯ_ДЕТАЛЬ), название города (ЗАДАННЫЙ_ГОРОД), прирост состояния (ЗАДАННЫЙ_ПРИРОСТ) и уровень состояния (ЗАДАННЫЙ_УРОВЕНЬ). Программа просматривает всех поставщиков детали, идентифицируемой значением ЗАДАННАЯ_ДЕТАЛЬ. Для каждого такого поставщика состояние увеличивается на заданный прирост, если он находится в городе ЗАДАННЫЙ_ГОРОД. В противном случае, если состояние поставщика меньше значения ЗАДАННЫЙ_УРОВЕНЬ, поставщик удаляется вместе со всеми его поставками. Во всех случаях информация о поставщике выводится на печать с указанием, каким образом программа поступила с этим конкретным поставщиком.
SQLEX: PROC OPTIONS (MAIN);
DCL ЗАДАННАЯ_ДЕТАЛЬ CHAR 16);
DCL ЗАДАННЫЙ_ГОРОД CHAR (16);
DCL ЗАДАННЫЙ_ПРИРОСТ FIXED BINARY (15);
DCL ЗАДАННЫЙ_УРОВЕНЬ FIXED BINARY (16);
DCL HOMEP_ ПОСТАВЩИКА CHAR (5);
DCL ФАМИЛИЯ CHAR (20);
DCL СОСТОЯНИЕ FIXED BINABY(15);
DCL ГОРОД CHAR 115);
DCL ДИСПОЗИЦИЯ CHAR (7);
DCL ДРУГИЕ_ПОСТАВЩИКИ ВIТ(1);
EXEC SQL INCLUDE SQLCA;
EXEC SQL DECLARE S TABLE
(НОМЕР_ПОСТАВЩИКА CHAR (5) NOT NULL,
ФАМИЛИЯ CHAR (20),
СОСТОЯНИЕ SMALLINT,
ГОРОД CHAR (20));
EXEC SQL DECLARE SP TABLE
(НОМЕР_ПОСТАВЩИКА CHAR (5) NOT NULL.
НОМЕР_ДЕТАЛИ CHAR (6) NOT NULL,
КОЛИЧЕСТВО INTEGER);
EXEC SQL DECLARE Z CURSOR FOR
SELECT HOMEP_ПОСТАВЩИКА, ФАМИЛИЯ, СОСТОЯНИЕ,
ГОРОД
FROM S
WHERE EXISTS
(SELECT
FROM SP
WHERE SP. НОМЕР_ПОСТАВЩИКА=
S.HOMEP_ПОСТАВЩИКА
AND SP. НОМЕР_ДЕТАЛИ =
:ЗАДАННЫЙ_НОМЕР)
FOR update OF СОСТОЯНИЕ ;
EXEC SQL WHENEVER NOT FOUND CONTINUE;
EXEC SQL WHENEVER SQLERROR CONTINUE;
ЕХEС SQL whenever SQLWARNING CONTINUE;
ON CONDITION (DBEXCEPTION)
BEGIN:
PUT SKIP LIST (SQLCA);
EXEC SQL ROLLBACK;
GO TO QUIT;
END;
GET LIST (ЗАДАННАЯ_ДЕТАЛЬ, ЗАДАННЫЙ_ГОРОД, ЗАДАННЫЙ_ПРИРОСТ,
ЗАДАННЫЙ_УРОВЕНЬ);
EXEC SQL OPEN Z;
IF SQLCODE = 0
THEN SIGNAL CONDITION (DBEXCEPTION)
ДРУГИЕ_ПОСТАВЩИКИ =‘1’ B;
DO WHILE (ДРУГИЕ_ПОСТАВЩИКИ);
EXEC SQL FETCH Z INTO :НОМЕР_ПОСТАВЩИКА,
:ФАМИЛИЯ,
:СОСТОЯНИЕ,
:ГОРОД;
SELECT; /*SELECT языка ПЛ/1, а не SQL*/
WHEN (SQLCODE = 100)
ДРУГИЕ_ПОСТАВЩИКИ = '0'B
WHEN (SQLCODE =100 & SOLCODE = 0)
SIGNAL CONDITION (DBEXCEPTION);
WHEN- (SQLCODE =0)
DO;
ДИСПОЗИЦИЯ = ‘bbbbbbb ';
IF ГОРОД = ЗАДАННЫЙ_ ГОРОД
THEN
DO;
EXEC SQL UPDATE S
SET СОСТОЯНИЕ = СОСТОЯНИЕ
+ : ЗАДАННЫЙ_ПРИРОСТ
WHERE CURRENT OF Z ;
IF SQLCODE =0
THEN SIGNAL CONDITION (DBEXCEPTION);
ДИСПОЗИЦИЯ = 'ОБНОВЛЕН ' ;
END;
ELSE
IF СОСТОЯНИЕ < ЗАДАННЫЙ_УРОВЕНЬ
THEN
DO;
EXEC SQL DELETE
FROM SP
WHERE НОМЕР_ПОСТАВЩИКА =
: НОМЕР_ПОСТАВЩИКА;
IF SQLCODE = 0 & SQLCODE = 100
THEN SIGNAL CONDITION (DBEXCEPTION);
EXEC SQL DELETE
FROM S
WHERE CURRENT OF Z;
IF SQLCODE = 0
THEN SIGNAL CONDITION (DBEXCEPTION);
ДИСПОЗИЦИЯ= ‘УДАЛЕН';
END;
PUT SKIP LIST (НОМЕР_ПОСТАВЩИКА, ФАМИЛИЯ, СОСТОЯНИЕ, ГОРОД,
ДИСПОЗИЦИЯ);
END; /*WHEN (SQLCODE=0) ...* /
END; /*SELECT языка ПЛ/1 */
END; /*DO WHILE* /
EXEC SQL CLOSE Z;
EXEC SOL COMMIT;
QUIT: RETURN;
END; /*SQLEX */
Рис. 10.3. Исчерпывающий пример
Возникают следующие проблемы.
1. Во-первых, всюду игнорируется возможность того, что некоторое поле, выборка которого производится, может иметь неопределенное значение. Такое упрощение введено исключительно с целью сокращения размеров примера.
2. Далее обратите внимание на два предложения DECLARE для таблиц S и SP. Эти объявления, очевидно, представляют собой не что иное, как небольшие текстуальные вариации соответствующих предложений CREATE TABLE языка SQL. Для построения таких деклараций в интересах пользователей предусматривается специальная служебная программа — генератор деклараций (DCLGEN).
Примечание. Название DCLGEN обычно произносится «деклеген» с мягким «г».
По существу, DCLGEN использует информацию из каталога системы DB2 для построения одной или обеих следующих синтаксических конструкций:
— предложения DECLARE для данной таблицы
— соответствующей декларации в языках ПЛ/1 или Кобол для структуры того же вида, что и таблица, которая будет использоваться в качестве мишени для выборки и/или источника для обновления.
Результаты работы программы DCLGEN сохраняются в форме раздела библиотечного набора данных, имя которого специфицировано пользователем. Он может быть в дальнейшем включен в программу, записанную на включающем языке, с помощью следующего предложения:
ЕХЕС SQL INCLUDE раздел;
где «раздел» — это имя рассматриваемого раздела.
Предыдущее обсуждение показывает, что DCLGEN подобно каталогу выполняет некоторые функции, которые в более старых системах по традиции считались функциями отдельного программного продукта — словаря.
3. Как уже указывалось в разделе 10.2, каждое предложение языка SQL в принципе должно сопровождаться проверкой возвращаемого значения SQLCODE. Для того чтобы упростить этот процесс, предусмотрено предложение WHENEVER (всякий раз, когда). Это предложение имеет следующий синтаксис:
ЕХЕС SQL WHENEVER условие действие;
где «условие» может быть одним из следующих:
NOT FOUND (не найдено)
SQLWARNING (SQL-предупреждение)
SQLERROR (SQL-ошибка),
а «действие»—это либо предложение CONTINUE (продолжать), либо предложение GO TO (перейти к). Предложение WHENEVER не является исполняемым предложением. Оно представляет собой, скорее, директиву для прекомпилятора. Предложение «WHENEVER условие GO TO метка» заставляет прекомпилятор вставлять после каждого встречающегося ему исполняемого предложения языка SQL предложение вида «IF условие GO TO метка». В свою очередь предложение «WHENEVER условие CONTINUE» заставляет прекомпилятор не вставлять какие-либо такие предложения в связи с тем, что программист вставит их вручную. Указанные выше три «условия» определяются следующим образом:
NOT FOUND означает, что SQLCODE = 100
SQLWARNING означает, что SQLCODE > 0 и SQLCODE = 100
SQLERROR означает, что SQLCODE < О
Каждое предложение WHENEVER, которое встречается прекомпилятору при последовательном просмотре им текста программы (для конкретного условия), отменяет действие предыдущего найденного им такого предложения (для этого условия). Предполагается, что в начале текста программы для каждого из трех возможных условий имеется неявное предложение WHENEVER, специфицирующее в каждом случае CONTINUE.
В приведенном примере программы в учебных целях явным образом делаются все проверки на исключительную ситуацию. Если какая-либо исключительная ситуация имеет место, управление передается процедуре, которая печатает диагностическую информацию (в данном примере, область связи SQL), издает команду ROLLBACK (см. п. 4 ниже), а затем передает управление заключительному RETURN.
4. Когда программа некоторым образом обновляет базу данных, такое обновление следует рассматривать первоначально лишь как предварительное — предварительное в том смысле, что если что-либо в дальнейшем выполнится с ошибкой, это обновление может быть аннулировано самой программой или системой. Если, например, в программе встречается неожиданная ошибка, скажем, переполнение, и она аварийно завершается, то система от имени программы автоматически аннулирует все такие предварительные обновления. Обновления остаются предварительными до тех пор, пока не произойдет одно из двух: а) исполнится предложение COMMIT (фиксировать), которое все предварительные обновления сделает окончательными («зафиксированными»), или б) исполнится предложение ROLLBACK (откат), которое аннулирует все предварительные обновления. После того как обновление зафиксировано, гарантируется, что оно никогда не будет аннулировано (это определение понятия «зафиксированное обновление»).
В рассматриваемом примере программа издает COMMIT, когда она приходит к своему нормальному завершению, и издает ROLLBACK, если встречается какая-либо исключительная ситуация при исполнении предложений SQL. На самом деле, такая явная спецификация предложения COMMIT не является необходимой. Система автоматически будет издавать COMMIT от имени программы для любой программы, которая достигает нормального завершения. Она также автоматически будет издавать ROLLBACK от имени программы для любой программы, которая не достигает нормального завершения. В данном примере, однако, обязательна явная спецификация предложения ROLLBACK, поскольку программа спроектирована таким образом, чтобы осуществлять нормальное ее завершение даже в случае, если имеют место исключительные ситуации SQL.
Примечание. Вопросы обновления рассматривались выше в предположении обстановки TSO, поскольку предложения COMMIT и ROLLBACK допустимы только в этом случае. Эффект выполнения этих предложений в обстановке IMS и CICS достигается соответствующими обращениями (с помощью CALL) к IMS и CICS. Значительно более глубокое и полное обсуждение вопроса о «зафиксированных обновлениях» и связанного с этим понятия обработки транзакций приводится в следующей главе.
УПРАЖНЕНИЯ
10.1. Используя базу данных поставщиков, деталей и изделий, напишите программу с предложениями встроенного SQL, которая бы печатала все записи поставщиков в порядке номеров поставщиков. За каждой записью поставщика в листинге должны непосредственно следовать в порядке номеров изделий все записи изделий, для которых поставляет детали данный поставщик.
10.2. Почему Вы считаете, что необходима фраза FOR UPDATE?
10.3. Модифицируйте Ваше решение упражнения 10.1 с тем, чтобы дополнительно сделать следующее: а) повысить на 50 процентов состояние каждого поставщика, который поставляет детали более чем для двух изделий; б) удалить всех тех поставщиков, которые не поставляют деталей вообще ни для какого изделия.
10.4. (Более трудное). Пусть заданы таблицы
CREATE TABLE ДЕТАЛИ
(НОМЕР_ДЕТАЛИ . . . NOT NULL,
ОПИСАНИЕ . . . );
CREATE TABLE СТРУКТУРА_ДЕТАЛЕЙ
(НОМЕР_ОСНОВНОЙ_ДЕТАЛИ . . . NOT NULL,
НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ . . . NOT NULL,
КОЛИЧЕСТВО . . . );
где СТРУКТУРА_ДЕТАЛЕЙ показывает, какие детали (НОМЕР_ОСНОВНОЙ_ДЕТАЛИ) содержат другие детали (НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ) как компоненты первого уровня. Напишите программу с использованием SQL, которая бы печатала все составляющие детали данной детали на всех уровнях (задача «разузлования»).
Примечание. Следующий пример может помочь Вам наглядно представить себя эту задачу:
НОМЕР_ОСНОВ НОЙ_ДЕТАЛИ | НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ | КОЛИЧЕСТВО |
P1 P1 Р5 РЗ Р6 Р5 P2 | Р2 P4 РЗ Р6 Р1 Р6 P4 | 2 4 1 3 9 8 3 |
ОТВЕТЫ К НЕКОТОРЫМ УПРАЖНЕНИЯМ
10.1. Имеется в основном два способа написания такой программы.
В первом требуются два курсора, например CS и CJ, определяемых следующим образом:
ЕХЕС SQL DECLARE CS CURSOR FOR
SELECT НОМЕР_ПОСТАВЩИКА, ФАМИЛИЯ,
СОСТОЯНИЕ, ГОРОД
FROM S
ORDER BY НОМЕР_ПОСТАВЩИКА;
ЕХЕС SQL DECLARE CJ CURSOR FOR
SELECT НОМЕР_ИЗДЕЛИЯ, НАЗВАНИЕ, ГОРОД
FROM J
WHERE НОМЕР_ИЗДЕЛИЯ IN
(SELECT НОМЕР_ИЗДЕЛИЯ
FROM SPJ
WHERE НОМЕР_ПОСТАВЩИКА =
: CS_НОМЕР_ПОСТАВЩИКА)
ORDER BY НОМЕР_ИЗДЕЛИЯ;
где переменная включающего языка CS_НОМЕР_ПОСТАВЩИКА содержит значение номера поставщика, выбранного из курсора CS.
В этом случае имеем, по существу, следующую логику:
ЕХЕС SQL OPEN CS;
DO для всех записей S, к которым возможен доступ через CS;
ЕХЕС SQL FETCH CS INTO
: CS_НОМЕР_ПОСТАВЩИКА,
:CS_ФАМИЛИЯ, : CS_СОСТОЯНИЕ, : CS—ГОРОД;
печатать CS_НОМЕР_ПОСТАВЩИКА, CS_ФАМИЛИЯ,
CS_COCТОЯHИE, CS_ГОРОД;
ЕХЕС SQL OPEN CJ;
DO для всех записей J, к которым возможен доступ через CJ;
ЕХЕС SQL FETCH CJ INTO : CJ_НОМЕР_ИЗДЕЛИЯ.
: CJ_НАЗВАНИЕ, : CJ_ГОРОД;
печатать CJ_НОМЕР_ИЗДЕЛИЯ, CJ_НАЗВАНИЕ, CJ_ГОРОД;
END;
ЕХЕС SQL CLOSE CJ;
END;
ЕХЕС SQL CLOSE CS;
Недостаток приведенного решения заключается в том, что не используются в полной мере средства SQL для обработки данных на уровне множеств. По существу, программист «вручную» кодирует соединение. Второй подход требует единственного курсора, и поэтому позволяет действительно воспользоваться теоретико-множественной природой языка SQL. Однако, к сожалению, здесь необходимо использовать внешнее соединение, и поэтому программа должна сначала его построить следующим образом. (Это второе решение, следовательно, может быть менее эффективным, чем первое, так как в нем требуется, по существу, просматривать одни и те же данные несколько раз. Непосредственная поддержка внешнего соединения в SQL, желательная во всяком случае по соображениям ее широкой применяемости, могла бы облегчить решение этой задачи.)
ЕХЕС SQL CREATE TABLE ВРЕМЕННАЯ
(НОМЕР_ПОСТАВЩИКА . . . ,
ФАМИЛИЯ . . . ,
СОСТОЯНИЕ . . . ,
ГОРОД_ПОСТАВЩИКА . . . ,
НОМЕР_ИЗДЕЛИЯ . . . ,
НАЗВАНИЕ . . . ,
ГОРОД_ИЗДЕЛИЯ . . . , );
ЕХЕС SQL INSERT INTO ВРЕМЕННАЯ
SELECT НОМЕР_ПОСТАВЩИКА, ФАМИЛИЯ,
СОСТОЯНИЕ, ГОРОД_ПОСТАВЩИКА,
НОМЕР_ИЗДЕЛИЯ, НАЗВАНИЕ,
ГОРОД_ИЗДЕЛИЯ
FROM S, SPJ, J
WHERE S.HOMEP_ПОСТАВЩИКА = SPJ.НОМЕР_
ПОСТАВЩИКА AND SPJ .НОМЕР_ИЗДЕЛИЯ =
J.НОМЕР_ИЗДЕЛИЯ;
EXEC SQL INSERT INTO ВРЕМЕННАЯ
SELECT НОМЕР_ПОСТАВЩИКА, ФАМИЛИЯ,
СОСТОЯНИЕ, ГОРОД, 'bb', 'bb', 'bb'
FROM S
WHERE NOT EXISTS
(SELECT * FROM SPJ WHERE SPJ.HOMEP_
ПОСТАВЩИКА = S НОМЕР_ПОСТАВЩИКА);
Теперь:
EXEC SQL DECLARE CSJ CURSOR FOR
SELECT НОМЕР_ПОСТАВЩИКА, ФАМИЛИЯ,
СОСТОЯНИЕ, ГОРОД_ПОСТАВЩИКА,
НОМЕР_ИЗДЕЛИЯ, НАЗВАНИЕ,
FROM ГОРОД_ИЗДЕЛИЯ ВРЕМЕННАЯ
ORDER BY HOMEP_ПОСТАВЩИКА, НОМЕР_ИЗДЕЛИЯ;
EXEC SQL OPEN CSJ;
DO для всех записей таблицы ВРЕМЕННАЯ, к которым возможен доступ через CSJ;
EXEC SQL FETCH CSJ INTO :CS_НОМЕР_ПОСТАВЩИКА,
:CS_ФАМИЛИЯ, : CS_СОСТОЯНИЕ.
:CS_ГОРОД_ПОСТАВЩИКА,
:CJ_НОМЕР_ИЗДЕЛИЯ,
:CJ_НАЗВАНИЕ,
:CJ _ГОРОД_ИЗДЕЛИЯ;
IF CS_НОМЕР_ПОСТАВЩИКА отличается от его значения на предыдущей итерации
THEN печатать CS_НОМЕР_ПОСТАВЩИКА, CS_ФАМИЛИЯ,
CS_ СОСТОЯНИЕ, CS_ГОРОД_ПОСТАВЩИКА;
печатать CJ_НОМЕР_ИЗДЕЛИЯ, CJ_НАЗВАНИЕ, CJ_ГОРОД_ИЗДЕЛИЯ;
END;
EXEC SQL CLOSE CSJ;
EXEC SQL DROP TABLE ВРЕМЕННАЯ;
10.2. Предположим, что в программе имеется предложение DECLARE CURSOR следующего вида:
EXEC SQL DECLARE С CURSOR FOR
SELECT . . .
FROM Т
. . . . . . . ;
Выбор пути доступа, соответствующего курсору С, осуществляется генератором планов прикладных задач. Допустим, что выбран индекс, базирующийся на поле F таблицы Т. Тогда множество записей, к которым может быть осуществлен доступ через курсор С, когда он активизируется, будет упорядочено в соответствии со значениями поля F. Если бы программе было разрешено изменять значение F через курсор С, т. е. с помощью предложения UPDATE следующего вида:
EXEC SQL UPDATE T
SET F = . . .
WHERE CURRENT OF С;
то обновляемая запись должна была бы, вероятно, «перемещаться» (логически), поскольку она должна была бы теперь занимать иную позицию в упорядоченном множестве. Иными словами, курсор С по существу переходил бы в новую) позицию, и результат был бы непредсказуемым. Чтобы исключить такую ситуацию, пользователь должен предупредить генератор планов прикладных задач о всех полях, которые могут обновляться, с тем, чтобы не выбирались пути доступа, базирующиеся на этих полях.
10.3. Второе из двух предложенных решений упражнения 10.1 оперирует копией реальных данных. Поэтому для этой задачи мы вынуждены использовать первый подход. Если не считать этого, решение оказывается по существу простым. Соответствующие предложения встроенного SQL:
EXEC SQL UPDATE S
SET СОСТОЯНИЕ = СОСТОЯНИЕ * 1.5
WHERE CURRENT OF CS;
EXEC SQL DELETE
FROM S
WHERE CURRENT OF CS;
10.4. Это хороший пример задачи, с которой SQL в его текущем состоянии не позволяет хорошо справляться. Основная трудность заключается в следующем. Нам нужно «разбить» заданную деталь на n уровней, где значение n неизвестно во время написания программы. Если бы это было возможно, наиболее простой путь осуществления такого n-уровневого «разбиения» заключался бы в использовании рекурсивной программы, в которой каждое рекурсивное обращение создает новый курсор следующим образом:
GET LIST (ЗАДАННАЯ_ДЕТАЛЬ);
CALL RECURSION (ЗАДАННАЯ_ДЕТАЛЬ);
RETURN;
RECURSION: PROC (БОЛЕЕ_КРУПНАЯ_ДЕТАЛЬ) RECURSIVE;
DCL ДЕТАЛЬ_БОЛЕЕ_ВЫСОКОГО_УРОВНЯ . . .;
DCL ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ . . .;
EXEC SQL DECLARE С «повторно открываемый» CURSOR
FOR
SELECT НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ
FROM СТРУКТУРА_ДЕТАЛЕЙ
WHERE НОМЕР_ОСНОВНОЙ_ДЕТАЛИ =
: БОЛЕЕ_КРУПНАЯ_ДЕТАЛЬ;
печатать БОЛЕЕ_КРУПНАЯ_ДЕТАЛЬ;
EXEC SQL OPEN С;
DO WHILE (не найдено);
EXEC SQL FETCH С INTO : ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ;
CALL RECURSION(ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ);
END;
EXEC SQL CLOSE С;
END; /* конец процедуры RECURSION*
Предполагается, что придуманная нами спецификация «повторно открываемый» означает возможность издавать для курсора С предложение OPEN С, когда он уже открыт. Результатом выполнения этого предложения является создание нового экземпляра такого курсора для специфицированного запроса с использованием текущих значений переменных включающего языка, к которым обращается данный запрос. Предположим далее, что обращения к С в предложении FETCH (и др.) являются обращениями к «текущему» экземпляру курсора С и что предложение CLOSE уничтожает этот экземпляр и восстанавливает предыдущий экземпляр как «текущий». Иными словами, предполагается, что повторно открываемый курсор формирует стек, для которого OPEN и CLOSE служат операторами «протолкнуть в стек» и «вытолкнуть из стека».
К сожалению, в настоящее время эти предложения являются чисто гипотетическими. Таких средств, как повторно открываемый курсор, в настоящее время в языке SQL нет. В действительности, попытка издать предложение OPEN С для курсора С, который уже открыт, приведет к ошибке. Предыдущая программа неверна. Но приведенный пример ясно показывает, что «повторно открываемые курсоры» были бы очень желательным расширением текущей версии SQL.
Поскольку предыдущая процедура неработоспособна, приведем набросок одной из возможных (но весьма неэффективных) процедур, позволяющих решить задачу.
GET LIST (ЗАДАННАЯ_ДЕТАЛЬ);
CALL RECURSION (ЗАДАННАЯ_ДЕТАЛЬ);
RETURN;
RECURSION: PROC (БОЛЕЕ_КРУПНАЯ_ДЕТАЛЬ) RECURSIVE;
DCL ДЕТАЛЬ_БОЛЕЕ_ВЫСОКОГО_УРОВНЯ . . .; ...
DCL ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ . . . INITIAL ('bbbbbb');
EXEC SQL DECLARE С CURSOR FOR
SELECT НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ
FROM СТРУКТУРА_ДЕТАЛЕЙ
WHERE НОМЕР_ОСНОВНОЙ_ДЕТАЛИ =
: БОЛЕЕ_КРУПНАЯ_ДЕТАЛЬ
AND НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ >
:ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ
ORDER BY НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ;
DO всегда
печатать ДЕТАЛЬ_БОЛЕЕ_ВЫСОКОГО_УРОВНЯ;
EXEC SQL OPEN С;
EXEC SQL FETCH С INTO :ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ;
IF не найдена THEN RETURN;
IF найдена THEN
DO;
EXEC SQL CLOSE C;
CALL RECURSION (ДЕТАЛЬ_УРОВНЯ);
END;
END;
END; /*КОНЕЦ процедуры RECURSION*/
Заметим, что в этом решении при каждом обращении к процедуре RECURSION используется один и тот же курсор. (В противоположность этому, при каждом обращении к RECURSION динамически создаются новые экземпляры переменных ДЕТАЛЬ_БОЛЕЕ_ВЫСОКОГО_УРОВНЯ и ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_ УРОВНЯ. Эти экземпляры уничтожаются при завершении обращения к процедуре.) В связи с этим фактом следует использовать небольшую хитрость
...AND НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ >
:ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ
ORDER BY НОМЕР_СОСТАВЛЯЮЩЕЙ_ДЕТАЛИ, так что при каждом обращении к RECURSION мы игнорируем все непосредственные компоненты (ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ) для текущего значения ДЕТАЛЬ_БОЛЕЕ_ВЫСОКОГО_УРОВНЯ, которое уже было обработано.