Границы моего языка означают границы моего мира. Людвиг Виттгенштейн Основы языка PL/SQL Подпрограммы Подпрограммы PL/SQL Подпрограмма – это поименованный блок PL/SQL, который принимает параметры и может быть вызван. PL/SQL имеет два типа подпрограмм: • процедуры (procedure); • функции (function). Обычно процедуру вызывают для того, чтобы выполнить некоторое действие, а функцию – для того, чтобы вычислить некоторое значение. Подпрограммы можно определять в любом инструменте ORACLE, который поддерживает PL/SQL. Их можно объявлять: • в блоках PL/SQL, • в процедурах, • в функциях, • в пакетах. Подпрограммы должны объявляться в конце декларативной секции, после всех других программных объектов. Описание процедур и функций procedure <имя процедуры> [(<список формальных параметров>)] is [ -- объявления ] begin -- выполняемые действия [ exception -- обработчики исключительных ситуаций ] end [<имя процедуры>]; function <имя функции> [(<список формальных параметров>)] return <тип возвращаемого значения> is [ -- объявления ] begin -- выполняемые действия return <возвращаемое значение>; ... [ exception -- обработчики исключительных ситуаций ] end [<имя функции>]; Параметры подпрограмм В списке параметров каждый параметр описывается следующим образом: <имя переменной> [ IN | OUT | IN OUT ] <тип данных> [ { := | DEFAULT } <значение> ] Тип данных не может иметь ограничений по размеру. IN – мода входного параметра (по умолчанию); OUT – мода выходного параметра; IN OUT – мода входного и выходного параметра. Значение по умолчанию (DEFAULT) присваивается в той ситуации, когда при вызове процедуры (функции) фактический параметр не передается. Вызовы подпрограмм declare vdate date; function dt (v char) return date is begin return to_date(v, 'dd-mm-yyyy'); end; procedure period (d1 date, d2 date default trunc(sysdate)) is begin ... end; begin vdate := dt ('15-10-2012'); period (vdate); ... end; Вызовы пользовательских функций могут появляться в процедурных предложениях, но НЕ в предложениях SQL. Моды параметров Три возможные моды: IN (умалчиваемая), OUT и IN OUT. Они могут использоваться в любой процедуре. В функциях следует избегать использования моды OUT или IN OUT. Параметр с модой IN передает значение вызываемой подпрограмме. Внутри подпрограммы такой параметр выступает как константа, поэтому ему нельзя присвоить значение. Параметры IN могут инициализироваться умалчиваемыми значениями. Параметр с модой OUT позволяет возвращать значение вызывающей программе. Внутри подпрограммы такой параметр выступает как неинициализированная переменная. Поэтому его значение нельзя присваивать другим переменным или переприсвоить самому себе. Параметр IN OUT позволяет передавать в подпрограмму начальные значения и возвращать обновленные значения вызывающей программе. Внутри подпрограммы такой параметр выступает как инициализированная переменная. Поэтому ему можно присвоить значение, а его значение можно присваивать другим переменным. Моды параметров Выход из подпрограмм Предложение RETURN немедленно завершает выполнение подпрограммы и возвращает управление вызывающей программе. Выполнение продолжается с предложения, следующего за вызовом подпрограммы. Подпрограмма может содержать несколько предложений RETURN, ни одно из которых не обязано быть последним лексическим предложением в подпрограмме. Выполнение любого из них немедленно завершает подпрограмму. Однако наличие в подпрограмме нескольких точек выхода не является хорошей практикой программирования. В процедурах предложение RETURN не может содержать выражение. Это предложение просто возвращает управление вызывающей программе до достижения нормального конца процедуры. Однако в функциях предложение RETURN ДОЛЖНО содержать выражение, которое вычисляется при выполнении предложения RETURN. Результирующее значение присваивается идентификатору функции. Поэтому функция должна содержать хотя бы одно предложение RETURN. В противном случае PL/SQL возбуждает предопределенное исключение PROGRAM_ERROR во время выполнения. Упреждающее объявление подпрограмм PL/SQL требует, чтобы идентификатор был объявлен ДО его использования. Поэтому необходимо объявить подпрограмму, прежде чем вызывать ее. Для рекурсивных подпрограмм, для объявления подпрограмм в алфавитном порядке и проч. предусмотрена возможность упреждающих объявлений: DECLARE PROCEDURE calc_rating (...); -- упреждающее объявление /* Определить подпрограммы в алфавитном порядке. */ PROCEDURE award_bonus (...) IS ... BEGIN calc_rating(...); ... Разрешение вызовов процедур (функций) Пример функции -- Функция выполнения периодической фиксации. -- Параметры: m - текущее количество изменений, period - период. -- Увеличивает m на 1 и проверяет, достигнуто ли значение period. -- Если нет, возвращает увеличенное значение; если да, то commit и -- проверка таблицы break_table. Если break_table не пуста, то функция -- возвращает отрицательное число, иначе m обнуляется и возвращается 0. create or replace function inc(m IN binary_integer, period binary_integer) return binary_integer is v_break integer; begin m:=m+1; if m>=period then update prg set id=id+m where rownum<2; commit; select count(*) into v_break from break_table; if v_break=0 then m:=0; else return -1; end if; end if; return 0; end; / Пример функции -- Принимает 2 параметра: num – количество строк, d1 – дата/время начала обработки -- Возвращает время обработки этих строк в привычном виде (часы-минуты-секунды). create or replace function time_interval(num number, d1 date) return char is n binary_integer; h binary_integer; i binary_integer; d2 date; begin d2:=sysdate; n:=(d2-d1)*3600*24; h:= trunc(n/3600); i:= trunc((n-h*3600)/60); if n<60 then return 'Обработано '|| to_char(num) ||' строк(и) за ' ||to_char(n) || 'с.'; else return 'Обработано '|| to_char(num) ||' строк(и) за ‘ ||to_char(h)|| 'ч.‘ || to_char(i) || 'м.'; end if; end; / Пример процедуры -- Процедура подсчёта количества собственников, для которых в РИК -- содержится исправленная информация. create or replace procedure ispr_nsob is m integer:=0; k integer; l integer:=0; num integer:=0; cnt number; step number:=100000; begin select max(pr_gil) into cnt from tpv.nsob; cnt:=cnt/step; Продолжение примера процедуры for i in 0..cnt loop select count(*) into k from ric r, tpv.nsob n where r.id_num between i*step+1 and (i+1)*step and n.pr_gil between i*step+1 and (i+1)*step and r.id_num=n.pr_gil and (r.fam<>n.fam or r.name<> n.name or r.otch<>n.otch or convdate(born_year, born_month, born_day, 1)<>n.date_born); l:=l+k; insert into idd values(l); num:=inc(m,1); if num<0 then exit; end if; commit; end loop; dbms_output.put_line('Количество исправлений данных о собственниках – ' ||to_char(l)); end; / Рекурсия -- Пример рекурсивной функции: вычисление факториала FUNCTION fac (n POSITIVE) RETURN INTEGER IS -- возвращает n! BEGIN IF n = 1 THEN -- условие завершения RETURN 1; ELSE RETURN n * fac(n - 1); -- рекурсивный вызов END IF; END fac; Рекурсия PROCEDURE find_staff (mgr_no NUMBER, tier NUMBER := 1) IS boss_name CHAR(10); CURSOR c1 (boss_no NUMBER) IS SELECT empno, ename FROM emp WHERE mgr = boss_no; BEGIN /* Дать имя босса */ SELECT ename INTO boss_name FROM emp WHERE empno = mgr_no; IF tier=1 THEN INSERT INTO staff -- результир. таблица из 1 столбца VALUES (boss_name || ' руководит штатом'); END IF; /* Найти непосредственных подчиненных данного начальника */ FOR ee IN c1 (mgr_no) LOOP INSERT INTO staff VALUES (boss_name||' руководит '||ee.name||' на уровне '||to_char(tier)); /* Спуститься на следующий уровень подчиненности */ find_staff(ee.empno, tier + 1); -- рекурсивный вызов END LOOP; END; Альтернативный запрос: INSERT INTO staff SELECT PRIOR ename||' руководит '||ename||' на уровне '||to_char(LEVEL - 1) FROM emp START WITH empno = 7839 CONNECT BY PRIOR empno = mgr; Взаимная рекурсия -- Функции определения четного / нечетного числа FUNCTION odd (n NATURAL) RETURN BOOLEAN; -- упреждающее объявл. FUNCTION even (n NATURAL) RETURN BOOLEAN IS BEGIN IF n = 0 THEN RETURN TRUE; ELSE RETURN odd(n - 1); -- взаимно рекурсивный вызов END IF; END even; FUNCTION odd (n NATURAL) RETURN BOOLEAN IS BEGIN IF n = 0 THEN RETURN FALSE; ELSE RETURN even(n - 1); -- взаимно рекурсивный вызов END IF; END odd; odd(4) even(3) odd(2) even(1) odd(0) -- возвращает FALSE Позиционная и именная нотация При вызове подпрограммы можно записывать фактические параметры, используя позиционную или именную нотацию. Иными словами, можно указывать соответствие между фактическими и формальными параметрами через позиции этих параметров или через их имена. Пример: DECLARE acct INTEGER; amt REAL; PROCEDURE credit (acctno INTEGER, amount REAL) IS ... ... BEGIN credit(acct, amt); credit(amount => amt, acctno => acct); credit(acctno => acct, amount => amt); credit(acct, amount => amt); END; credit(acctno => acct, amt); -- неправильно -- позиционная нотация -- именная нотация -- именная нотация -- смешанная нотация Проблема алиасов Методы передачи параметров: копирование или ссылка. Примеры: 1) DECLARE str CHAR(10); PROCEDURE reverse (in_str CHAR, out_str OUT CHAR) IS ... BEGIN -- обратить порядок символов в строке ... -- значение in_str ('ABCD' / 'DCBA') зависит от метода передачи параметров END reverse; ... BEGIN str := 'ABCD'; reverse(str, str); ... -- результат не определен END; 2) DECLARE rent REAL; PROCEDURE raise_rent (increase IN OUT REAL) IS ... BEGIN rent := rent + increase; ... END raise_rent; ... BEGIN ... raise_rent(rent); -- не определено END; И здесь результат непредсказуем. Хранимые подпрограммы Повышение продуктивности Улучшение производительности Экономия памяти Целостность приложений Повышение безопасности Создание хранимых подпрограмм Оператор определения процедуры Oracle использует следующий синтаксис: create [or replace] procedure [имя_схемы.]имя_процедуры [(имя_параметра [{ in | out | in out }] тип_данных [(имя_параметра [{ in | out | in out }] тип_данных …])] { is | as } программа_на_PL/SQL create [or replace] function [имя_схемы.]имя_функции [(имя_параметра [{ in | out | in out }] тип_данных [(имя_параметра [{ in | out | in out }] тип_данных …])] return тип_данных { is | as } программа_на_PL/SQL Для создания процедуры или функции необходимо иметь привилегию CREATE PROCEDURE, а для создания процедуры или функции в чужой схеме – привилегию CREATE ANY PROCEDURE. Вызов хранимых подпрограмм 1) Из другой подпрограммы: create_dept(name, location); 2) Из прикладной программы: приложение прекомпилятора или приложение OCI может вызывать хранимые подпрограммы из анонимных блоков PL/SQL. EXEC SQL EXECUTE BEGIN create_dept(:name, :location); END; END-EXEC; Фактические параметры :name и :location – это хост-переменные. EXEC SQL EXECUTE BEGIN emp_actions.create_dept(:name, :location); END; END-EXEC; 3) Из инструмента ORACLE: SQL> EXECUTE create_dept('MARKETING', 'NEW YORK'); SQL> BEGIN create_dept('MARKETING', 'NEW YORK'); END; Замечания по использованию хранимых подпрограмм Подпрограммы, участвующие в распределенной транзакции, триггерах базы данных и приложениях SQL*Forms, не могут вызывать хранимых подпрограмм, содержащих предложения COMMIT, ROLLBACK или SAVEPOINT. Обращения к хранимым функциям могут появляться в процедурных предложениях, но НЕ в предложениях SQL внутри программ на PL/SQL. Хранимая подпрограмма ДЕЙСТВИТЕЛЬНА, если ни ее исходный код, ни любой из объектов, к которым она обращается, не был ни удален (DROP), ни заменен (REPLACE), ни изменен (ALTER) с момента последней компиляции этой подпрограммы. Если хранимая подпрограмма НЕДЕЙСТВИТЕЛЬНА, она должна быть перекомпилирована перед исполнением. Эта перекомпиляция осуществляется в два этапа. Сначала ORACLE определяет, нет ли недействительных подпрограмм или пакетов среди тех, к которым обращается данная подпрограмма, и перекомпилирует их. На втором этапе перекомпилируется сама требуемая подпрограмма, после чего она может быть исполнена. Пакеты Процедуры, функции и глобальные переменные, объединенные общим функциональным замыслом, часто оформляют в виде единого объекта базы данных — пакета. Прием оформления родственных программ в пакет хорошо известен из программистской практики. Особенностью пакетов PL/SQL является раздельная компиляция и хранение интерфейсной и исполнительной частей пакета. Пакет как объект состоит из двух частей: спецификации пакета и тела пакета. Привилегия create procedure (CREATE ANY PROCEDURE). Синтаксис: -- спецификация пакета: create [or replace] package [имя_схемы.]имя_пакета { is | as } -- объявления общих типов и объектов -- спецификации подпрограмм end имя_пакета; -- тело пакета create [or replace] package body [имя_схемы.]имя_пакета { is | as } -- объявления личных типов и объектов -- тела подпрограмм [BEGIN -- предложения инициализации] END [имя_пакета]; Пакеты Пример: PACKAGE trans_data IS TYPE TimeTyp IS RECORD (minute SMALLINT, hour SMALLINT); TYPE TransTyp IS RECORD (category VARCHAR2, account INTEGER, amount REAL, time TimeTyp); minimum_balance CONSTANT REAL := 10.00; number_processed INTEGER; insufficient_funds EXCEPTION; END trans_data; Для обращения к типам, объектам и подпрограммам, объявленным в спецификации пакета, используются квалифицированные ссылки: имя_пакета.имя_типа имя_пакета.имя_объекта имя_пакета.имя_подпрограммы Можно обращаться к содержимому пакета из триггеров базы данных, хранимых подпрограмм, встроенных блоков PL/SQL, а также анонимных блоков PL/SQL, посылаемых в ORACLE интерактивно через SQL*Plus или SQL*DBA. Перекрытие имен Пример: PACKAGE journal_entries IS PROCEDURE journalize (amount NUMBER, trans_date CHAR); PROCEDURE journalize (amount NUMBER, trans_date NUMBER); END journal_entries; PACKAGE BODY journal_entries IS PROCEDURE journalize (amount NUMBER, trans_date CHAR) IS BEGIN INSERT INTO journal VALUES (amount, TO_DATE(trans_date, 'DD-MON-YYYY')); END journalize; PROCEDURE journalize (amount NUMBER, trans_date NUMBER) IS BEGIN INSERT INTO journal VALUES (amount, TO_DATE(trans_date, 'J')); END journalize; END journal_entries; Замечания по использованию пакетов Переменные, константы и курсоры, объявленные в пакете, имеют следующие уникальные характеристики: Каждая сессия имеет свой собственный набор переменных, констант и курсоров из данного пакета. В данной сессии, при первом обращении к пакету его переменные и параметры курсоров имеют пустые значения, если они не инициализируются. В течение сессии пользователь пакета может изменить значения переменных и параметров курсоров в пакете. При окончании сессии эти значения теряются и должны быть повторно инициализированы в начале очередной сессии. При перекомпиляции спецификация пакета зависимые объекты помечаются как недействительные. При перекомпиляции тела пакета ORACLE определяет, действительны ли объекты, от которых зависит тело пакета. Если среди таких объектов есть недействительные, ORACLE перекомпилирует их, прежде чем перекомпилировать тело пакета. Если все перекомпиляции успешны, тело пакета становится действительным. В противном случае ORACLE возвращает ошибку выполнения, а тело пакета остается недействительным. Перекомпиляция тела пакета не вызывает недействительности зависимых объектов.