PL/SQL скрипт вызывает ‘ОШИБКА на строке 1 DECLARE’, я не вижу причины.

Вопрос или проблема

Добрый день, сообщество,

У меня есть скрипт PLSQL, выполняемый в ORACLE 19, который выдает ошибку, которую я не понимаю.

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

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

Мой скрипт PLSQL выглядит так:

set serveroutput on;
WHENEVER SQLERROR EXIT SQL.SQLCODE;

DECLARE
    index_not_exists EXCEPTION;
    table_not_exists EXCEPTION;
    PRAGMA EXCEPTION_INIT (index_not_exists, -1418);
    PRAGMA EXCEPTION_INIT (table_not_exists, -942);

    TYPE ind_list IS TABLE OF VARCHAR(100) NOT NULL;
    ind_to_drop ind_list := ind_list('sst_ind1', 'sst_ind2');
    /* первый индекс неверен, потому что col0 не существует */
    ind_to_create ind_list := ind_list('create index sst_ind1 on sst_table(col0)', 'create index sst_ind2 on sst_table(col2)');

    sql_to_exec varchar(200);
    is_all_index_created boolean := TRUE;

BEGIN
    BEGIN
        EXECUTE IMMEDIATE 'drop table sst_table cascade constraints purge';
    EXCEPTION
        WHEN table_not_exists THEN
        NULL;
    END;

    EXECUTE IMMEDIATE 'create table sst_table (col1 number, col2 number)';
    EXECUTE IMMEDIATE 'create index sst_ind1 on sst_table (col1)';
    EXECUTE IMMEDIATE 'create index sst_ind2 on sst_table (col2)';

    FOR l_index IN ind_to_drop.FIRST..ind_to_drop.LAST LOOP
        BEGIN
            sql_to_exec := 'drop index ' || ind_to_drop(l_index);
            dbms_output.put_line(sql_to_exec);
            EXECUTE IMMEDIATE sql_to_exec;
        EXCEPTION
            WHEN index_not_exists THEN
                dbms_output.put_line('нет индекса ' || ind_to_drop(l_index));
        END;
    END LOOP;

    FOR l_index IN ind_to_create.FIRST..ind_to_create.LAST LOOP
        BEGIN
            dbms_output.put_line(ind_to_create(l_index));
            EXECUTE IMMEDIATE ind_to_create(l_index);
        EXCEPTION
            WHEN OTHERS THEN
                dbms_output.put_line('ОШИБКА С ' || ind_to_create(l_index));
                is_all_index_created := FALSE;
        END;
    END LOOP;

    IF NOT is_all_index_created THEN
        RAISE_APPLICATION_ERROR(-20001, 'ПРОБЛЕМА СОЗДАНИЯ ИНДЕКСА');
    END IF;

END;
/

exit

Когда я выполняю свой скрипт через sqplus, я получаю НИЖЕУКАЗАННУЮ ОШИБКУ, которую не понимаю

sqlplus xxxxx/yyyyy@zzzzzz @ubscls_6711.sql

SQL*Plus: Выпуск 11.2.0.4.0 Производственный на Ср Сен 25 17:04:11 2024

Авторские права (c) 1982, 2013, Oracle. Все права защищены.

Подключено к:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Производственный

drop index sst_ind1
drop index sst_ind2
create index sst_ind1 on sst_table(col0)
ОШИБКА С create index sst_ind1 on sst_table(col0)
create index sst_ind2 on sst_table(col2)
DECLARE
*
ОШИБКА на строке 1:
ORA-20001: ПРОБЛЕМА СОЗДАНИЯ ИНДЕКСА
ORA-06512: на строке 49

Отключено от Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Производственный

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

Спасибо

Скрипт делает то, что вы сказали, чтобы он сделал… ваш вопрос утверждает, что:

  • создание индекса, которое вызывает ошибку, должно продолжаться до завершения создания оставшихся индексов

В коде есть комментарий, который говорит:

первый индекс неверен, потому что col0 не существует

Вывод показывает:

create index sst_ind1 on sst_table(col0)
ОШИБКА С create index sst_ind1 on sst_table(col0)

… поэтому создание этого индекса терпит неудачу, как и ожидалось (скрывая настоящую ошибку, что не полезно, как сказал @Koen); затем он продолжает и показывает:

create index sst_ind2 on sst_table(col2)

… что, очевидно, проходит успешно. Так что оно продолжало создавать оставшиеся индексы.

Вы используете флаг is_all_index_created, чтобы отслеживать любые проблемы. Он устанавливается в false из-за ожидаемого сбоя. Затем в конце вашего кода у вас есть:

    IF NOT is_all_index_created THEN
        RAISE_APPLICATION_ERROR(-20001, 'ПРОБЛЕМА СОЗДАНИЯ ИНДЕКСА');
    END IF;

и это именно та ошибка, которую вы видите:

DECLARE
*
ОШИБКА на строке 1:
ORA-20001: ПРОБЛЕМА СОЗДАНИЯ ИНДЕКСА
ORA-06512: на строке 49

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


В начале скрипта у вас есть

WHENEVER SQLERROR EXIT SQL.SQLCODE;

так что ваш скрипт выйдет с кодом -20001, что указывает на сбой, а не с нулем, что указывает на успех. Однако вы, возможно, не увидите -20001, он может быть преобразован в меньший диапазон; Скрипты, работающие в Unix, оборачиваются, если код выхода находится вне допустимого диапазона, поэтому вы можете увидеть -33 или 233, к примеру. (Что объясняет, почему это следует избегать, так как некоторые фактические коды ошибок обернутся в ноль и будут выглядеть как успех… безопаснее использовать ... EXIT FAILURE)

Скрипт обрабатывает любые поднятые исключения, но не показывает, в чем ошибка. Обратите внимание на этот блок в скрипте.

        EXCEPTION
            WHEN OTHERS THEN
                dbms_output.put_line('ОШИБКА С ' || ind_to_create(l_index));
                is_all_index_created := FALSE;
        END;

Что он делает: Если возникает любая ошибка, то выводится на экран, что… произошла ошибка.

Это не очень полезно. Есть несколько встроенных функций, которые предоставляют вам больше информации. SQLERRM дает код ошибки и описание ошибки, SQLCODE просто показывает код ошибки, dbms_utility.format_error_backtrace показывает полный стек.

Для вашего случая SQLERRM должно быть достаточно.

        EXCEPTION
            WHEN OTHERS THEN
                dbms_output.put_line('ОШИБКА С ' || ind_to_create(l_index));
                dbms_output.put_line(SQLERRM);
                is_all_index_created := FALSE;
        END;

Ответ или решение

Конечно, давайте разберем ваш сценарий PL/SQL и попробуем понять, что происходит.

Ваш скрипт предназначен для удаления существующих индексов и их последующего создания, однако он вызывает ошибку ORA-20001: PROBLEM CREATING INDEX из-за логики проверки на успешное создание индексов. В частности, ошибка возникает, когда один из индексов не может быть создан (в данном случае это sst_ind1, который делает ссылку на несуществующий столбец col0). После этого логика скрипта устанавливает флаг is_all_index_created в значение FALSE, что в свою очередь приводит к вызову RAISE_APPLICATION_ERROR в конце блока.

Вот как ваш код работает:

  1. Индекс sst_ind1 не может быть создан из-за отсутствия столбца col0, что вызывает исключение, и флаг is_all_index_created становится FALSE.
  2. Индекс sst_ind2 создается успешно, однако скрипт все равно проверяет флаг и, найдя его равным FALSE, вызывает ошибку ORA-20001.

Решение проблемы:

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

Модифицированный код:

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

SET SERVEROUTPUT ON;
WHENEVER SQLERROR EXIT SQL.SQLCODE;

DECLARE
    index_not_exists EXCEPTION;
    table_not_exists EXCEPTION;
    PRAGMA EXCEPTION_INIT(index_not_exists, -1418);
    PRAGMA EXCEPTION_INIT(table_not_exists, -942);

    TYPE ind_list IS TABLE OF VARCHAR(100) NOT NULL;
    ind_to_drop ind_list := ind_list('sst_ind1', 'sst_ind2');
    ind_to_create ind_list := ind_list('create index sst_ind1 on sst_table(col0)', 'create index sst_ind2 on sst_table(col2)');

    sql_to_exec VARCHAR(200);
    errors_count NUMBER := 0; -- Счетчик ошибок

BEGIN
    BEGIN
        EXECUTE IMMEDIATE 'DROP TABLE sst_table CASCADE CONSTRAINTS PURGE';
    EXCEPTION
        WHEN table_not_exists THEN
            NULL;
    END;

    EXECUTE IMMEDIATE 'CREATE TABLE sst_table (col1 NUMBER, col2 NUMBER)';

    FOR l_index IN ind_to_drop.FIRST..ind_to_drop.LAST LOOP
        BEGIN
            sql_to_exec := 'DROP INDEX ' || ind_to_drop(l_index);
            dbms_output.put_line(sql_to_exec);
            EXECUTE IMMEDIATE sql_to_exec;
        EXCEPTION
            WHEN index_not_exists THEN
                dbms_output.put_line('Index ' || ind_to_drop(l_index) || ' does not exist.');
        END;
    END LOOP;

    FOR l_index IN ind_to_create.FIRST..ind_to_create.LAST LOOP
        BEGIN
            dbms_output.put_line(ind_to_create(l_index));
            EXECUTE IMMEDIATE ind_to_create(l_index);
        EXCEPTION
            WHEN OTHERS THEN
                dbms_output.put_line('ERROR WITH ' || ind_to_create(l_index));
                dbms_output.put_line(SQLERRM);
                errors_count := errors_count + 1; -- Увеличиваем счетчик ошибок
        END;
    END LOOP;

    IF errors_count > 0 THEN
        RAISE_APPLICATION_ERROR(-20001, 'There were ' || errors_count || ' errors creating indexes.');
    END IF;

END;
/

Что изменилось:

  1. Я добавил счетчик errors_count, который отслеживает количество ошибок во время создания индексов.
  2. Вместо флага is_all_index_created, после выполнения всех попыток создания индексов, скрипт проверяет значения счетчика. Если ошибок больше нуля, вызывается исключение с сообщением о количестве ошибок.

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

Оцените материал
Добавить комментарий

Капча загружается...