日期:2014-05-16  浏览次数:20706 次

FORALL 之 SAVE EXCEPTIONS 子句应用一例

     对于大批量的DML操作中出现的错误,除了使用DML error logging特性来记录在DML期间出现的错误之外,使用批量SQL语句FORALL的SAVE
EXCEPTIONS是不错的选择之一。DML error logging特性的使用较FORALL之 SAVE EXCEPTIONS相对简单,也存在一些不足,如每一个被操作的DML
对象需要创建相应的对应的日志表,不利于集中管理。本文对DML error logging这个不利于集中管理的特性使用FORALL 之 SAVE EXCEPTIONS
方式来完成。

   下面的示例来自一个实际的应用,撇开原始的表名与实际应用中的处理过程,仅仅通过简单示例来展现。
      1、两个不同的DB,假定设定为数据库A和数据库B(为简化,在同一个DB上来演示)。
      2、需要将数据库A的一些表的数据同步到数据库B对应的目的表
      3、如果同步的过程中出现某条特定的记录错误,则写该记录产生的错误信息(含表名,主键)到日志表,并将其原表同步状态更新为N,否则更新为Y
      4、如果非由于INSERT产生的错误信息,则要求写过程名及对应的错误信息到日志表

   如对于批量SQL较为熟悉,请直接阅读下文,否则,请参阅阅读本文所需要的相关知识:

      批量SQL之 FORALL 语句
      批量SQL之 BULK COLLECT 子句
      PL/SQL 集合的初始化与赋值
      PL/SQL 联合数组与嵌套表
      PL/SQL 变长数组
      PL/SQL --> PL/SQL记录

一、创建演示环境
为简化,下面的演示代码在同一个数据库上完成,在不同的DB上来完成仅仅是需要设定DB LINK而已。

-->创建用于演示的源表emp_source,其数据来自scott.emp
scott@CNMMBO> create table emp_source as select empno,ename,sal from emp; 

Table created.

-->为源表增加一个字段is_sync,用于记录是否同步成功
scott@CNMMBO> alter table emp_source add is_sync char(1);

Table altered.

-->创建目的表
scott@CNMMBO> create table emp_dest as select empno,ename,sal from emp_source where 1=0;

Table created.

-->创建记录错误信息的日志表
scott@CNMMBO> create table err_log_tbl(log_seq number(12) not null, log_time date not null,
  2  sp_name varchar2(100),table_name varchar2(30),table_pk varchar2(30),err_msg varchar2(2000));

Table created.

-->为表emp_dest 添加约束用于在insert过程中触发错误产生
scott@CNMMBO> alter table emp_dest add constraint ck_sal check(sal>500);

Table altered.

scott@CNMMBO> alter table emp_dest modify(ename constraint nn_ename not null);

Table altered.

-->创建一个用于错误日志表上的sequence
CREATE SEQUENCE SCOTT.ERR_SEQ
  START WITH 20
  MAXVALUE 999999999999999999999999999
  MINVALUE 0
  NOCYCLE
  NOCACHE
  NOORDER;

-->创建一个函数用于获取sequence
CREATE OR REPLACE FUNCTION SCOTT.gen_new_err_seq
   RETURN err_log_tbl.log_seq%TYPE
IS
   newrecid   err_log_tbl.log_seq%TYPE;
BEGIN
   SELECT ERR_SEQ.NEXTVAL INTO newrecid FROM DUAL;

   RETURN newrecid;
END;
/

二、使用下面的PL/SQL块演示

-->下面的演示代码可以封装到包
DECLARE
   c_sp_name        CONSTANT VARCHAR2 (50) := 'anonymity_plsql_block';
   c_process_name   CONSTANT VARCHAR2 (20) := 'ins_emp_dest';
   c_table_name              VARCHAR2 (30) := 'emp_dest';
   debugpos                  bo_common_pkg.debug_pos_type := 0;
   v_limit                   PLS_INTEGER := 5;    -->定义游标fetch时提取数量的限制数,由于emp_source记录较少,此处设定为5
   err_msg                   VARCHAR2 (1000);

   CURSOR cur_emp                                 -->声明游标从源表取数据
   IS
      SELECT empno, ename, sal FROM emp_source WHERE is_sync IS NULL;

   TYPE emp_tab_type IS TABLE OF cur_emp%ROWTYPE;

   emp_tab                   emp_tab_type;      -->声明基于游标的嵌套表

   TYPE err_rec_type IS TABLE OF err_log_tbl%ROWTYPE;   

   err_tab                   err_rec_type := err_rec_type (); -->声明基于err_log_tab的嵌套表并初始化
   sub_proc_exp              EXCEPTION;     -->定义了两个异常
   bulk_error                EXCEPTION;
   PRAGMA EXCEPTION_INIT (bulk_error, -24381);
BEGIN
   debugpos   := 10;

   OPEN cur_emp;

   LOOP
      BEGIN