Руководство администратора Справочник по PL/SQL Руководство разработчика приложений Разные статьи для разработчика

ГЛАВА 5

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

        ОБРАБОТКА ОШИБОК


                      Нет ничего приятнее, чем когда в тебя промахнутся.
                                                        Уинстон Черчилль


        Ошибки   времени    выполнения   происходят    из-за   просчетов
        проектирования,  ошибок  кодирования,  сбоев  оборудования  и по
        многим  другим   причинам.   Хотя   невозможно  предвидеть   все
        возможные  ошибки,  вы  можете  спланировать обработку некоторых
        видов ошибок, могущих возникнуть в вашей программе PL/SQL.

        В большинстве языков программирования ошибка времени выполнения,
        такая  как  ПЕРЕПОЛНЕНИЕ  СТЕКА  или  ДЕЛЕНИЕ  НА  0, если вы не
        предусмотрели  ее  обработку,  прекращает  нормальную  работу  и
        возвращает  управление  в  операционную  систему.  В PL/SQL есть
        специальный механизм, называемый ОБРАБОТКОЙ ИСКЛЮЧЕНИЙ,  который
        позволяет  вам  "защитить"  вашу  программу  так, что она сможет
        продолжать выполнение даже при наличии ошибок.







































                                                   Обработка ошибок  5-1


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

Общие сведения

        В  PL/SQL  условие  ошибки  называется  ИСКЛЮЧЕНИЕМ.  Исключения
        могут  быть  внутренне  определены  (исполнителем  PL/SQL)   или
        определены пользователем.

        Примеры внутренне определенных  исключений включают "ДЕЛЕНИЕ  НА
        0" и "НЕХВАТКУ ПАМЯТИ".  Некоторые общие внутренне  определенные
        исключения имеют предопределенные имена, такие как ZERO_DIVIDE и
        STORAGE_ERROR.  Другим внутренне определенным исключениям  имена
        могут быть присвоены.

        Вы можете определить ваши собственные исключения в декларативной
        части либого блока,  подпрограммы или пакета  PL/SQL.  Например,
        вы можете определить исключение с именем insufficient_funds  для
        обработки банковских счетов,  превысивших кредит.  В  отличие от
        внутренних исключений, пользовательские исключения ДОЛЖНЫ  иметь
        имена.

        Когда возникает ошибка, соответствующее исключение ВОЗБУЖДАЕТСЯ.
        Это  значит,  что   нормальное  выполнение  останавливается,   и
        управление передается на часть обработки исключений вашего блока
        или  подпрограммы  PL/SQL.   Внутренние  исключения возбуждаются
        неявно  (автоматически)  системой  исполнения;  пользовательские
        исключения  возбуждаются  явно,  посредством  предложений RAISE,
        которые могут также возбуждать предопределенные исключения.

        Для  обработки  возбуждаемых  исключений  вы  пишете   отдельные
        программы,   называемые    ОБРАБОТЧИКАМИ   ИСКЛЮЧЕНИЙ.     После
        выполнения  обработчика  исключений  исполнение  текущего  блока
        заканчивается, и окружающий  блок продолжает свое  выполнение со
        следующего  предложения.   Если  окружающего  блока  нет   (т.е.
        текущий  блок   не  вложен   в  другой   блок),  то   управление
        возвращается в хост-окружение.



























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

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

        DECLARE
            pe_ratio  NUMBER(3,1);
        BEGIN
            ...
            SELECT price / earnings INTO pe_ratio FROM stocks
                WHERE symbol = 'XYZ';
                -- может вызвать ошибку "деление на 0"
            INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio);
            COMMIT;
        EXCEPTION  -- здесь начинаются обработчики исключений
            WHEN ZERO_DIVIDE THEN  -- обрабатывает "деление на 0"
                INSERT INTO stats (symbol, ratio) VALUES ('XYZ', NULL);
                COMMIT;
            ...
            WHEN OTHERS THEN  -- обрабатывает все прочие ошибки
                ROLLBACK;
        END;  -- здесь заканчиваются обработчики исключений и весь блок

        Необязательный обработчик  OTHERS перехватывает  все исключения,
        персонально не перечисленные в блоке.

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

        INSERT INTO stats (symbol, ratio)
            SELECT symbol, DECODE(earnings, 0, NULL, price / earnings)
            FROM stocks
            WHERE symbol = 'XYZ';

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























                                                   Обработка ошибок  5-3


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

Преимущества исключений

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

        BEGIN
            SELECT ...
                -- проверить на ошибку 'no data found'
            SELECT ...
                -- проверить на ошибку 'no data found'
            SELECT ...
                -- проверить на ошибку 'no data found'
        END;

        Обработка ошибок  при этом  не является  ни четко  отделенной от
        нормальной  обработки,  ни  концептуально  осмысленной.  Если вы
        пренебрежете   проверкой   в   одном   месте,   ошибка   пройдет
        необнаруженной  и  может  вызвать  другие,  внешне  не связанные
        ошибки.

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

        BEGIN
            SELECT ...
            SELECT ...
            SELECT ...
        EXCEPTION
            WHEN NO_DATA_FOUND THEN  -- этот обработчик перехватывает
                                     -- все ошибки 'no data found'
        END;

        Заметьте, как  исключения улучшают  читабельность, позволяя  вам
        изолировать программы  обработки ошибок.   Основной алгоритм  не
        затемняется алгоритмами восстановления от ошибок.

        Исключения  также  повышают  надежность.   Вам нет необходимости
        беспокоиться  о  проверке  ошибки  в  любой точке, где она может
        возникнуть.    Просто   добавьте   соответствующий    обработчик
        исключений в ваш блок PL/SQL.  Если данное исключение когда-либо
        будет возбуждено в  этом блоке (или  любом его подблоке),  у вас
        есть гарантия, что оно будет обработано.

















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

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

Предопределенные исключения

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

        Для обработки других ошибок ORACLE вы можете использовать  общий
        обработчик  OTHERS.   Функции  сообщений  об  ошибках  SQLCODE и
        SQLERRM  особенно  полезны  в  обработчике  OTHERS,  так как они
        возвращают  код  ошибки  ORACLE  и  текст  сообщения  об ошибке.
        Альтернативно,  вы  можете  использовать  прагму EXCEPTION_INIT,
        чтобы ассоциировать имена  исключений с номерами  ошибок ORACLE.
        Подробнее   об   этом   см.   в   разделе  раздел "Использование
        EXCEPTION_INIT" ниже в этой главе.

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

        Имя исключения                  Ошибка ORACLE   Код SQLCODE
        ----------------------------------------------------------------
        CURSOR_ALREADY_OPEN             ORA-06511       -6511
        DUP_VAL_ON_INDEX                ORA-00001       -1
        INVALID_CURSOR                  ORA-01001       -1001
        INVALID_NUMBER                  ORA-01722       -1722
        LOGIN_DENIED                    ORA-01017       -1017
        NO_DATA_FOUND                   ORA-01403       +100
        NOT_LOGGED_ON                   ORA-01012       -1012
        PROGRAM_ERROR                   ORA-06501       -6501
        STORAGE_ERROR                   ORA-06500       -6500
        TIMEOUT_ON_RESOURCE             ORA-00051       -51
        TOO_MANY_ROWS                   ORA-01422       -1422
        TRANSACTION_BACKED_OUT          ORA-00061       -61
        VALUE_ERROR                     ORA-06502       -6502
        ZERO_DIVIDE                     ORA-01476       -1476


















                                                   Обработка ошибок  5-5


        Ниже приведены краткие описания предопределенных исключений:

CURSOR_ALREADY_OPEN

        Возбуждается при попытке открыть уже открытый курсор.  Вы должны
        закрыть (CLOSE) курсор, прежде чем открывать его повторно.

        Курсорный цикл FOR автоматически открывает свой курсор.  Поэтому
        вы не можете войти в такой цикл, если данный курсор уже  открыт.
        Нельзя также явно открывать курсор внутри цикла FOR.

DUP_VAL_ON_INDEX

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

INVALID_CURSOR

        Возбуждается,  когда  вызов  PL/SQL  специфицирует  некорректный
        курсор (например, при попытке закрыть неоткрытый курсор).

INVALID_NUMBER

        Возбуждается в предложении SQL, когда преобразование  символьной
        строки  в  число  сбивается  из-за  того, что строка не содержит
        правильного    представления    числа.     Например,   следующее
        предложение  INSERT  возбудит  исключение  INVALID_NUMBER, когда
        ORACLE попытается преобразовать 'HALL' в число:

        INSERT INTO emp (empno,ename,deptno) VALUES ('HALL', 7888, 20);

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

LOGIN_DENIED

        Возбуждается при некорректном имени пользователя или пароле  при
        попытке подключения к ORACLE.

NO_DATA_FOUND

        Возбуждается,  когда  предложение  SELECT  INTO не возвращает ни
        одной строки,  или при  обращении к  неинициализированной строке
        таблицы PL/SQL.

        Групповые функции SQL, такие как AVG или SUM, ВСЕГДА  возвращают
        значение, даже если это значение есть NULL.  Поэтому предложение
        SELECT INTO, вызывающее  групповую функцию, никогда  не возбудит
        исключение NO_DATA_FOUND.

        Поскольку NO_DATA_FOUND  возбуждается, когда  предложение SELECT
        INTO  не   возвращает  строк,   вы  можете   проверять  значение
        SQL%NOTFOUND только в обработчике исключений.  Однако,  значение
        атрибута  имя_курсора%NOTFOUND  можно  проверять  после   каждой
        операции FETCH.  От операции FETCH ожидается, что в конце концов
        она не сможет  возвратить очередную строку,  так что, когда  это
        происходит, никакого исключения не возбуждается.






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

NOT_LOGGED_ON

        Возбуждается, когда ваша  программа PL/SQL выдает  вызов ORACLE,
        не будучи подключена к ORACLE.

PROGRAM_ERROR

        Возбуждается, когда PL/SQL встретился с внутренней проблемой.

STORAGE_ERROR

        Возбуждается, когда PL/SQL исчерпал доступную память, или  когда
        память запорчена.

TIMEOUT_ON_RESOURCE

        Возбуждается при  возникновении таймаута,  когда ORACLE  ожидает
        ресурса.

TOO_MANY_ROWS

        Возбуждается, когда  предложение SELECT  INTO возвращает  больше
        одной строки.

TRANSACTION_BACKED_OUT

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

VALUE_ERROR

        Возбуждается  при  возникновении  арифметической  ошибки, ошибки
        преобразования,   ошибки   усечения   или   ошибки  ограничения.
        Например,  VALUE_ERROR  возбуждается  при  усечении   строкового
        значения,   присваиваемого   переменной   PL/SQL.    (Однако при
        усечении  строкового  значения,  присваиваемого хост-переменной,
        никакого исключения не возбуждается.)

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

        DECLARE
            my_empno  NUMBER(4);
            my_ename  CHAR(10);
        BEGIN
            my_empno := 'HALL';
            ...

        В предложениях SQL в таких случаях возбуждается INVALID_NUMBER.

ZERO_DIVIDE

        Возбуждается при попытке деления числа на 0.





                                                   Обработка ошибок  5-7


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

Пользовательские исключения

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

Объявление исключений
---------------------

        Вы  можете  объявлять  исключения  только  в декларативной части
        блока,   подпрограммы   или   пакета   PL/SQL.    Вы  объявляете
        исключение, вводя  его имя,  за которым  следует ключевое  слово
        EXCEPTION.  В следующем примере объявляется исключение с  именем
        past_due:

        DECLARE
            past_due  EXCEPTION;
            acct_num  NUMBER(5);
        BEGIN
            ...

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

Правила сферы
-------------

        Нельзя  объявлять  исключение  дважды  в  одном  и том же блоке.
        Однако  вы  можете  объявить  то  же  самое  исключение  в  двух
        различных блоках.

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

        Вы  можете  переобъявить  глобальное  исключение  в подблоке.  В
        таком случае локальное объявление имеет преимущество, и  подблок
        не может обращаться к глобальному исключению (если только оно не
        было  объявлено  в  помеченном   блоке;  в  этом  случае   можно
        использовать синтаксис метка_блока.имя_исключения).














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

        Следующий пример иллюстрирует правила сферы для исключений:

        DECLARE
            past_due  EXCEPTION;
            acct_num  NUMBER;
        BEGIN
            ...
            ---------------- начало подблока -------------------------
            DECLARE
                past_due  EXCEPTION;  -- имеет преимущество в подблоке
                acct_num  NUMBER;
            BEGIN
                ...
                IF ... THEN
                    RAISE past_due;  -- это не обрабатывается
                END IF;
                ...
            END;
            ---------------- конец подблока --------------------------
            ...
        EXCEPTION
            WHEN past_due THEN  -- это не относится к исключению,
                ...             -- возбуждаемому в подблоке
        END;

        Здесь окружающий блок не обрабатывает исключение, возбуждаемое в
        подблоке,  потому  что  объявление  past_due  в  подблоке  имеет
        преимущество.  Два исключения past_due, хотя их имена одинаковы,
        являются разными, точно так  же, как различны две  объявленные в
        этом примере переменные  acct_num.  Поэтому предложение  RAISE в
        подблоке  и  фраза  WHEN  в  окружающем блоке относятся к РАЗНЫМ
        исключениям.

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



























                                                   Обработка ошибок  5-9


Использование EXCEPTION_INIT
----------------------------

        Для обработки  непоименованных внутренних  исключений вы  должны
        использовать  обработчик   OTHER  либо   прагму  EXCEPTION_INIT.
        ПРАГМА   -   это   директива   (указание)   компилятору.  Прагмы
        (называемые  также  ПСЕВДОИНСТРУКЦИЯМИ)  обрабатываются во время
        компиляции, а не  во время выполнения.   Они не изменяют  смысла
        программы, а лишь поставляют информацию компилятору.   Например,
        в  языке   Ada  следующая   предопределенная  прагма   указывает
        компилятору, что необходимо оптимизировать использование памяти:

        pragma OPTIMIZE(space);

        В  PL/SQL,   предопределенная  прагма   EXCEPTION_INIT  сообщает
        компилятору имя исключения, которое вы ассоциируете с конкретным
        кодом  ошибки  ORACLE.   Это  позволяет  вам обращаться к любому
        внутреннему исключению  по имени,  написав для  него специальный
        обработчик.

        Вы кодируете прагму EXCEPTION_INIT в декларативной части  блока,
        подпрограммы или пакета PL/SQL, используя следующий синтаксис:

        PRAGMA EXCEPTION_INIT(имя_исключения, код_ошибки_ORACLE);

        Здесь   имя_исключения   -   это   имя   исключения,   ранее уже
        объявленного в  этом блоке.   Прагма должна  появиться в  той же
        декларативной  части,  что  и  соответствующее  исключение,  как
        показано в следующем примере:

        DECLARE
            insufficient_privileges  EXCEPTION;
            PRAGMA EXCEPTION_INIT(insufficient_privileges, -1031);
                --------------------------------------------------------
                -- ORACLE возвращает код ошибки -1031, если, например,
                -- вы пытаетесь обновить таблицу, для которой имеете
                -- лишь полномочия SELECT.
                --------------------------------------------------------
        BEGIN
            ...
        EXCEPTION
            WHEN insufficient_privileges THEN
                -- обработать ошибку
            ...
        END;


















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

Использование raise_application_error
-------------------------------------

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

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

Переобъявление предопределенных исключений
------------------------------------------

        Вспомним, что PL/SQL объявляет свои предопределенные  исключения
        глобально, в пакете STANDARD, так что вам не требуется объявлять
        их самим.  Однако переобъявление предопределенного исключения не
        приведет к ошибке, так как ваше локальное объявление перекрывает
        глобальное внутреннее  объявление.  Например,  если вы  объявите
        исключение  с  именем  invalid_number,  а затем PL/SQL внутренне
        возбудит   предопределенное   исключение   INVALID_NUMBER,    то
        обработчик, написанный вами для вашего invalid_number, не увидит
        внутреннего исключения.  В таком  случае вы должны обращаться  к
        предопределенному исключению с помощью квалифицированной ссылки,
        как показывает следующий пример:

            ...
        EXCEPTION
            WHEN invalid_number OR STANDARD.INVALID_NUMBER THEN
                -- обработать ошибку
            ...
        END;























                                                  Обработка ошибок  5-11


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

Как возбуждаются исключения

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


Использование предложения RAISE
-------------------------------

        Блоки и  подпрограммы PL/SQL  должны явно  возбуждать исключение
        лишь  в  том  случае,   когда  ошибка  делает  невозможным   или
        нежелательным  продолжение  обработки.   Вы  можете закодировать
        предложение RAISE  для данного  исключения в  любом месте  сферы
        этого исключения.   В следующем  примере вы  возбуждаете в вашем
        блоке PL/SQL пользовательское исключение с именем out_of_stock:

        DECLARE
            out_of_stock    EXCEPTION;
            number_on_hand  NUMBER(4);
        BEGIN
            ...
            IF number_on_hand < 1 THEN
                RAISE out_of_stock;
            END IF;
            ...
        EXCEPTION
            WHEN out_of_stock THEN
                -- обработать ошибку
        END;

        Вы можете также явно возбуждать предопределенное исключение, как
        показывает следующий пример:

        RAISE INVALID_NUMBER;

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

        DECLARE
            acct_type  NUMBER;
            ...
        BEGIN
            ...
            IF acct_type NOT IN (1, 2, 3) THEN
                RAISE INVALID_NUMBER;
            END IF;
            ...
        EXCEPTION
            WHEN INVALID_NUMBER THEN
                ROLLBACK;
            ...
        END;





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

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

Как распространяются исключения

        Что происходит, если в текущем блоке нет обработчика  исключений
        для возбужденного исключения?  В этом случае блок завершается, а
        исключение ПРОДВИГАЕТСЯ  в окружающий  блок.  Теперь  окружающий
        блок становится текущим, а исключение воспроизводит себя в  нем.
        Если обработчик исключения не находится и в этом блоке,  процесс
        поиска  повторяется.   Если  текущий  блок  не имеет окружающего
        блока, PL/SQL возвращает ошибку "необрабатываемое исключение"  в
        хост-окружение.

        Рисунки  5-1,  5-2  и  5-3  иллюстрируют правила распространения
        исключений.


Рис.5-1
Правила распространения: Пример 1

           ----------------------------------¬
           ¦ BEGIN                           ¦
           ¦     ...                         ¦
           ¦    ---------------------------¬ ¦
           ¦    ¦ BEGIN                    ¦ ¦
           ¦    ¦     IF X = 1 THEN        ¦ ¦
           ¦    ¦         RAISE A; -----¬  ¦ ¦
           ¦    ¦     ELSIF X = 2 THEN  ¦  ¦ ¦
           ¦    ¦         RAISE B;      ¦  ¦ ¦
           ¦    ¦     ELSE              ¦  ¦ ¦
           ¦    ¦         RAISE C;      ¦  ¦ ¦
           ¦    ¦     END IF;           ¦  ¦ ¦
           ¦    ¦     ...               ¦  ¦ ¦
           ¦    ¦                       ¦ -+-+-------------------------¬
           ¦    ¦ EXCEPTION             ¦ ¦ Исключение A обрабатывается¦
           ¦    ¦     WHEN A THEN   ---- ¦ локально, затем выполнение ¦
           ¦    ¦     ...            ---¬ ¦ продолжается в окружающем  ¦
           ¦    ¦ END;                  ¦ ¦ блоке                      ¦
           ¦    ¦                       ¦ LT-T--------------------------
           ¦    ¦                       ¦  ¦ ¦
           ¦    L-----------------------+--- ¦
           ¦     ...              ------    ¦
           ¦ EXCEPTION                       ¦
           ¦     WHEN B THEN                 ¦
           ¦     ...                         ¦
           ¦ END;                            ¦
           L----------------------------------
















                                                  Обработка ошибок  5-13


Рис.5-2
Правила распространения: Пример 2

           ----------------------------------¬
           ¦ BEGIN                           ¦
           ¦     ...                         ¦
           ¦    ---------------------------¬ ¦
           ¦    ¦ BEGIN                    ¦ ¦
           ¦    ¦     IF X = 1 THEN        ¦ ¦
           ¦    ¦         RAISE A;         ¦ ¦
           ¦    ¦     ELSIF X = 2 THEN     ¦ ¦
           ¦    ¦         RAISE B; -----¬  ¦ ¦
           ¦    ¦     ELSE              ¦  ¦ ¦
           ¦    ¦         RAISE C;      ¦  ¦ ¦
           ¦    ¦     END IF;           ¦  ¦ ¦
           ¦    ¦     ...               ¦  ¦ ¦
           ¦    ¦                       ¦ -+-+-------------------------¬
           ¦    ¦ EXCEPTION             ¦ ¦ Исключение B продвигается  ¦
           ¦    ¦     WHEN A THEN   ---- ¦ до первого окружающего     ¦
           ¦    ¦     ...            ---¬ ¦ блока, имеющего подходящий ¦
           ¦    ¦ END;                  ¦ ¦ обработчик                 ¦
           ¦    ¦                       ¦ LT-T--------------------------
           ¦    L-----------------------+--- ¦
           ¦     ...                    ¦ ---+-------------------------¬
           ¦ EXCEPTION                  ¦ ¦ Исключение B обрабатывается¦
           ¦     WHEN B THEN      ------ ¦ затем управление передается¦
           ¦     ...              ------¬ ¦ назад в хост-окружение     ¦
           ¦ END;                       ¦ L--T--------------------------
           L----------------------------+-----
                                  ------

Рис.5-3
Правила распространения: Пример 3

           ----------------------------------¬
           ¦ BEGIN                           ¦
           ¦     ...                         ¦
           ¦    ---------------------------¬ ¦
           ¦    ¦ BEGIN                    ¦ ¦
           ¦    ¦     IF X = 1 THEN        ¦ ¦
           ¦    ¦         RAISE A;         ¦ ¦
           ¦    ¦     ELSIF X = 2 THEN     ¦ ¦
           ¦    ¦         RAISE B;         ¦ ¦
           ¦    ¦     ELSE                 ¦ ¦
           ¦    ¦         RAISE C; -----¬  ¦ ¦
           ¦    ¦     END IF;           ¦  ¦ ¦
           ¦    ¦     ...               ¦  ¦ ¦
           ¦    ¦                       ¦  ¦ ¦
           ¦    ¦ EXCEPTION             ¦  ¦ ¦
           ¦    ¦     WHEN A THEN   ----  ¦ ¦
           ¦    ¦     ...            ---¬  ¦ ¦
           ¦    ¦ END;                  ¦  ¦ ¦
           ¦    L-----------------------+--- ¦
           ¦     ...                    ¦ ---+-------------------------¬
           ¦ EXCEPTION                  ¦ ¦ Исключение C не имеет      ¦
           ¦     WHEN B THEN       ----- ¦ обработчика, поэтому ошибка¦
           ¦     ...               -----¬ ¦ возвращается в хост-среду  ¦
           ¦ END;                       ¦ L--T--------------------------
           L----------------------------+-----
                                   -----



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

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

        DECLARE
            ...
        BEGIN
            ...
            ---------------- начало подблока -------------------------
            DECLARE
                past_due  EXCEPTION;
            BEGIN
                ...
                IF ... THEN
                    RAISE past_due;
                END IF;
                ...
            END;
            ---------------- конец подблока --------------------------
            ...
        EXCEPTION
            ...
            WHEN OTHERS THEN
                ROLLBACK;
        END;

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































                                                  Обработка ошибок  5-15


Повторное возбуждение исключения
--------------------------------

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

        Чтобы повторно возбудить исключение, поместите предложение RAISE
        в  локальный  обработчик   этого  исключения,  как   показано  в
        следующем примере:

        DECLARE
            out_of_balance  EXCEPTION;
        BEGIN
            ...
            ---------------- начало подблока -------------------------
            BEGIN
                ...
                IF ... THEN
                    RAISE out_of_balance;  -- возбудить исключение
                END IF;
                ...
            EXCEPTION
                WHEN out_of_balance THEN
                    -- обработать ошибку
                    RAISE;  -- повторно возбудить текущее исключение
                ...
            END;
            ---------------- конец подблока --------------------------
        EXCEPTION
            WHEN out_of_balance THEN
                -- обработать ошибку иным способом
            ...
        END;

        Если   опустить   имя   исключения   в   предложении  RAISE (что
        допускается только в обработчике исключений), то подразумевается
        текущее исключение.























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

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

Обработка возбуждаемых исключений

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

        ...
        EXCEPTION
            WHEN имя_исключения1 THEN  -- обработчик
                ряд_предложений1
            WHEN имя_исключения2 THEN  -- другой обработчик
                ряд_предложений2
            ...
            WHEN OTHERS THEN           -- необязательный обработчик
                ряд_предложений3
        END;


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

        Необязательный обработчик исключений  OTHERS - всегда  последний
        обработчик исключений в блоке;  он действует как обработчик  для
        всех  исключений,  не  перечисленных  персонально  в этом блоке.
        Таким образом, блок или подпрограмма может содержать только один
        обработчик OTHERS.  Рассмотрим следующий пример:

        ...
        EXCEPTION
            WHEN ... THEN
                -- обработать ошибку
            WHEN ... THEN
                -- обработать ошибку
            WHEN ... THEN
                -- обработать ошибку
            WHEN OTHERS THEN
                -- обработать все прочие ошибки
        END;

        Использование  обработчика  OTHERS  гарантирует,  что  ни   одно
        исключение не пройдет необработанным.












                                                  Обработка ошибок  5-17


        Если  вы  хотите  выполнять  одну  и  ту  же  последовательность
        предложений  для  двух  или  более исключений, перечислите имена
        этих исключений в  фразе WHEN, разделяя  их ключевым словом  OR,
        как показано ниже:

        ...
        EXCEPTION
            WHEN over_limit OR under_limit OR VALUE_ERROR THEN
                -- обработать ошибку
            ...
        END;

        Если  будет  возбуждено  любое  из  перечисленных  в  фразе WHEN
        исключений,  соответствующий   обработчик  получит   управление.
        Включение ключевого слова OTHERS в список имен исключений  фразы
        WHEN НЕ ДОПУСКАЕТСЯ.  Слово  OTHERS может появиться только  само
        по   себе.    Вы   можете   иметь   сколько  угодно обработчиков
        исключений,    а    каждый    обработчик    может  ассоциировать
        последовательность  предложений  с  любым  списком   исключений.
        Однако  любое  имя  исключения  может  появиться лишь один раз в
        части обработки исключений блока или подпрограммы PL/SQL.

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

Исключения, возбужденные в объявлениях
--------------------------------------

        Исключения  могут  также  возбуждаться некорректными выражениями
        инициализации  в  объявлениях.   Например,  следующее объявление
        неявно возбуждает  исключение VALUE_ERROR,  потому что  limit не
        может хранить числа, большие 999:

        DECLARE
            limit  CONSTANT  NUMBER(3) := 5000;
            -- возбуждает исключение VALUE_ERROR
        BEGIN
            ...
        EXCEPTION
            WHEN VALUE_ERROR THEN  -- не перехватит это исключение
                ...
        END;

        Обработчики в текущем  блоке не могут  перехватывать исключений,
        возбужденных  в   объявлениях,  потому   что  такое   исключение
        НЕМЕДЛЕННО продвигается в окружающий блок.

Исключения, возбужденные в обработчиках
---------------------------------------

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

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

Переходы в обработчик и из него
-------------------------------

        Предложение GOTO нельзя  использовать для перехода  в обработчик
        исключений либо для перехода из обработчика исключений в текущий
        блок.  Например, следующее предложение GOTO НЕЗАКОННО:

        DECLARE
            pe_ratio  NUMBER(3,1);
        BEGIN
            DELETE FROM stats WHERE symbol = 'XYZ';
            SELECT price / NVL(earnings, 0) INTO pe_ratio FROM stocks
                WHERE symbol = 'XYZ';
            <>
            INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio);
        EXCEPTION
            WHEN ZERO_DIVIDE THEN
                pe_ratio := 0;
                GOTO my_label;  -- незаконный переход в текущий блок
        END;

        Однако  предложение  GOTO  можно  использовать  для  перехода из
        обработчика исключения в окружающий блок.

Использование SQLCODE и SQLERRM
-------------------------------

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

        Для внутренне определенных исключений, SQLCODE возвращает  номер
        ошибки ORACLE,  передавшей управление  обработчику.  Этот  номер
        отрицателен,  исключая  случай  ошибки  ORACLE  "no data found",
        когда SQLCODE  возвращает +100.   SQLERRM возвращает  сообщение,
        ассоциированное  с  возникшей  ошибкой  ORACLE.   Это  сообщение
        начинается с кода ошибки ORACLE.

        Для  пользовательских  исключений,  SQLCODE  возвращает  +1,   а
        SQLERRM возвращает сообщение

        User-Defined Exception

        если   вы   не   использовали   прагму   EXCEPTION_INIT,   чтобы
        ассоциировать ваше  исключение с  номером ошибки  ORACLE; в этом
        случае  SQLCODE   возвращает  этот   номер  ошибки,   а  SQLERRM
        возвращает соответствующее  сообщение об  ошибке.  Заметим,  что
        максимальная  длина  сообщения  ORACLE  об ошибке составляет 512
        символов, включая код ошибки, вложенные сообщения и подстановки,
        такие как имена таблиц и столбцов.

        Если не возбуждено никакое исключение, то SQLCODE возвращает  0,
        а SQLERRM возвращает сообщение

        ORA-0000: normal, successful completion








                                                  Обработка ошибок  5-19


        Вы можете передать функции  SQLERRM номер ошибки; в  этом случае
        SQLERRM  возвратит  сообщение,  ассоциированное  с  этим номером
        ошибки.   Номер  ошибки,   передаваемый  SQLERRM,  должен   быть
        отрицателен.    Нулевой   код,   передаваемый   SQLERRM,  всегда
        возвращает сообщение

        ORA-0000: normal, successful completion

        Передача SQLERRM  положительного номера  ошибки (за  исключением
        +100) всегда возвратит сообщение

        User-Defined Exception

        а передача SQLERRM кода +100 возвратит сообщение

        ORA-01403: no data found

        В следующем  примере, SQLERRM  возвратит не  то, что  ожидалось,
        потому    что    передается    положительное    значение  вместо
        отрицательного:

        DECLARE
            msg  CHAR(100);
        BEGIN
            FOR num IN 1..9999 LOOP
                msg := SQLERRM(num);  -- надо задавать SQLERRM(-num)
                INSERT INTO errors VALUES (msg);
            END LOOP;
        END;

        Вы   не   можете   использовать   функции   SQLCODE   и  SQLERRM
        непосредственно   в   предложениях   SQL.   Например,  следующее
        предложение незаконно:

        INSERT INTO errors VALUES (SQLCODE, SQLERRM);

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

        DECLARE
            err_num  NUMBER;
            err_msg  CHAR(100);
        BEGIN
            ...
        EXCEPTION
            ...
            WHEN OTHERS THEN
                err_num := SQLCODE;
                err_msg := SUBSTR(SQLERRM, 1, 100);
                INSERT INTO errors VALUES (err_num, err_msg);
        END;

        Строковая функция SUBSTR()  гарантирует, что возможное  усечение
        при  присваивании  переменной  err_msg  не  возбудит  исключения
        VALUE_ERROR.   Функции  SQLCODE  и  SQLERRM  особенно  полезны в
        исключении OTHERS,  потому что  они позволяют  установить, какое
        внутреннее исключение было возбуждено.





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

Необработанные исключения
-------------------------

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

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

        Необработанные исключения  могут также  влиять на  подпрограммы.
        При успешном выходе из подпрограммы PL/SQL присваивает  значения
        параметрам   OUT.   Однако,   если   вы   выходите  в результате
        необработанного   исключения,   значения   параметрам   OUT   НЕ
        присваиваются.  Кроме того,  как уже сказано,  если подпрограмма
        сбивается в результате необработанного исключения, ORACLE неявно
        отменяет ее работу.  Однако, если подпрограмма выдала COMMIT  до
        возбуждения  необрабатываемого  исключения,  отменена будет лишь
        неподтвержденная часть работы.

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

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

Полезные приемы

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


Продолжение работы после возбуждения исключения
-----------------------------------------------

        Обработчик исключений  позволяет вам  исправить ошибку,  которая
        иначе могла бы стать фатальной, до выхода из блока.  Однако если
        завершить обработчик, то блок больше не получит управления.   Вы
        не можете  вернуться в  текущий блок  из обработчика исключений.
        Так,  в   следующем  примере,   если  предложение   SELECT  INTO
        возбуждает  исключение  ZERO_DIVIDE  (деление  на  0),  то вы не
        можете вернуть управление на INSERT:

        DECLARE
            pe_ratio  NUMBER(3,1);
        BEGIN
            DELETE FROM stats WHERE symbol = 'XYZ';
            SELECT price / NVL(earnings, 0) INTO pe_ratio FROM stocks
                WHERE symbol = 'XYZ';
            INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio);
        EXCEPTION
            WHEN ZERO_DIVIDE THEN
                ...
        END;


                                                  Обработка ошибок  5-21


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

        DECLARE
            pe_ratio  NUMBER(3,1);
        BEGIN
            DELETE FROM stats WHERE symbol = 'XYZ';
            ---------------- начало подблока ------------------
            BEGIN
                SELECT price / NVL(earnings,0) INTO pe_ratio FROM stocks
                    WHERE symbol = 'XYZ';
            EXCEPTION
                WHEN ZERO_DIVIDE THEN
                    pe_ratio := 0;
            END;
            ---------------- конец подблока -------------------
            INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio);
        EXCEPTION
            ...
        END;

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

        В  последнем  примере,  если  предложение  SELECT  INTO  вызовет
        исключение ZERO_DIVIDE,  локальный обработчик  перехватит его  и
        установит   нулевое   значение   переменной   pe_ratio.    После
        завершения   обработчика   подблок   завершится,   и  выполнение
        продолжится с предложения INSERT.


Повторение транзакции
---------------------

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

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













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

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

        DECLARE
            name    CHAR(20);
            ans1    CHAR(3);
            ans2    CHAR(3);
            ans3    CHAR(3);
            suffix  NUMBER := 1;
        BEGIN
            ...
            LOOP  -- можно написать "FOR i IN 1..10 LOOP", чтобы
                  -- ограничиться максимально десятью попытками
                ------------------- начало подблока -------------------
                BEGIN
                    ...
                    SAVEPOINT start_transaction; -- точка сохранения
                        /* Удалить результаты опроса. */
                    DELETE FROM results WHERE answer1 = 'NO';
                        /* Добавить имя и ответы респондента. */
                    INSERT INTO results VALUES (name, ans1, ans2, ans3);
                        /* Это может дать исключение DUP_VAL_ON_INDEX, *
                         * если два респондента имеют одинаковые имена,*
                         * так как индекс по столбцу name уникальный. */
                    COMMIT;
                    EXIT;
                EXCEPTION
                    WHEN DUP_VAL_ON_INDEX THEN
                        ROLLBACK TO start_transaction;  -- откат
                        suffix := suffix + 1; -- попробуем исправить имя
                        name := name || TO_CHAR(suffix);
                    ...
                END;
                ------------------- конец подблока --------------------
            END LOOP;
        END;