ОТВЕТЫ К НЕКОТОРЫМ УПРАЖНЕНИЯМ

 

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.

return false">ссылка скрыта

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

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 мы игнорируем все непосредственные компоненты (ДЕТАЛЬ_БОЛЕЕ_НИЗКОГО_УРОВНЯ) для текущего значения ДЕТАЛЬ_БОЛЕЕ_ВЫСОКОГО_УРОВНЯ, которое уже было обработано.