ГЛАВА 7

        ----------------------------------------------------------------

        ПАКЕТЫ


                                Сколь ни хорошо унаследовать библиотеку,
                                            еще лучше собрать ее самому.
                                                         Огюстен Биррель

        Эта глава описывает, как группировать взаимосвязанные конструкты
        PL/SQL  в  пакет.   Пакетированные  конструкты  могут   включать
        коллекцию  процедур   или  совокупность   определений  типов   и
        объявлений переменных.  Например, пакет Управление кадрами может
        включать  процедуры  приема  на  работу  и  увольнения.  Однажды
        написанный, ваш общецелевой пакет компилируется и сохраняется  в
        базе данных  ORACLE, где,  как библиотека,  его содержимое может
        совместно использоваться многими приложениями.

        Замечание: Пакеты могут сохраняться в базе данных ORACLE лишь
        при наличии Процедурного расширения базы данных.









































                                                             Пакеты  7-1


----------------

Что такое пакет?

        ПАКЕТ -  это объект  базы данных,  который группирует  логически
        связанные  типы,  программные  объекты  и  подпрограммы  PL/SQL.
        Пакеты обычно состоят из двух частей, спецификации и тела,  хотя
        иногда  в  теле  нет  необходимости.   СПЕЦИФИКАЦИЯ пакета - это
        интерфейс с вашими приложениями; она объявляет типы, переменные,
        константы,  исключения,  курсоры  и  подпрограммы, доступные для
        использования  в  пакете.   ТЕЛО  пакета  полностью   определяет
        курсоры и подпрограммы, тем самым реализуя спецификацию пакета.

        В отличие от подпрограмм, пакеты нельзя вызывать, передавать  им
        параметры или вкладывать  их друг в  друга.  В остальном  формат
        пакета аналогичен формату подпрограммы:

        PACKAGE имя IS  -- спецификация (видимая часть)
            -- объявления общих типов и объектов
            -- спецификации подпрограмм
        END [имя];

        PACKAGE BODY имя IS  -- тело (скрытая часть)
            -- объявления личных типов и объектов
            -- тела подпрограмм
        [BEGIN
            -- предложения инициализации]
        END [имя];

        Спецификация содержит  ОБЩИЕ объявления,  которые видимы  вашему
        приложению.    Тело   содержит   детали   реализации   и  ЛИЧНЫЕ
        объявления, которые скрыты от вашего приложения.

        Представляйте себе спецификацию как функциональный интерфейс,  а
        тело - как "черный ящик" (см. рис.7-1).

Рис.7-1
Интерфейс пакета

          Приложение               Пакет                База данных
        -------------¬                                ----------------¬
        ¦            ¦                                ¦               ¦
        ¦            ¦        ---------------¬----\   ¦               ¦
        ¦            ¦------¦ Спецификация ¦      \ ¦               ¦
        ¦            ¦        +--------------+        \ -------¬      ¦
        ¦            ¦        ¦--------------¦        ¦ +------+      ¦
        ¦            ¦        ¦--- Тело -----¦        / ¦------¦      ¦
        L-------------        ¦--------------¦       /¦ L-------      ¦
                              L---------------------/ ¦               ¦
                                                      L----------------

        Вы можете  отлаживать, развивать  или заменять  тело пакета,  не
        изменяя интерфейса к этому телу (т.е. спецификации пакета).










7-2  Руководство пользователя и справочник по PL/SQL


        Пакеты создаются интерактивно в  SQL*Plus или SQL*DBA с  помощью
        команд  CREATE  PACKAGE  и  CREATE  PACKAGE  BODY.   В следующем
        примере  пакетируются  тип   записи,  курсор  и   две  процедуры
        управления кадрами:

        CREATE PACKAGE emp_actions AS  -- спецификация
            TYPE EmpRecTyp IS RECORD (emp_id INTEGER, salary REAL);
            CURSOR desc_salary (emp_id NUMBER) RETURN EmpRecTyp;

            PROCEDURE hire_employee
                (ename   CHAR,
                 job     CHAR,
                 mgr     NUMBER,
                 sal     NUMBER,
                 comm    NUMBER,
                 deptno  NUMBER);

            PROCEDURE fire_employee (emp_id NUMBER);
        END emp_actions;

        CREATE PACKAGE BODY emp_actions AS  -- тело
            CURSOR desc_salary (emp_id NUMBER) RETURN EmpRecTyp IS
                SELECT empno, sal FROM emp ORDER BY sal DESC;

            PROCEDURE hire_employee
                (ename   CHAR,
                 job     CHAR,
                 mgr     NUMBER,
                 sal     NUMBER,
                 comm    NUMBER,
                 deptno  NUMBER) IS
            BEGIN
                INSERT INTO emp VALUES (empno_seq.NEXTVAL, ename, job,
                    mgr, SYSDATE, sal, comm, deptno);
            END hire_employee;

            PROCEDURE fire_employee (emp_id NUMBER) IS
            BEGIN
                DELETE FROM emp WHERE empno = emp_id;
            END fire_employee;
        END emp_actions;

        Заметьте,     что     процедура     hire_employee     использует
        последовательность  базы  данных  empno_seq  и  функцию SYSDATE,
        чтобы вставлять  соответственно номер  нового сотрудника  и дату
        приема.

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












                                                             Пакеты  7-3


----------------

Преимущества пакетов

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

Модульность

        Пакеты позволяют вам  инкапсулировать логически связанные  типы,
        объекты и  подпрограммы в  поименованный модуль  PL/SQL.  Каждый
        пакет легко понять, а  интерфейсы между пакетами просты,  ясны и
        хорошо определены.  Это облегчает разработку приложений.

Облегчение проектирования

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

Скрытие информации

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

Расширенная функциональность

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

Улучшенная производительность

        Когда  вы  вызываете  пакетированную  подпрограмму первый раз, в
        память  загружается  весь  пакет.   Поэтому  последующие  вызовы
        других   подпрограмм   этого   пакета   не   требуют    операций
        ввода-вывода.

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



7-4  Руководство пользователя и справочник по PL/SQL


----------------

Спецификация пакета

        Спецификация  пакета  содержит  общие  объявления.   Сфера  этих
        объявлений локальна для  вашей схемы в  базе данных и  глобальна
        для самого пакета.  Таким образом, объявленные объекты  доступны
        из  вашего  приложения  и  из  любого  места  в пакете.  Рис.7-2
        иллюстрирует правила сферы для пакетов.

Рис.7-2
Сфера пакетов

       --
       ¦                        --
       ¦                        ¦                --
       ¦                        ¦                ¦  процедура
       ¦  спецификация объекта -+  тело пакета  -+  функция
       ¦                        ¦                ¦  процедура
       ¦                        ¦                L-
       ¦                        L-
       ¦
схема -+
       ¦
       ¦                        --
       ¦                        ¦                --
       ¦                        ¦                ¦  функция
       ¦  спецификация объекта -+  тело пакета  -+  функция
       ¦                        ¦                ¦  процедура
       ¦                        ¦                L-
       ¦                        L-
       ¦
       ¦  другие объекты
       ¦
       L-

        Спецификация перечисляет ресурсы пакета, доступные  приложениям.
        Она содержит всю  информацию, необходимую вашему  приложению для
        использования  этих  ресурсов.   Например,  следующее объявление
        показывает, что  функция с  именем fac  принимает один  аргумент
        типа INTEGER и возвращает значение типа INTEGER:

        FUNCTION fac (n INTEGER) RETURN INTEGER;  -- возвращает n!

        Это вся информация, необходимая  вам для вызова данной  функции.
        Вам  нет  необходимости  рассматривать  фактическую   реализацию
        функции fac (например, итеративна она или рекурсивна).
















                                                             Пакеты  7-5


        Только подпрограммы и курсоры имеют реализацию, или ОПРЕДЕЛЕНИЕ.
        Поэтому,   если   спецификация   пакета   объявляет   лишь типы,
        константы,  переменные  и  исключения,  тело  пакета  не  нужно.
        Приведем пример такого пакета:

        -- пакет, состоящий только из спецификации
        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;

        Пакет  trans_data  не  нуждается   в  теле,  потому  что   типы,
        константы, переменные и исключения не требуют реализации.  Такие
        пакеты  позволяют  вам  определять  глобальные  переменные - для
        использования подпрограммами и  триггерами - которые  существуют
        на протяжении всей сессии.

Обращение к содержимому пакета
------------------------------

        Для обращения к типам,  объектам и подпрограммам, объявленным  в
        спецификации пакета, используются квалифицированные ссылки:

        имя_пакета.имя_типа
        имя_пакета.имя_объекта
        имя_пакета.имя_подпрограммы

        Вы  можете  обращаться  к  содержимому  пакета из триггеров базы
        данных, хранимых подпрограмм, встроенных блоков PL/SQL, а  также
        анонимных блоков PL/SQL, посылаемых в ORACLE интерактивно  через
        SQL*Plus  или  SQL*DBA.   В  следующем  примере вы обращаетесь к
        пакетированной переменной  miminum_balance, которая  объявлена в
        пакете trans_data:

        DECLARE
            new_balance  REAL;
            ...
        BEGIN
            ...
            IF new_balance < trans_data.minimum_balance THEN
                ...
            END IF;
            ...











7-6  Руководство пользователя и справочник по PL/SQL


----------------

Тело пакета

        Тело  пакета  реализует   спецификацию  пакета.   Оно   содержит
        определения   всех   курсоров   и   подпрограмм,   объявленных в
        спецификации  пакета.   Не  забывайте,  что  любая подпрограмма,
        определенная в  теле пакета,  доступна извне  пакета лишь  в том
        случае,  если  ее  спецификация  также появляется в спецификации
        пакета.

        Тело  пакета  может  также  содержать личные объявления, которые
        определяют  типы  и  объекты,  необходимые для внутренней работы
        пакета.  Сфера таких объявлений локальна в теле пакета.  Поэтому
        объявленные здесь  типы и  объекты недоступны  нигде, кроме тела
        пакета.  В отличие  от спецификации пакета,  декларативная часть
        тела пакета может содержать тела подпрограмм.

        За   декларативной   частью   тела   пакета   может    следовать
        необязательная  часть  инициализации,  которая  обычно  содержит
        предложения,  инициализирующие  некоторые  из  переменных, ранее
        объявленных в пакете.

        Часть инициализации пакета не играет большой роли, потому что, в
        отличие от подпрограмм, пакет нельзя вызывать или передавать ему
        параметры.     Следовательно,    часть    инициализации   пакета
        отрабатывает лишь один раз, при первом обращении к пакету.




































                                                             Пакеты  7-7


----------------

Несколько примеров

        Рассмотрим следующий пакет  с именем emp_actions.   Спецификация
        этого пакета объявляет следующие типы, объекты и подпрограммы:

            *  типы EmpRecTyp и DeptRecTyp

            *  функции hire_employee, nth_highest_salary и rank

            *  процедуры fire_employee и raise_salary

            *  курсор desc_salary

            *  исключение salary_missing

        После создания этого пакета вы можете разрабатывать  приложения,
        которые используют его типы, вызывают его подпрограммы, работают
        с его курсором или возбуждают его исключение.  Когда вы создаете
        пакет (CREATE), он сохраняется  в базе данных ORACLE  для общего
        пользования.

        PACKAGE emp_actions IS
            /* Объявить внешне видимые типы, курсор, исключение. */
            TYPE EmpRecTyp IS RECORD (emp_id INTEGER, salary REAL);
            TYPE DeptRecTyp IS RECORD (dept_id INTEGER, location CHAR);
            CURSOR desc_salary (emp_id NUMBER) RETURN EmpRecTyp;
            salary_missing  EXCEPTION;
            /* Объявить внешне вызываемые подпрограммы. */
            FUNCTION hire_employee
                (ename   CHAR,
                 job     CHAR,
                 mgr     INTEGER,
                 sal     NUMBER,
                 comm    NUMBER,
                 deptno  INTEGER) RETURN INTEGER;
            PROCEDURE fire_employee (emp_id INTEGER);
            PROCEDURE raise_salary (emp_id INTEGER, increase NUMBER);
            FUNCTION nth_highest_salary (n INTEGER) RERURN EmpRecTyp;
        END emp_actions;






















7-8  Руководство пользователя и справочник по PL/SQL


        PACKAGE BODY emp_actions IS
            number_hired  INTEGER;  -- видна только в этом пакете

            /* Определить курсор, объявленный в пакете. */
            CURSOR desc_salary (emp_id NUMBER) RETURN EmpRecTyp IS
                SELECT empno, sal FROM emp ORDER BY sal DESC;

            /* Определить подпрограммы, объявленные в пакете. */

            FUNCTION hire_employee
                (ename   CHAR,
                 job     CHAR,
                 mgr     INTEGER,
                 sal     NUMBER,
                 comm    NUMBER,
                 deptno  INTEGER) RETURN INTEGER IS
                new_empno  INTEGER;
            BEGIN
                SELECT empno_seq.NEXTVAL INTO new_empno FROM DUAL;
                INSERT INTO emp VALUES (new_empno, ename, job,
                    mgr, SYSDATE, sal, comm, deptno);
                number_hired := number_hired + 1;
                RETURN(new_empno);
            END hire_employee;

            PROCEDURE fire_employee (emp_id INTEGER) IS
            BEGIN
                DELETE FROM emp WHERE empno = emp_id;
            END fire_employee;

            PROCEDURE raise_salary (emp_id INTEGER, increase NUMBER) IS
                current_salary  NUMBER;
            BEGIN
                SELECT sal INTO current_salary FROM emp
                    WHERE empno = emp_id;
                IF current_salary IS NULL THEN
                    RAISE salary_missing;
                ELSE
                    UPDATE emp SET sal = sal + increase
                        WHERE empno = emp_id;
                END IF;
            END raise_salary;





















                                                             Пакеты  7-9


            FUNCTION nth_highest_salary (n INTEGER) RETURN EmpRecTyp IS
                emp_rec  EmpRecTyp;
            BEGIN
                OPEN desc_salary;
                FOR i IN 1..n LOOP
                    FETCH desc_salary INTO emp_rec;
                    EXIT WHEN desc_salary%NOTFOUND;
                END LOOP;
                CLOSE desc_salary;
                RETURN (emp_rec);
            END nth_highest_salary;

            /* Определить локальные функции, доступные лишь в пакете. */
            FUNCTION rank (emp_id INTEGER, job_title CHAR)
                RETURN INTEGER IS
            /* Возвращает ранг (высший = 1) сотрудника при данной *
             * должности на основе рейтинга производительности.   */
                head_count  INTEGER;
                score       NUMBER;
            BEGIN
                SELECT COUNT(*) INTO head_count FROM emp
                    WHERE job = job_title;
                SELECT rating INTO score FROM reviews
                    WHERE empno = emp_id;
                score := score / 100;  -- максимальный рейтинг = 100
                RETURN (head_count + 1) - ROUND(head_count * score);
            END rank;
        BEGIN  -- здесь начинается часть инициализации пакета
            INSERT INTO emp_audit VALUES (SYSDATE, USER, 'EMP_ACTIONS');
            number_hired := 0;
        END emp_actions;

        Не забывайте,  что часть  инициализации пакета  выполняется лишь
        один раз, при первом  обращении к пакету.  Поэтому,  в последнем
        примере, в аудиторскую  таблицу emp_audit будет  вставлена всего
        одна     строка.      Аналогично,     переменная    number_hired
        инициализируется  лишь  однажды.   При  каждом  вызове процедуры
        hire_employee  переменная   number_hired  обновляется.    Однако
        счетчик,  поддерживаемый  этой  переменной,  ведется для сессии.
        Это   значит,   что   он   отражает   число   новых сотрудников,
        обработанных  одним  пользователем,  но  НЕ  общее  число  вновь
        принятых сотрудников.





















7-10  Руководство пользователя и справочник по PL/SQL


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

        PACKAGE bank_transactions IS
            /* Объявить внешне видимую константу. */
            minimum_balance  CONSTANT NUMBER := 100.00;
            /* Объявить внешне вызываемые процедуры. */
            PROCEDURE apply_transactions;
            PROCEDURE enter_transaction
                (acct    NUMBER,
                 kind    CHAR,
                 amount  NUMBER);
        END bank_transactions;

        PACKAGE BODY bank_transactions IS
            /* Объявить глобальную перем. для состояния транзакции. */
            new_status  VARCHAR2(70) := 'Unknown';

            /* Использовать упреждающие объявления, потому что
               apply_transactions вызывает credit_account и
               debit_account, которые еще не объявлены к моменту
               вызова. */

            PROCEDURE credit_account (acct NUMBER, credit REAL);
            PROCEDURE debit_account (acct NUMBER, debit REAL);

            /* Определить процедуры, специфицированные в пакете. */

            PROCEDURE apply_transactions IS
            /* Применить транзакции, ожидающие в таблице транзакций, *
             * к банковским счетам. Использовать курсор.             */
                CURSOR trans_cursor IS
                    SELECT acct_id, kind, amount FROM transactions
                        WHERE status = 'Pending'
                        ORDER BY time_tag
                        FOR UPDATE OF status;  -- для блокировки строк
            BEGIN
                FOR trans IN trans_cursor LOOP
                    IF trans.kind = 'D' THEN
                        debit_account(trans.acct_id, trans.amount);
                    ELSIF trans.kind = 'C' THEN
                        crebit_account(trans.acct_id, trans.amount);
                    ELSE
                        new_status := 'Rejected';
                    END IF;
                    UPDATE transactions SET status = new_status
                        WHERE CURRENT OF trans_cursor;
                END LOOP;
            END apply_transactions;











                                                            Пакеты  7-11


            PROCEDURE enter_transaction
            /* Добавить транзакцию в таблицу транзакций. */
                    (acct    NUMBER,
                     kind    CHAR,
                     amount  NUMBER) IS
            BEGIN
                INSERT INTO transactions
                    VALUES (acct, kind, amount, 'Pending', SYSDATE);
            END enter_transaction;

            /* Определить локальные процедуры, доступные только *
             * внутри пакета.                                   */

            PROCEDURE do_journal_entry
            /* Записать транзакцию в журнал. */
                (acct     NUMBER,
                 kind     CHAR,
                 new_bal  NUMBER) IS
            BEGIN
                INSERT INTO journal
                    VALUES (acct, kind, new_bal, sysdate);
                IF kind = 'D' THEN
                    new_status := 'Debit applied';
                ELSE
                    new_status := 'Credit applied';
                END IF;
            END do_journal_entry;

            PROCEDURE credit_account (acct NUMBER, credit REAL) IS
            /* Кредитовать счет, если номер счета действителен. */
                old_balance  NUMBER;
                new_balance  NUMBER;
            BEGIN
                SELECT balance INTO old_balance FROM accounts
                    WHERE acct_id = acct
                    FOR UPDATE OF balance;  -- чтобы заблокировать стр.
                new_balance := old_balance + credit;
                UPDATE accounts SET balance = new_balance
                    WHERE acct_id = acct;
                do_journal_entry(acct, 'C', new_balance);
            EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    new_status := 'Bad account number';
                WHEN OTHERS THEN
                    new_status := SUBSTR(SQLERRM,1,70);
            END credit_account;

















7-12  Руководство пользователя и справочник по PL/SQL


            PROCEDURE debit_account (acct NUMBER, debit REAL) IS
            /* Дебитовать счет, если номер счета действителен, *
             * и на счете достаточная сумма.                   */
                old_balance         NUMBER;
                new_balance         NUMBER;
                insufficient_funds  EXCEPTION;
            BEGIN
                SELECT balance INTO old_balance FROM accounts
                    WHERE acct_id = acct
                    FOR UPDATE OF balance;  -- чтобы заблокировать стр.
                new_balance := old_balance - debit;
                IF new_balance >= minimum_balance THEN
                    UPDATE accounts SET balance = new_balance
                        WHERE acct_id = acct;
                    do_journal_entry(acct, 'D', new_balance);
                ELSE
                    RAISE insufficient_funds;
                END IF;
            EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    new_status := 'Bad account number';
                WHEN OTHERS THEN
                    new_status := SUBSTR(SQLERRM,1,70);
            END debit_account;
        END bank_transactions;  -- часть инициализации не нужна

        В этом пакете часть инициализации не используется.




































                                                            Пакеты  7-13


----------------

Сравнение личных и общих объектов

        Посмотрим еще раз на  пакет emp_actions.  Тело пакета  объявляет
        переменную  с  именем  number_hired,  которая   инициализируется
        нулевым  значением.   В  отличие  от  элементов,  объявленных  в
        спецификации  пакета  emp_actions,  элементы,  объявленные в его
        теле, могут использоваться только внутри пакета.  Следовательно,
        код  PL/SQL  вне  пакета   не  может  обращаться  к   переменной
        number_hired.  Такие элементы называются ЛИЧНЫМИ.

        Однако элементы, объявленные в спецификации пакета  emp_actions,
        такие  как  исключение  salary_missing,  являются  видимыми  вне
        пакета.  Следовательно, код PL/SQL вне пакета может обращаться к
        переменной salary_missing.  Такие элементы называются ОБЩИМИ.

        Если вам надо  поддерживать какие-нибудь элементы  на протяжении
        всей сессии или между транзакциями, помещайте их в декларативную
        часть тела пакета.   Например, значение переменной  number_hired
        остается неизменным между вызовами hire_employee.  Не забывайте,
        однако, что  это значение  number_hired индивидуально  для вашей
        сессии.

        Если,  помимо  этого,  вы  должны  сделать какие-нибудь элементы
        общими, помещайте их в спецификацию пакета.  Например, константа
        minimum_balance,    объявленная     в    спецификации     пакета
        bank_transactions, доступн для общего использования.


----------------

Перекрытие имен

        Вспомните из главы 6,  что PL/SQL позволяет двум  или нескольким
        пакетированным  подпрограммам  иметь  одно  и  то  же  имя.  Эта
        возможность полезна, когда  вы хотите, чтобы  подпрограмма могла
        принимать   параметры,   принадлежащие   разным   типам  данных.
        Например,  следующий  пакет  определяет  две  процедуры с именем
        journalize:

        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;











7-14  Руководство пользователя и справочник по PL/SQL


            PROCEDURE journalize (amount NUMBER, trans_date NUMBER) IS
            BEGIN
                INSERT INTO journal
                    VALUES (amount, TO_DATE(trans_date, 'J'));
            END journalize;
        END journal_entries;

        Первая процедура принимает дату trans_date как строку  символов,
        тогда как вторая  - как число  (юлианский день).  Тем  не менее,
        обе процедуры обрабатывают эти данные как следует.


----------------

Вызов пакетированных подпрограмм

        Пакетированные   подпрограммы   должны   вызываться   с  помощью
        квалифицированной ссылки, как показывает следующий пример:

        emp_actions.hire_employee(name, title, ...);

        Это указывает компилятору PL/SQL, что hire_employee находится  в
        пакете emp_actions.

        Вы можете вызывать пакетированные подпрограммы из триггера  базы
        данных, другой хранимой подпрограммы, приложения  прекомпилятора
        ORACLE, приложения  OCI, или  из инструмента  ORACLE, такого как
        SQL*Plus.  Ниже приведены некоторые примеры.

Из другой хранимой подпрограммы

        Хранимая    подпрограмма    может    вызывать     пакетированную
        подпрограмму.   Например,   в  независимой   подпрограмме  может
        появиться    следующий     вызов    пакетированной     процедуры
        hire_employee:

        emp_actions.hire_employee(name, title, ...);

Из прикладной программы

        Приложение  прекомпилятора  или  приложение  OCI  может вызывать
        хранимые подпрограммы из  анонимных блоков PL/SQL.   В следующем
        примере вы вызываете  пакетированную процедуру hire_employee  из
        программы прекомпилятора ORACLE:

        EXEC SQL EXECUTE
            BEGIN
                emp_actions.hire_employee(name, title, ...);
            END;
        END-EXEC;

        Фактические параметры name  и title -  это хост-переменные.











                                                            Пакеты  7-15


Из инструмента ORACLE

        Вы можете вызывать  пакетированные подпрограммы интерактивно  из
        инструментов ORACLE, таких как SQL*Plus, SQL*Forms или  SQL*DBA.
        Например,  из  SQL*Plus  вы  могли  бы  вызвать   пакетированную
        процедуру hire_employee следующим образом:

        SQL> EXECUTE emp_actions.hire_employee('TATE', 'CLERK', ...);

Предостережение
---------------

        Перед    выполнением    пакетированной    подпрограммы    ORACLE
        устанавливает  неявную  точку  сохранения.   Если   подпрограмма
        сбивается  в   результате  необработанного   исключения,  ORACLE
        осуществляет  откат   к  этой   точке  сохранения.    Тем  самым
        отменяется вся работа, проделанная подпрограммой.

Ограничение
-----------

        Тело   пакетированной   подпрограммы   может   содержать   любое
        предложение SQL или PL/SQL.  Однако подпрограммы, участвующие  в
        распределенной транзакции, триггерах  базы данных и  приложениях
        SQL*Forms,   не   могут   вызывать   пакетированных подпрограмм,
        содержащих предложения COMMIT, ROLLBACK или SAVEPOINT.

Удаленный доступ
----------------

        Для вызова пакетированных подпрограмм, хранимых в удаленной базе
        данных ORACLE, используйте следующий синтаксис:

        имя_пакета.имя_подпрограммы@связьБД(параметр1, параметр2, ...);

        В   следующем   примере   вызывается   пакетированная  процедура
        hire_employee, определенная в  пакете emp_actions в  базе данных
        newyork:

        emp_actions.hire_employee@newyork(name, title, ...);

Состояния пакетов и зависимости
-------------------------------

        Спецификация пакета всегда находится в одном из двух  состояний:
        действительна   или   недействительна.    Спецификация    пакета
        ДЕЙСТВИТЕЛЬНА, если ни ее исходный код, ни любой из объектов,  к
        которым  она  обращается,  не  был  ни удален (DROP), ни заменен
        (REPLACE),  ни  изменен  (ALTER)  с момента последней компиляции
        этой спецификации.
        С другой стороны,  спецификация пакета НЕДЕЙСТВИТЕЛЬНА,  если ее
        исходный код или  любой из объектов,  к которым она  обращается,
        был либо  удален (DROP),  либо заменен  (REPLACE), либо  изменен
        (ALTER) с момента последней компиляции этой спецификации.  Когда
        ORACLE  помечает  спецификацию  пакета  как недействительную, он
        также помечает как недействительные все объекты, обращающиеся  к
        этому пакету.

        К телу  пакета применяются  те же  правила, с  той разницей, что
        ORACLE  может  перекомпилировать  тело  пакета,  НЕ  помечая его
        спецификацию  как  недействительную.   Эта возможность позволяет
        ограничить каскад перекомпиляций, вызываемых зависимостями.

7-16  Руководство пользователя и справочник по PL/SQL


Характеристики сессии
---------------------

        Переменные,  константы  и  курсоры,  объявленные в пакете, имеют
        следующие уникальные характеристики:

            *  Каждая сессия  имеет свой  собственный набор  переменных,
               констант и курсоров из данного пакета.

            *  В  данной  сессии,  при  первом  обращении  к  пакету его
               переменные и  параметры курсоров  имеют пустые  значения,
               если вы не инициализируете их.

            *  В  течение  сессии  пользователь  пакета  может  изменить
               значения переменных и параметров курсоров в пакете.   При
               окончании  сессии  эти  значения  теряются  и должны быть
               повторно  инициализированы,  когда  начинается  очередная
               сессия.

        Объявляйте переменные и  курсоры в пакете  лишь тогда, когда  вы
        хотите, чтобы их существование длилось все время сессии.

Зависимости
-----------

        Когда  спецификация  пакета  перекомпилируется,  ORACLE помечает
        зависимые  объекты  как  недействительные.   К  таким   объектам
        относятся независимые  или пакетированные  подпрограммы, которые
        вызывают   или    обращаются   к    объектам,   объявленным    в
        перекомпилируемой спецификации  пакета.  Когда  вы вызовете  или
        обратитесь  к  зависимому,   но  еще  не   перекомпилированному,
        объекту,  ORACLE  автоматически  перекомпилирует  его  во  время
        выполнения.

        Когда   перекомпилируется   тело   пакета,   ORACLE  определяет,
        действительны ли  объекты, от  которых зависит  тело пакета.   К
        таким объектам относятся независимые подпрограммы и спецификации
        пакетов,  к  которым   существуют  обращения  из   процедур  или
        курсоров, определенных  в перекомпилируемом  теле пакета.   Если
        среди    таких    объектов    есть    недействительные,   ORACLE
        перекомпилирует их,  прежде чем  перекомпилировать тело  пакета.
        Если  все   перекомпиляции  успешны,   тело  пакета   становится
        действительным.   В  противном  случае  ORACLE возвращает ошибку
        выполнения,  а  тело  пакета  остается недействительным.  Ошибки
        компиляции сохраняются в словаре данных вместе с пакетом.

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

        Для дополнительной информации о зависимостях пакетов  обратитесь
        к документу ORACLE7 Server Application Developer's Guide.








                                                            Пакеты  7-17


----------------

Пакет STANDARD

        Пакет   с   именем   STANDARD   определяет   окружение   PL/SQL.
        Спецификация этого пакета глобально объявляет типы, исключения и
        подпрограммы,  автоматически  доступные  любой программе PL/SQL.
        Например, в  пакете STANDARD  объявлена встроенная  функция ABS,
        которая возвращает абсолютное значение своего аргумента:

        FUNCTION ABS (n NUMBER) RETURN NUMBER;

        Содержимое  пакета  STANDARD  видно приложениям непосредственно,
        т.е.  не  требует  квалифицированных  ссылок.   Так,  вы  можете
        вызывать  функцию  ABS  из  триггера  базы  данных,  их хранимой
        подпрограммы, из приложения прекомпилятора ORACLE, из приложения
        OCI,  а  также  из  разнообразных  инструментов  ORACLE, включая
        SQL*Forms, SQL*Menu, SQL*Plus и SQL*ReportWriter.

        Если вы переобъявите ABS  в программе PL/SQL, то  ваше локальное
        объявление   перекроет   глобальное   объявление.    Однако   вы
        по-прежнему можете  вызывать эту  встроенную функцию,  используя
        квалифицированную ссылку:

        ... STANDARD.ABS(x) ...

        Большинство  встроенных  функций  имеют  перекрывающиеся  имена.
        Так, в пакете STANDARD содержатся следующие объявления:

        FUNCTION TO_CHAR (right DATE) RETURN VARCHAR2;
        FUNCTION TO_CHAR (left NUMBER) RETURN VARCHAR2;
        FUNCTION TO_CHAR (left DATE, right VARCHAR2) RETURN VARCHAR2;
        FUNCTION TO_CHAR (left NUMBER, right VARCHAR2) RETURN VARCHAR2;

        PL/SQL разрешает обращения к TO_CHAR сопоставлением количества и
        типоа данных фактических и формальных параметров.



























7-18  Руководство пользователя и справочник по PL/SQL


----------------

Пакеты, специфичные для продуктов

        Чтобы помочь  вам создавать  приложения, опирающиеся  на PL/SQL,
        сервер ORACLE и некоторые инструменты ORACLE поставляют  пакеты,
        содержащие подпрограммы, специфичные для тех или иных продуктов.
        Например, SQL*Forms предоставляет пакет с именем SQLFORMS.   Для
        информации о пакетах, ориентированных на продукты, обратитесь  к
        справочному руководству по соответствующему продукту.


Окружение ORACLE
----------------

        Важным клиентом PL/SQL является  сервер ORACLE.  Пакет с  именем
        DBMS_STANDARD  предоставляет  средства  языка,  которые помогают
        вашему приложению  взаимодействовать с  ORACLE.  Например,  этот
        пакет предоставляет  процедуру raise_application_error,  которая
        позволяет вам  выдавать пользовательские  сообщения об  ошибках.
        Таким образом, вы можете возвращать ошибки приложению и избегать
        возврата  необработанных  исключений.   Синтаксис  вызова   этой
        процедуры имеет вид

        raise_application_error(номер_ошибки, сообщение_об_ошибке);

        где   номер_ошибки   -    отрицательное   число   в    интервале
        -20000..-20999, а сообщение_об_ошибке  - строка символов  длиной
        до  512  символов.   Пакет  DBMS_STANDARD  является  расширением
        пакета STANDARD, так что вам не требуется использовать для  него
        квалифицированные ссылки.

        Приложение  может  вызывать  процедуру   raise_application_error
        только из выполняющейся хранимой подпрограммы.  При своем вызове
        процедура   raise_application_error   завершает    подпрограмму,
        выполняет  откат  всех  сделанных  ей  изменений  и   возвращает
        приложению определенные пользователем номер ошибки и  сообщение,
        которые  могут  перехватываться  пользователем  как любая ошибка
        ORACLE.  Например:

        PROCEDURE raise_salary (emp_id NUMBER, increase REAL) IS
            current_salary  NUMBER;
        BEGIN
            SELECT sal INTO current_salary FROM emp
                WHERE empno = emp_id;
            IF current_salary IS NULL THEN
                raise_application_error(-20101, 'Salary is missing');
            ELSE
                UPDATE emp SET sal = sal + increase
                    WHERE empno = emp_id;
            END IF;
        END raise_salary;











                                                            Пакеты  7-19


        Вызывающее приложение  получает исключение  PL/SQL, которое  оно
        может  обработать  с  помощью   функций  SQLCODE  и  SQLERRM   в
        обработчике  исключений  OTHERS.   Более  того, приложение может
        использовать    прагму    EXCEPTION_INIT,    чтобы   сопоставить
        специфические    номера    ошибок,    возвращаемые    процедурой
        raise_application_error,    своим    собственным    исключениям,
        например:

        EXEC SQL EXECUTE
            DECLARE
                ...
                null_salary  EXCEPTION;
                PRAGMA EXCEPTION_INIT(null_salary, -20101);
            BEGIN
                ...
                raise_salary(:emp_number, :amount);
            EXCEPTION
                WHEN null_salary THEN
                   INSERT INTO emp_audit VALUES (:emp_number, ...);
                ...
            END;
        END-EXEC;

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

        Как  правило,  процедура  raise_application_error используется в
        триггерах  базы  данных.   За  примером  обратитесь  к   разделу
        "Триггеры базы данных" в главе 4.


----------------

Рекомендации

        При написании пакетов  старайтесь делать их  максимально общими,
        чтобы  их  можно  было  использовать  в будущих приложениях.  Не
        стоит  писать  пакеты,  повторяющие  те возможности, которые уже
        предоставлены ORACLE.

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

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










7-20  Руководство пользователя и справочник по PL/SQL