忽略导致错误的行

Ignore lines that causes errors

我有一个大型 Oracle 脚本,在 BEGIN - END;

中包含数千个包调用

有没有办法忽略导致错误的行并继续执行下一行? vb 中的某种“On Error Resume Next”。

如果您只有一个 BEGIN END 部分,那么您可以使用 EXCEPTION WHEN OTHERS THEN NULL。

SQL> declare
    v_var pls_integer;
    begin
    select 1 into v_var from dual;
    -- now error
    select 'A' into v_var from dual;
    exception when others then null;
    end;
SQL> /

PL/SQL procedure successfully completed.

SQL> declare
   v_var pls_integer;
    begin
    select 1 into v_var from dual;
    -- now error
    select 'A' into v_var from dual;
    --exception when others then null;
    end;
    /
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 6


SQL>

“忽略错误”的整个概念是一个错误,如果出现任何错误,就是一个谎言。这并不是说您不能捕获错误并继续处理,只是您必须处理错误。例如,假设用例:“数据已从多个 .csv 文件加载到阶段 table。现在根据...加载到 tables A 和 Table B .".

create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
Begin 
    For rec in (select * from stage)
    loop
       begin 
           insert into table_a (col1, col2) 
           values (rec.col_a1, col_a2);

           insert into table_b (col1, col2) 
           values (rec.col_b1, col_b2);
       exception 
           when others then null;
       end;
    end loop;
    process_message := 'Load Tables A,B Complete';
end ;

现在假设用户创建了一个 .csv 文件,在没有值或值未知的数字列中输入了“n/a”。这种太常见的情况的结果是所有这些行都没有加载,但是 你无法知道 直到用户抱怨他们的数据没有加载,即使你告诉他们它是. 而且您无法确定问题所在
更好的方法是“捕获并报告”。

create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
    load_error_occurred boolean := False;
Begin 
    For rec in (select * from stage)
    loop
       begin 
           insert into table_a (col1, col2) 
           values (rec.col_a1, rec.col_a2);
       exception 
           when others then 
                log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
                load_error_occurred := True;
       end; 
       
       begin 
           insert into table_b (col1, col2) 
           values (rec.col_b1, rec.col_b2);
       exception 
           when others then 
                log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
                load_error_occurred := True;
       end;

    end loop;
    
    if load_error_occurred then
       process_message := 'Load Tables A,B Complete: Error(s) Detected';    
    else
       process_message := 'Load Tables A,B Complete: Successful No Error(s)';
    end if;
end Load_Tables_A_B_from_Stage ; 
 

现在您已经将实际情况告知用户,并且通过与您联系的方式可以很容易地确定问题所在。

此处的用户是最一般意义上的用户。这可能意味着调用程序而不是个人。重点是您不必因错误而终止您的进程,但不要忽略它们

我不认为有任何神奇的单线可以解决这个问题。

与其他人一样,使用编辑器自动包装 BEGIN-EXCEPTION-END 块中的每个调用可能是 quicker/easier。

但是,如果觉得有点冒险,或者试试这个攻略:

假设您有这个:

BEGIN
  proc1;
  proc2;
  proc3;
.
.
.
  proc1000;
END;

您可以试试这个(未经测试、未编译但可能会让您知道要尝试什么):

DECLARE
   l_progress NUMBER := 0;
   l_proc_no  NUMBER := 0;
   e_proc_err  EXCEPTION;
   -- A 'runner' procedure than manegrs the counters and runs/skips dpending on these vals
   PROCEDURE run_proc ( pname IN VARCHAR2 ) IS
   BEGIN
      l_proc_no := l_proc_no + 1;
      IF l_proc_no >= l_progress
      THEN
         -- log 'Running pname'
         EXECUTE IMMEDIATE 'BEGIN ' || pname || '; END;' ;
         l_progress := l_progress + 1;
      ELSE
         -- log 'Skipping pname'
      END IF;
   EXCEPTION
      WHEN OTHERS THEN
         -- log 'Error in pname'
         l_progress := l_progress + 1;
         RAISE e_proc_err;
   END;
BEGIN
   l_progress := 0;
<<start>>
   l_proc_no := 0;
   run_proc ( 'proc1' );
   run_proc ( 'proc2' );
   run_proc ( 'proc3' );
.
.
   run_proc ( 'proc1000' );
EXCEPTION
   WHEN e_proc_err THEN
      GOTO start;
    WHEN OTHERS THEN
      RAISE;
END;

这里的想法是添加一个 'runner' 过程来动态执行每个过程并记录 运行、跳过、错误。

我们维护当前进程号 (l_proc_no) 和执行步骤总数 (l_progress) 的全局计数。

当发生错误时,我们记录它,提出它并让它落入外部块 EXCEPTION 处理程序,它将通过(邪恶的)GOTO 重新启动。

放置GOTO使得总执行计数不变但进程号重置为0。

现在,当 run_proc 被调用时,它发现 l_progress 大于 l_proc_no,并跳过它。

为什么这比简单地在每个调用周围包装一个 BEGIN EXCEPTION END 更好?

可能不是,但您对每一行代码进行了较小的更改,并且更加整齐地标准化了每次调用的日志记录。

危险是潜在的无限循环,这就是为什么我指定 e_proc_err 来表示被调用过程中的错误。但它可能需要调整以使其健壮。