ГЛАВА 8 ---------------------------------------------------------------- ИСПОЛНИТЕЛЬНЫЕ ОКРУЖЕНИЯ Каждый инструмент несет с собой дух, которым он был создан. Вернер Гейзенберг Вы можете использовать PL/SQL с различными инструментами разработки приложений, включая * Интерфейсы вызовов ORACLE (OCI) * Прекомпиляторы ORACLE * SQL*DBA * SQL*Forms * SQL*Menu * SQL*Module * SQL*Plus * SQL*ReportWriter В этой главе рассказывается, как использовать PL/SQL в среде SQL*Plus, прекомпиляторов ORACLE и интерфейсов OCI. Замечание: PL/SQL не поддерживает ввода-вывода, поэтому в интерактивных инструментах, каких как SQL*Plus, вы должны использовать таблицу базы данных, чтобы сохранять свои результирующие значения. Исполнительные окружения 8-1 ---------------- Среда SQL*Plus Если у вас есть опция обработки транзакций, то вы можете использовать PL/SQL в среде SQL*Plus несколькими способами: * Ввести и сохранить блок PL/SQL. * Ввести и выполнить блок PL/SQL. * Создать скрипт, содержащий блоки PL/SQL. * Загрузить и выполнить скрипт, содержащий блоки PL/SQL. * Вызвать хранимую подпрограмму. Ввод анонимного блока --------------------- Каждый блок PL/SQL начинается с зарезервированного слова DECLARE, или, если блок не имеет декларативной части, со слова BEGIN. Ввод любого из этих слов в ответ на подсказку SQL*Plus (SQL>) заставляет SQL*Plus: * очистить буфер SQL * войти в режим ввода (INPUT) * игнорировать точки с запятой (терминаторы предложений SQL) Поскольку SQL*Plus требует, чтобы блок PL/SQL начинался со слов DECLARE или BEGIN, вы не можете начать блок с метки блока. Начав ввод блока, вы вводите его целиком, строку за строкой. Ввод одиночной точки в очередной строке заканчивает ввод блока и сохраняет его в буфере SQL. Вы можете сохранить ваш блок PL/SQL в текстовом файле (скрипте) с помощью следующей команды: SQL> SAVE имя_файла Для редактирования этого файла можно использовать построчный редактор SQL*Plus. Описание этого редактора содержится в документе SQL*Plus User's Guide. Закончив редактирование файла, вы можете снова сохранить его, введя следующую команду: SQL> SAVE имя_файла REPLACE Выполнение анонимного блока --------------------------- Заканчивая ввод блока PL/SQL, вы не обязаны вводить одиночную точку. Если вместо точки ввести в очередной строке одиночную наклонную черту (/), то блок PL/SQL сохраняется в буфере SQL и выполняется. Если буфер SQL содержит блок PL/SQL, то для его выполнения введите команду RUN или просто наклонную черту: SQL> RUN или SQL> / 8-2 Руководство пользователя и справочник по PL/SQL После выполнения блока PL/SQL вы вернетесь на подсказку SQL*Plus. Буфер SQL не очищается до тех пор, пока вы не начнете ввод очередного предложения SQL или блока PL/SQL. Создание скрипта ---------------- Вы можете использовать ваш привычный текстовый редактор для создания текстовых файлов, содержащих блоки PL/SQL. Хотя в блоке PL/SQL нельзя использовать предложений SQL*Plus, вы можете в вашем текстовом файле (скрипте) перемешивать блоки PL/SQL с предложениями SQL*Plus. В следующем примере блок PL/SQL находится среди предложений SQL*Plus, подготавливающих и выдающих отчет. Заметьте, что каждый блок PL/SQL должен заканчиваться наклонной чертой (/). CLEAR BREAKS; CLEAR COLUMNS; COLUMN ENAME HEADING NAME; TTITLE 'CLERICAL STAFF'; DECLARE avg_sal NUMBER(7,2); BEGIN SELECT AVG(sal) INTO avg_sal FROM emp; IF avg_sal > 1500 THEN UPDATE emp SET sal = sal * 1.05 WHERE job LIKE 'CLERK%'; END IF; END; / SELECT ENAME, SAL FROM EMP WHERE JOB LIKE 'CLERK%'; Два предложения CLEAR сбрасывают возможные установки, оставшиеся от предыдущего отчета. Предложение COLUMN изменяет заголовок столбца ENAME в отчете на NAME. Предложение TTITLE задает заголовок, который будет выдаваться на каждой странице отчета. Наклонная черта после END выполняет блок PL/SQL. Исполнительные окружения 8-3 Загрузка и выполнение скрипта ----------------------------- Чтобы загрузить скрипт PL/SQL в буфер SQL*Plus, используйте следующую команду: SQL> GET имя_файла; После загрузки скрипта в буфер SQL вы можете выполнить его, как описано выше в секции "Выполнение анонимного блока". Если хотите, вы можете загрузить и выполнить скрипт одной командой: SQL> START имя_файла; В блоке PL/SQL можно использовать преимущества средства подстановки переменных SQL*Plus. Если вы используете это средство, то SQL*Plus запрашивает у вас значение каждой переменной, имеющей префикс &, перед выполнением блока PL/SQL. Например, перед выполнением следующего блока SQL*Plus запросит у вас значение num_rows: BEGIN ... FOR i in 1..&num_rows LOOP ... END LOOP; END; Вызов хранимых подпрограмм -------------------------- Из SQL*Plus можно вызывать независимые и пакетированные подпрограммы, хранящиеся в локальной или удаленной базе данных. В следующем примере вызывается локальная независимая процедура create_dept: SQL> EXECUTE create_dept('ADVERTISING', 'NEW YORK'); Этот вызов эквивалентен следующему вызову из анонимного блока PL/SQL: SQL> BEGIN create_dept('ADVERTISING', 'NEW YORK'); END; В следующем примере связь базы данных newyork используется при вызове удаленной хранимой процедуры raise_salary: SQL> EXECUTE raise_salary@newyork(7499, 1500); Вы можете создавать синонимы, чтобы обеспечить прозрачность адресации для удаленных независимых процедур. 8-4 Руководство пользователя и справочник по PL/SQL ---------------- Среда прекомпилятора ORACLE Прекомпиляторы ORACLE позволяют встраивать блоки PL/SQL в программы, написанные на любом из следующих языков высокого уровня: * Ada * C * COBOL * FORTRAN * Pascal * PL/I Такие программы и языки называются соответственно ХОСТ-ПРОГРАММАМИ и ХОСТ-ЯЗЫКАМИ. Написав программу, вы выполняете прекомпиляцию исходного файла. Прекомпилятор проверяет программу на синтаксические ошибки, а затем генерирует модифицированный исходный файл, который можно компилировать, редактировать и выполнять обычным образом. Встраивание блоков PL/SQL ------------------------- Прекомпиляторы рассматривают блок PL/SQL как единое встроенное предложение SQL. Следовательно, вы можете размещать блок PL/SQL в любом месте программы, где можно поместить встроенное предложение SQL. Вы должны поместить блок PL/SQL между ключевыми словами EXEC SQL EXECUTE и END-EXEC: EXEC SQL EXECUTE BEGIN ... END; END-EXEC; Не забывайте, что ключевое слово END-EXEC должно заканчиваться терминатором, присущим хост-языку, в котором вы пишете. Исполнительные окружения 8-5 Использование хост-переменных ----------------------------- ХОСТ-ПЕРЕМЕННЫЕ используются для передачи данных из хост-программы в блок PL/SQL и для передачи данных и информации состояния из блока PL/SQL в хост-программу. Как хост-язык, так и PL/SQL могут устанавливать и обращаться к значению хост-переменной. Значение ВХОДНОЙ хост-переменной устанавливается хост-программой и используется ORACLE. Обратно, значение ВЫХОДНОЙ хост-переменной устанавливается ORACLE и используется хост-программой. При обращении к хост-переменной в блоке PL/SQL перед ее именем кодируется двоеточие, которое сообщает прекомпилятору, что это - обращение к хост-переменной, а не к переменной PL/SQL или к объекту базы данных. Примеры Следующая программа на Pro*C иллюстрирует использование хост-переменных в блоке PL/SQL. Программа запрашивает у пользователя имя сотрудника и передает его встроенному блоку PL/SQL, который использует это имя в запросе по базе данных ORACLE. Результаты запроса передаются обратно в хост-программу, которая распечатывает их. -- доступен на диске в файле EXAMP9 #includeEXEC SQL BEGIN DECLARE SECTION; VARCHAR empname[11]; VARCHAR jobtype[9]; VARCHAR hired[9]; int salary; int dept; int worked_longer; int higher_sal; int total_in_dept; VARCHAR uid[20]; VARCHAR pwd[20]; EXEC SQL END DECLARE SECTION; EXEC SQL INCLUDE SQLCA; main() { /* Set up userid and password */ strcpy (uid.arr,"scott"); uid.len = strlen(uid.arr); strcpy (pwd.arr,"tiger"); pwd.len = strlen(pwd.arr); printf("\n\n\tEmbedded PL/SQL Demo\n\n"); 8-6 Руководство пользователя и справочник по PL/SQL printf("Trying to connect..."); EXEC SQL WHENEVER SQLERROR GOTO errprint; /* Connect to ORACLE */ EXEC SQL CONNECT :uid IDENTIFIED BY :pwd; printf(" connected.\n"); for (;;) /* Loop infinitely */ { printf("\n** Name of employee? ( to end) "); gets(empname.arr); /* Get the name */ if (strlen(empname.arr) == 0) /* No name entered */ { EXEC SQL COMMIT WORK RELEASE; /* so log off */ exit(0); /* and exit */ } empname.len = strlen(empname.arr); jobtype.len = 9; hired.len = 9; /* ---------------------------------- */ /* ----- Begin the PL/SQL Block ----- */ /* ---------------------------------- */ EXEC SQL EXECUTE BEGIN SELECT job, hiredate, sal, deptno INTO :jobtype, :hired, :salary, :dept FROM emp WHERE ename = UPPER(:empname); /* Get number of people whose length of */ /* service is longer */ SELECT COUNT(*) INTO :worked_longer FROM emp WHERE hiredate < :hired; /* Get number of people with a higher salary */ SELECT COUNT(*) INTO :higher_sal FROM emp WHERE sal > :salary; /* Get number of people in the same department*/ SELECT COUNT(*) INTO :total_in_dept FROM emp WHERE deptno = :dept; END; END-EXEC; /* -------------------------------- */ /* ----- End the PL/SQL Block ----- */ /* -------------------------------- */ /* Properly terminate character strings */ /* returned by ORACLE */ jobtype.arr[jobtype.len] = '\0'; hired.arr[hired.len] = '\0'; Исполнительные окружения 8-7 /* Display all the information */ printf("\n%s's job is: %s\n", empname.arr, jobtype.arr); printf("Hired on: %s\n", hired.arr); printf(" %d people have served longer\n", worked_longer); printf("Salary is: %d\n", salary); printf(" %d people have a higher salary\n", higher_sal); printf("Department number is: %d\n", dept); printf(" %d people in the department\n", total_in_dept); } /* End of loop */ errprint: /* We end up here if an error occurs */ EXEC SQL WHENEVER SQLERROR CONTINUE; printf("\n\n>>>>> Error during execution:\n"); /* Print ORACLE error message and log off database */ printf("%s\n", sqlca.sqlerrm.sqlerrmc); EXEC SQL ROLLBACK RELEASE; exit(1); } Заметьте, что хост-переменная empname устанавливается перед тем, как войти в блок PL/SQL, а остальные хост-переменные (jobtype, hired и т.д.) принимают значения внутри блока. Все необходимые преобразования данных между типами данных ORACLE и стандартными типами данных хост-языка выполняются автоматически. 8-8 Руководство пользователя и справочник по PL/SQL Следующий пример на Pro*C показывает, как можно реализовать на PL/SQL две банковские транзакции: -- доступен на диске в файле EXAMP10 #include EXEC SQL BEGIN DECLARE SECTION; int acct, amount; VARCHAR tran_type[10]; VARCHAR status[65]; VARCHAR uid[20]; VARCHAR pwd[20]; EXEC SQL END DECLARE SECTION; EXEC SQL INCLUDE SQLCA; main() { strcpy(uid.arr,"scott"); uid.len=strlen(uid.arr); strcpy(pwd.arr,"tiger"); pwd.len=strlen(pwd.arr); printf("\n\n\tEmbedded PL/SQL Demo\n\n"); printf("Trying to connect..."); EXEC SQL WHENEVER SQLERROR GOTO errprint; EXEC SQL CONNECT :uid IDENTIFIED BY :pwd; printf(" connected.\n"); for (;;) /* Loop infinitely */ { printf("\n\n** What is the account number? (-1 to end)"); scanf("%d", &acct); if (acct == -1) /* Need to disconnect from ORACLE */ { /* and end program if acct is -1 */ EXEC SQL COMMIT RELEASE; exit(0); } printf("\n What type of transaction? (C)redit or \ (D)ebit "); scanf("%s", &tran_type.arr); tran_type.len = 1; /* Only want the first character */ printf("\n What is the transaction amount? (Whole \ dollars only) "); scanf("%d", &amount); Исполнительные окружения 8-9 /* ---------------------------------- */ /* ----- Begin the PL/SQL block ----- */ /* ---------------------------------- */ EXEC SQL EXECUTE DECLARE old_bal NUMBER(11,2); no_account EXCEPTION; BEGIN :tran_type := UPPER(:tran_type); IF :tran_type = 'C' THEN -- credit the account UPDATE accounts SET bal = bal + :amount WHERE account_id = :acct; IF SQL%ROWCOUNT = 0 THEN -- no rows affected RAISE no_account; ELSE :status := 'Credit complete.'; END IF; ELSIF :trantype = 'D' THEN -- debit the account SELECT bal INTO old_bal FROM accounts WHERE account_id = :acct; IF old_bal >= :amount THEN -- there's enough -- money UPDATE accounts SET bal = bal - :amount WHERE account_id = :acct; :status := 'Debit complete.'; ELSE :status := 'Not enough funds.'; END IF; ELSE :status := :tran_type || ' is not a legal transaction.'; END IF; COMMIT; EXCEPTION WHEN NO_DATA_FOUND OR no_account THEN :status := 'Account does not exist.'; WHEN OTHERS THEN :status := 'Error: ' || SQLERRM(SQLCODE); END; END-EXEC; /* -------------------------------- */ /* ----- End the PL/SQL block ----- */ /* -------------------------------- */ 8-10 Руководство пользователя и справочник по PL/SQL status.arr[status.len] = '\0'; -- null terminate printf("\n\n Status: %s", status.arr); } /* End of loop */ errprint: EXEC SQL WHENEVER SQLERROR CONTINUE; printf("\n\n>>>> Error during execution:\n"); printf("%s\n",sqlca.sqlerrm.sqlerrmc); EXEC SQL ROLLBACK RELEASE; exit(1); } Использование индикаторных переменных ------------------------------------- Вы можете ассоциировать с каждой хост-переменной необязательную индикаторную переменную. ИНДИКАТОРНАЯ ПЕРЕМЕННАЯ - это целочисленная переменная, которая отражает состояние значения хост-переменной. Двумя главными назначениями индикаторных переменных являются: * присваивание пустых значений входным хост-переменным * определение пустых или усеченных значений у выходных хост-переменных Для входной хост-переменной (т.е. переменной, устанавливаемой в хост-языке и опрашиваемой в ORACLE) индикаторная переменная может принимать следующие значения: -1 ORACLE игнорирует значение хост-переменной и присваивает пустое значение столбцу базы данных. >= 0 ORACLE присваивает столбцу базы данных текущее значение хост-переменной. Для выходной хост-переменной (т.е. переменной, устанавливаемой в ORACLE и опрашиваемой в хост-языке) индикаторная переменная может принимать следующие значения: -1 Столбец базы данных содержит NULL, поэтому значение хост-переменной не определено. 0 Значение базы данных присвоено хост-переменной без усечения. >0 Значение из базы данных усечено при присваивании хост-переменной. Положительное целое, возвращаемое в индикаторной переменной, представляет собой оригинальную длину строкового значения. Индикаторная переменная должна быть определена в секции деклараций (DECLARE SECTION) как двухбайтовая целочисленная переменная. В предложениях SQL она должна задаваться через двоеточие за именем соответствующей ей хост-переменной. Хост-языку необходимы индикаторные переменные, потому что он не может манипулировать пустыми значениями. Благодаря индикаторным переменным встроенный блок PL/SQL может принимать от хост-программы пустые значения и возвращать хост-программе пустые или усеченные значения. Исполнительные окружения 8-11 Следующий пример программы Pro*COBOL показывает, как при помощи индикаторной переменной можно отслеживать значение хост-переменной. * начало встроенного блока PL/SQL EXEC SQL EXECUTE BEGIN SELECT ENAME, COMM INTO :EMP-ENAME, :COMMISSION:COMM-IND FROM EMP WHERE EMPNO = :EMP-NUMBER -- остаток блока PL/SQL ... END; END-EXEC. * конец встроенного блока PL/SQL MOVE COMMISSION TO COMMISSION-OUT. DISPLAY "Commission: " WITH NO ADVANCING. IF COMM-IND = -1 * если возвращенное значение индикаторной переменной равно -1, * то значение ее выходной хост-переменной есть null DISPLAY "N/A" ELSE DISPLAY COMMISSION-OUT. Внутри блока PL/SQL индикаторная переменная должна задаваться через двоеточие за именем соответствующей ей хост-переменной. В блоке PL/SQL нельзя обращаться к индикаторной переменной индивидуально. Более того, если вы обращаетесь к хост-переменной вместе с ее индикаторной переменной, вы должны использовать этот способ обращения всюду в данном блоке. Иными словами, если вы пишете :переменная:индикатор, вы не можете писать просто :переменная в другом месте этого же блока. Это вытекает из того факта, что PL/SQL трактует запись :переменная:индикатор как единственную переменную. В следующем примере, так как индикаторная переменная job_ind появляется вместе со своей хост-переменной job_host в предложении SELECT, она должна кодироваться в том же сочетании и в предложении IF: EXEC SQL EXECUTE BEGIN ... SELECT ename, job INTO :ename_host, :job_host:job_ind FROM emp WHERE empno = :empno_host; ... IF :job_host:job_ind IS NULL THEN RAISE status_unknown; END IF; ... END; END-EXEC; Хотя вы не можете обращаться непосредственно к индикаторным переменным в блоке PL/SQL, PL/SQL проверяет значение этих переменных при входе в блок и корректно устанавливает их значения при выходе из блока. 8-12 Руководство пользователя и справочник по PL/SQL Пустые значения При входе в блок, если индикаторная переменная имеет значение -1, PL/SQL автоматически присваивает значение NULL соответствующей хост-переменной. При выходе из блока, если хост-переменная имеет значение NULL, PL/SQL автоматически присваивает индикаторной переменной значение -1. В следующем примере, если ename_ind имеет значение -1 перед входом в блок PL/SQL, то будет возбуждено исключение name_missing. EXEC SQL EXECUTE BEGIN ... IF :ename_host:ename_ind IS NULL THEN RAISE name_missing; END IF; ... EXCEPTION WHEN name_missing THEN ... END; END-EXEC; Усеченные значения PL/SQL не возбуждает исключения, когда присваивание значения хост-переменной вызывает усечение. Однако, если при этом используется индикаторная переменная, то PL/SQL сохраняет в ней длину значения перед усечением. В следующем примере хост-программа может выяснить, путем проверки значения ename_ind, было ли усечение при присваивании значения переменной ename_host: EXEC SQL EXECUTE DECLARE new_ename CHAR(10); ... BEGIN ... :ename_host:ename_ind := new_ename; ... END; END_EXEC; Исполнительные окружения 8-13 Использование псевдотипа VARCHAR -------------------------------- Прекомпиляторы ORACLE позволяют использовать псевдотип VARCHAR для объявления символьных строк переменной длины. (ПСЕВДОТИП - это тип данных, не присущий хост-языку). Переменнпя VARCHAR хранится как 2-байтовое поле длины, за которым следует n-байтовая строка символов, где n может принимать значения от 1 до 65533. Например, прекомпилятор Pro*C расширяет декларацию EXEC SQL BEGIN DECLARE SECTION VARCHAR my_ename[10] EXEC SQL END DECLARE SECTION; в следующую структуру данных: struct { unsigned short len; unsigned char arr[10]; } my_ename; Чтобы получить длину переменной VARCHAR, вы просто обращаетесь к ее полю длины (len). Вам не приходится использовать строковую функцию или алгоритм подсчета символов. ORACLE автоматически устанавливает поле длины каждой выходной хост-переменной типа VARCHAR. Однако для того, чтобы использовать ВЫХОДНУЮ хост-переменную VARCHAR в блоке PL/SQL, вы должны инициализировать ее поле длины ПЕРЕД входом в блок PL/SQL. Поэтому установите поле длины равным объявленному (максимальному) значению, как показано в следующем примере на Pro*C: EXEC SQL BEGIN DECLARE SECTION; int my_empno; VARCHAR my_ename[10] /* declare variable-length string */ float my_sal; ... EXEC SQL END DECLARE SECTION; ... my_ename.len = 10; /* initialize length field */ EXEC SQL EXECUTE BEGIN SELECT ename, sal INTO :my_ename, :my_sal FROM emp WHERE empno = :my_empno; ... END; END-EXEC; ... Для дополнительной информации о псевдотипе VARCHAR обратитесь к соответствующему Дополнению в руководстве по прекомпилятору ORACLE. 8-14 Руководство пользователя и справочник по PL/SQL Использование предложения DECLARE TABLE --------------------------------------- Если ваш встроенный блок PL/SQL обращается к таблице, которой еще не существует, то вы получите ошибку, когда прекомпилятор попытается проверить ссылки на таблицы. Чтобы избежать такой ошибки прекомпиляции, используйте предложение DECLARE TABLE, чтобы сообщить прекомпилятору структуру таблицы. В следующем примере таблица dept объявляется для прекомпилятора Pro*C: EXEC SQL DECLARE dept TABLE (deptno NUMBER(2), dname CHAR(14), loc CHAR(13)); Если вы используете DECLARE TABLE для определения таблицы, уже существующей в базе данных, то прекомпилятор использует ваше определение, игнорируя словарь данных базы данных ORACLE. Заметьте, что предложение DECLARE TABLE нельзя использовать внутри блока PL/SQL. Использование опции SQLCHECK Прекомпиляторы ORACLE могут помочь вам отладить программу, проверяя синтаксис и семантику встроенных предложений SQL и блоков PL/SQL. Вы управляете уровнем этого контроля, специфицируя в командной строке опцию прекомпилятора SQLCHECK. Вы можете специфицировать следующие значения для SQLCHECK: * SEMANTICS * SYNTAX * NONE Однако, если вы используете в вашей программе встроенные блоки PL/SQL, то вы должны всегда специфицировать SQLCHECK=SEMANTICS. При этом прекомпилятор проверяет синтаксис и семантику предложений манипулирования данными SQL, типов данных хост-переменных и блоков PL/SQL. Информацию, необходимую для семантического контроля, прекомпилятор берет из встроенных предложений DECLARE TABLE, или, если вы специфицируете опцию командной строки USERID, путем соединения с ORACLE и опроса словаря данных. Вам нет необходимости соединяться с ORACLE, если все таблицы базы данных, к которым обращаются ваши предложения SQL и блоки PL/SQL, определены в предложениях DECLARE TABLE. Исполнительные окружения 8-15 Использование хост-массивов с таблицами PL/SQL ---------------------------------------------- В блок PL/SQL можно передавать хост-массивы и индикаторные массивы. Их можно индексировать переменной PL/SQL типа BINARY_INTEGER или хост-переменной, совместимой с этим типом. Обычно в PL/SQL передается весь массив, но вы можете использовать предложение ARRAYLEN, обсуждаемое ниже, чтобы специфицировать меньший размер массива. Более того, с помощью вызова процедуры вы можете присвоить все значения в хост-массиве строкам таблицы PL/SQL. Если индекс массива меняется в диапазоне m..n, то диапазон индекса соответствующей таблицы PL/SQL будет всегда 1..n-m+1. Например, если индекс массива лежит в диапазоне 5..10, то диапазон индекса соответствующей таблицы PL/SQL будет 1..6. В следующем примере на Pro*C хост-массив с именем salary передается в блок PL/SQL, который использует его в вызове функции. Среди формальных параметров функции есть таблица PL/SQL с именем num_tab. Вызов функции заставляет PL/SQL присвоить все значения хост-массива, соответствующего фактическому параметру salary, строкам формального параметра num_tab. #include main() { /* Объявить хост-массив */ EXEC SQL BEGIN DECLARE SECTION; ... float salary[100]; EXEC SQL END DECLARE SECTION; /* Заполнить хост-массив */ ... EXEC SQL EXECUTE DECLARE TYPE NumTabTyp IS TABLE OF REAL INDEX BY BINARY INTEGER: mid_salary REAL; n BINARY INTEGER := 100; FUNCTION median (num_tab NumTabTyp, n INTEGER) RETURN REAL IS BEGIN ... -- вычислить среднее по таблице END; BEGIN mid_salary := median(:salary, n); -- передать массив ... END; END-EXEC; ... } 8-16 Руководство пользователя и справочник по PL/SQL Вы можете также использовать вызов процедуры для того, чтобы присвоить значения всех строк таблицы PL/SQL соответствующим элементам хост-массива. См. пример программы 6 в приложении B. На рис.8-1 показаны допустимые преобразования типов между строками таблицы PL/SQL и элементами хост-массива. Например, хост-массив типа LONG совместим с таблицей PL/SQL типа VARCHAR2, LONG, RAW или LONG RAW. Стоит отметить, что он несовместим с таблицей PL/SQL типа CHAR. Рис.8-1 Допустимые преобразования типов Хост-массив г=======T====T====T========T======T====T=====T========¬ ¦CHAR ¦DATE¦LONG¦LONG RAW¦NUMBER¦RAW ¦ROWID¦VARCHAR2¦ г========+=======+====+====+========+======+====+=====+========¦ Таблица ¦CHAR ¦ Да ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ PL/SQL ¦--------+-------+----+----+--------+------+----+-----+--------¦ ¦DATE ¦ ¦ Да ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦--------+-------+----+----+--------+------+----+-----+--------¦ ¦LONG ¦ ¦ ¦ Да ¦ Да ¦ ¦ Да ¦ ¦ Да ¦ ¦--------+-------+----+----+--------+------+----+-----+--------¦ ¦LONG RAW¦ ¦ ¦ Да ¦ Да ¦ ¦ Да ¦ ¦ Да ¦ ¦--------+-------+----+----+--------+------+----+-----+--------¦ ¦NUMBER ¦ ¦ ¦ ¦ ¦ Да ¦ ¦ ¦ ¦ ¦--------+-------+----+----+--------+------+----+-----+--------¦ ¦RAW ¦ ¦ ¦ Да ¦ Да ¦ ¦ Да ¦ ¦ Да ¦ ¦--------+-------+----+----+--------+------+----+-----+--------¦ ¦ROWID ¦ ¦ ¦ ¦ ¦ ¦ ¦ Да ¦ ¦ ¦--------+-------+----+----+--------+------+----+-----+--------¦ ¦VARCHAR2¦ ¦ ¦ Да ¦ Да ¦ ¦ Да ¦ ¦ Да ¦ L========¦=======¦====¦====¦========¦======¦====¦=====¦========- Прекомпиляторы ORACLE не проверяют вашего использования хост-массивов. В частности, не выполняется никакой проверки индекса на диапазон. Исполнительные окружения 8-17 Предложение ARRAYLEN Предположим, что вы должны передать входной хост-массив в блок PL/SQL для обработки. По умолчанию при привязывании такого массива прекомпилятор ORACLE использует его объявленные размеры. Однако, если вы не хотите обрабатывать весь массив, выы можете использовать предложение ARRAYLEN, чтобы специфицировать меньший размер. ARRAYLEN ассоциирует хост-массив с хост-переменной, в которой хранится значение размерности массива. Повторим последний пример, но на этот раз используем ARRAYLEN, чтобы перекрыть умалчиваемый размер хост-массива salary: #include main() { /* Объявить хост-массив */ EXEC SQL BEGIN DECLARE SECTION; ... float salary[100]; int my_dim; EXEC SQL ARRAYLEN salary (my_dim); EXEC SQL END DECLARE SECTION; /* Заполнить хост-массив */ ... /* Установить меньший размер массива */ my_dim = 25; EXEC SQL EXECUTE DECLARE TYPE NumTabTyp IS TABLE OF REAL INDEX BY BINARY INTEGER: mid_salary REAL; n BINARY INTEGER := 100; FUNCTION median (num_tab NumTabTyp, n INTEGER) RETURN REAL IS BEGIN ... -- вычислить среднее по таблице END; BEGIN mid_salary := median(:salary, :my_dim); --передать массив ... END; END-EXEC; ... } Лишь 25 элементов массива передаются блоку PL/SQL, потому что предложение ARRAYLEN ассоциировало размер массива с переменной my_dim, а ее текущее значение равно 25. Как следствие, когда блок PL/SQL передается в ORACLE для исполнения, пересылается массив меньшего размера. Это экономит время, а в сетевом окружении уменьшает трафик. 8-18 Руководство пользователя и справочник по PL/SQL Следующий пример на Pro*C показывает, как присваиваются значения элементам хост-массива и как адресовать индивидуальные элементы хост-массива: #include ... main() { /* Объявить хост-массив и индикаторный массив */ EXEC SQL BEGIN DECLARE SECTION; float sal [100]; float comm [100]; short icomm [100]; int i = 50; EXEC SQL END DECLARE SECTION; EXEC SQL EXECUTE DECLARE TYPE SalTabTyp IS TABLE OF emp.sal%TYPE INDEX BY BINARY INTEGER: TYPE CommTabTyp IS TABLE OF emp.comm%TYPE INDEX BY BINARY INTEGER: sal_tab SalTabTyp; comm_tab CommTabTyp; j BINARY INTEGER := 0; ... BEGIN /* Загрузить оклады и комиссионные в таблицы PL/SQL */ FOR emprec IN (SELECT sal, comm FROM emp) LOOP j := j + 1; sal_tab(j) := emprec.sal; comm_tab(j) := emprec.comm; END LOOP; /* Обработать таблицы PL/SQL */ ... /* Присвоить значения строк хост-массивам */ WHILE :i > 0 LOOP :sal(:i) := sal_tab(:i); /* если comm пусто, icomm получит значение -1 */ :comm:icomm(:i) := comm_tab(:i); :i := :i - 1; END LOOP; END; END-EXEC; ... } Исполнительные окружения 8-19 Использование динамического SQL ------------------------------- В отличие от статического SQL, предложения динамического SQL не встраиваются в вашу исходную программу. Вместо этого они хранятся в символьных строках, которые ваша программа вводит (или генерирует) во время выполнения. Такие строки могут вводиться интерактивно или считываться из файла. Прекомпиляторы ORACLE рассматривают блок PL/SQL как одно предложение SQL. Поэтому, как и предложение SQL, блок PL/SQL может быть сохранен в строковой хост-переменной для последующей обработки командами динамического SQL. Не забывайте, что в таком блоке PL/SQL нельзя использовать однострочные комментарии (об этом говорилось в главе 2). Следующие подразделы кратко иллюстрируют использование PL/SQL с динамическим SQL при помощи методов 1, 2 и 4. Для дополнительной информации обратитесь к документу Programmer's Guide to the ORACLE Precompilers. Метод 1 Если ваш блок PL/SQL не содержит хост-переменных, вы можете использовать метод 1, чтобы выполнить строку PL/SQL обычным способом. В следующем примере на Pro*C вы запрашиваете у пользователя текст блока PL/SQL, сохраняете его в строковой хост-переменной с именем user_block, а затем выполняете его: main() { printf("\nEnter a PL/SQL block: "); scanf("%s", user_block); EXEC SQL EXECUTE IMMEDIATE :user_block; ... Если вы сохраняете блок PL/SQL в строковой переменной, опустите фразу EXEC SQL EXECUTE, фразу END-EXEC и терминатор предложения. 8-20 Руководство пользователя и справочник по PL/SQL Метод 2 Если ваш блок PL/SQL содержит заранее известное число входных и выходных хост-переменных, вы можете использовать метод 2, чтобы сначала подготовить, а затем выполнить строку PL/SQL. В примере на Pro*C ниже, вы запрашиваете блок PL/SQL у пользователя, сохраняете его в строковой переменной user_block, затем подготавливаете и выполняете ее. На этот раз блок PL/SQL использует хост-переменную с именем my_empno. main() { printf("\nEnter a PL/SQL block: "); scanf("%s", user_block); EXEC SQL PREPARE my_block FROM :user_block; EXEC SQL EXECUTE my_block USING :my_empno; ... Заметим, что my_block - это идентификатор, используемый прекомпилятором, но не хост-переменная и не просто переменная. Прекомпилятор рассматривает все хост-переменные PL/SQL как ВХОДНЫЕ, независимо от того, как они используются в блоке PL/SQL. Поэтому вы должны указать в фразе USING ВСЕ хост-переменные. Когда строка PL/SQL исполняется, хост-переменные из фразы USING замещают соответствующие шаблоны в строке, подготовленной через PREPARE. Хотя прекомпилятор трактует все хост-переменные PL/SQL как входные, значения присваиваются корректно. Входные значения (из программы) присваиваются входным хост-переменным, а выходные значения (например, из столбцов таблиц) присваиваются выходным хост-переменным. Метод 4 Если ваш блок PL/SQL содержит заранее неизвестное число входных или выходных хост-переменных, вы должны использовать метод 4. Чтобы использовать метод 4, вы организуете "связной дескриптор" для всех входных и выходных хост-переменных. Выполнение предложения DESCRIBE BIND VARIABLES сохраняет в связном дескрипторе информацию о входных и выходных хост-переменных. Для полного обсуждения метода 4 обратитесь к соответствующему дополнению к документу Programmer's Guide to the ORACLE Precompilers. Исполнительные окружения 8-21 Имитация динамического SQL -------------------------- Без динамического SQL вы не можете использовать переменные PL/SQL в запросе для спецификации имен выбираемых столбцов. В следующем примере, если в таблице EMP есть столбец с именем COLX, то переменной my_ename будет присвоено значение этого столбца, а не столбца ename, как хотелось. Если же в таблице EMP нет столбца с именем COLX, то переменной my_ename будет присвоено значение переменной PL/SQL colx (т.е. символьная строка 'ename', а не значение столбца ename). DECLARE colx CHAR(10); my_ename CHAR(10); ... BEGIN colx := 'ename'; SELECT colx INTO my_ename FROM emp WHERE ... ... END; Однако, в этом случае, вы можете имитировать динамический SQL, используя функцию DECODE. В следующем примере возвращаемые данные зависят от значения переменной my_column, задающей имя столбца: DECLARE my_column CHAR(10); my_data emp.ename%TYPE; BEGIN ... my_column := 'hiredate'; ... SELECT DECODE(my_column, 'ename', ename, 'hiredate', TO_CHAR(hiredate, 'ddmmyy'), 'empno', empno) INTO my_data FROM emp WHERE ... ; END; Данные, которые возвращает DECODE, всегда преобразуются к типу данных первого результирующего выражения. В примере выше, первое результирующее выражение - ename, и, так как ename имеет тип CHAR, ВСЕ возвращаемые данные будут приводиться к типу CHAR. Даже если my_column задает числовой столбец (как empno), его значение будет неявно преобразовываться в символьную строку. Таким образом, объявление my_data как emp.ename%TYPE правильно. Вы можете использовать этот способ и в любой другой среде. Например, он будет работать в SQL*Forms, SQL*Menu и SQL*Plus. 8-22 Руководство пользователя и справочник по PL/SQL Вызов хранимых подпрограмм -------------------------- Чтобы вызвать хранимую подпрограмму из хост-программы, вы должны использовать анонимный блок PL/SQL. В следующем примере вызывается независимая процедура create_dept: EXEC SQL EXECUTE BEGIN create_dept(:number, :name, :location); END; END-EXEC; Заметьте, что фактические параметры number, name и location являются хост-переменными. В следующем примере процедура creatd_dept является частью пакета с именем emp_actions, и вы должны использовать квалифицированную ссылку при вызове: EXEC SQL EXECUTE BEGIN emp_actions.create_dept(:number, :name, :location); END; END-EXEC; ---------------- Среда OCI В интерфейсе вызовов ORACLE (OCI) блоки PL/SQL обрабатываются так же, как и предложения SQL, за исключением одного небольшого, но важного отличия. В блоке PL/SQL вы должны использовать вызов OBNDRV, а не ODEFIN, для связывания всех переменных в предложении SQL или PL/SQL. Это касается как входных, так и выходных переменных. Вызов ODEFIN НЕ поддерживается для блоков PL/SQL. В PL/SQL все запросы должны иметь фразу INTO, содержащую заместители (хост-переменные и/или переменные PL/SQL) для всех выбираемых элементов списка SELECT. Например, следующее предложение SELECT некорректно в блоке PL/SQL: SELECT ename, sal FROM emp; Вместо этого оно должно кодироваться следующим образом: SELECT ename, sal INTO :my_ename, :my_sal FROM emp; Здесь my_ename и my_sal - заместители, соответствующие выбираемым столбцам ENAME и SAL из списка SELECT. Вы должны выполнить привязку этих заместителей, используя вызов OBNDRV. Кроме того, все заместители, используемые в блоках PL/SQL, должны быть поименованы (как my_ename в примере выше). Нумерованные заместители, такие как 10, и соответствующий вызов OBNDRN, ПОКА НЕ ПОДДЕРЖИВАЮТСЯ для блоков PL/SQL. Исполнительные окружения 8-23 Полный пример Следующая программа, написанная на C, демонстрирует пример реализации двух банковских транзакций на PL/SQL. Листинги включаемых файлов ocidfn.h и ocidem.h приведены в документе Programmer's Guide to the ORACLE Call Interfaces. #include #include #include #define ANSI #include "ocidfn.h" #include "ocidem.h" cda_def cda; lda_def lda; char hda[256]; char sqlstm[2048]; void error_handler(struct cda_def *); main() { int acct_number; char trans_type[1]; float trans_amt; char status[80]; if (orlon(&lda, hda, "scott/tiger", -1, (char *) 0, -1, -1)) { printf("Connect failed.\n"); exit(EXIT_FAILURE); } if (oopen(&cda, &lda, (char *) 0, -1, -1, (char *) 0, -1)) { printf("Error opening cursor. Exiting...\n"); exit(EXIT_FAILURE); } printf("Connected to ORACLE.\n"); 8-24 Руководство пользователя и справочник по PL/SQL /* Construct a PL/SQL block. */ strcpy(sqlstm, "DECLARE\ old_bal number(9,2);\ err_msg CHAR(70);\ nonexistent EXCEPTION;\ BEGIN\ :xtrans_type := UPPER(:xtrans_type);\ IF :xtrans_type = 'C' THEN\ UPDATE ACCTS SET BAL = BAL + :xtrans_amt\ WHERE ACCTID = :xacct_number;\ IF SQL%ROWCOUNT = 0 THEN\ RAISE nonexistent;\ ELSE\ :xstatus := 'Credit applied';\ END IF;\ ELSIF :xtrans_type = 'D' THEN\ SELECT BAL INTO old_bal FROM accts\ WHERE ACCTID = :xacct_number;\ IF old_bal = :xtrans_amt THEN\ UPDATE ACCTS SET BAL = BAL - :xtrans_amt\ WHERE ACCTID = :xacct_number;\ :xstatus := 'Debit applied';\ ELSE\ :xstatus := 'Insufficient funds';\ END IF;\ ELSE\ :xstatus := 'Invalid type: ' || :xtrans_type;\ END IF;\ COMMIT;\ EXCEPTION\ WHEN NO_DATA_FOUND OR nonexistent THEN\ :xstatus := 'Nonexistent account';\ WHEN OTHERS THEN\ err_msg := SUBSTR(SQLERRM, 1, 70);\ :xstatus := 'Error: ' || err_msg;\ END;"); Исполнительные окружения 8-25 /* Parse the PL/SQL block. */ if (oparse(&cda, sqlstm, -1, 0, 2)) { error_handler(&cda); exit(EXIT_FAILURE); } /* Bind the status variable. */ if (obndrv(&cda, ":xstatus", -1, status, 70, 5, -1, (char *) 0, (char *) 0, -1, -1)) { error_handler(&cda); exit(EXIT_FAILURE); } /* Bind the transaction type variable. */ if (obndrv(&cda, ":xtrans_type", -1, trans_type, 1, 1 -1, (char *) 0, (char *) 0, -1, -1)) { error_handler(&cda); exit(EXIT_FAILURE); } /* Bind the account number. */ if (obndrv(&cda, ":xacct_number", -1, &acct_number, sizeof(int), 3 -1, (char *) 0, (char *) 0, -1, -1)) { 8-26 Руководство пользователя и справочник по PL/SQL error_handler(&cda); exit(EXIT_FAILURE); } /* Bind the transaction amount variable. */ if (obndrv(&cda, ":xtrans_amt", -1, &trans_amt, sizeof(float), 4 -1, (char *) 0, (char *) 0, -1, -1)) { error_handler(&cda); exit(EXIT_FAILURE); } for (;;) { printf(\nAccount number: "); scanf("%d", &acct_number); fflush(stdin); if (acct_number == 0) break; printf("Transaction type (D or C): "); scanf("%c", trans_type); fflush(stdin); printf("Transaction amount: "); scanf("%f", &trans_amt); fflush(stdin); /* Execute the block. */ if (oexec(&cda)) error_handler(&cda); printf("%s\n", status); } printf("Have a good day!\n"); exit(EXIT_SUCCESS); } Исполнительные окружения 8-27 void error_handler(struct cda_def *cda) { char msg[512]; int n; printf("\n>>> ORACLE Error\n"); n = oerhms(&lda, cda-rc, msg, sizeof(msg)); printf("%.*s\n", n, msg); printf("Processing OCI function %s\n\n", oci_func_tab[cda-fc]); } Вызов хранимых подпрограмм -------------------------- Чтобы вызвать хранимую подпрограмму из программы OCI, вы должны использовать анонимный блок PL/SQL. В следующем примере на C независимая процедура raise_salary копируется в строковую переменную plsql_block: strcpy(plsql_block,"BEGIN raise_salary(:emp_id,:amount); END;"); После этого строка PL/SQL может быть обвязана и выполнена, как и обычное предложение SQL. 8-28 Руководство пользователя и справочник по PL/SQL