-- Create and populate a source table. CREATE TABLE source ( id NUMBER( 10 ) NOT NULL ,code VARCHAR2( 10 ) ,description VARCHAR2( 50 ) ,CONSTRAINT source_pk PRIMARY KEY( id ) ); DECLARE TYPE t_tab IS TABLE OF source%ROWTYPE; l_tab t_tab := t_tab( ); BEGIN FOR i IN 1 .. 100000 LOOP l_tab.EXTEND; l_tab( l_tab.LAST ).id := i; l_tab( l_tab.LAST ).code := TO_CHAR( i ); l_tab( l_tab.LAST ).description := 'Description for ' || TO_CHAR( i ); END LOOP; -- For a possible error condition. l_tab( 1000 ).code := NULL; l_tab( 10000 ).code := NULL; FORALL i IN l_tab.FIRST .. l_tab.LAST INSERT INTO source VALUES l_tab( i ); COMMIT; END; / EXEC DBMS_STATS.gather_table_stats(USER, 'source', cascade => TRUE); -- Create a destination table. CREATE TABLE dest ( id NUMBER( 10 ) NOT NULL ,code VARCHAR2( 10 ) NOT NULL ,description VARCHAR2( 50 ) ,CONSTRAINT dest_pk PRIMARY KEY( id ) ); -- Create a dependant of the destination table. CREATE TABLE dest_child ( id NUMBER ,dest_id NUMBER ,CONSTRAINT child_pk PRIMARY KEY( id ) ,CONSTRAINT dest_child_dest_fk FOREIGN KEY( dest_id ) REFERENCES dest( id ) );注意,code列在source 表中是可选,而在dest 表中是强制的
-- Create the error logging table. BEGIN DBMS_ERRLOG.create_error_log( dml_table_name => 'dest' ); END; / pl/SQL procedure successfully completed. --缺省情况下,创建的日志表基于当前schema。日志表的所有者以及日志名字,表空间名字也可以单独指定。缺省的日志表的名字基于基表并以 --"ERR$_"前缀开头。 SELECT owner, table_name, tablespace_name FROM all_tables WHERE owner = 'TEST'; OWNER TABLE_NAME TABLESPACE_NAME ------------------------------ ------------------------------ ------------------------------ TEST DEST USERS TEST DEST_CHILD USERS TEST ERR$_DEST USERS TEST SOURCE USERS 4 rows selected. --日志表的结构以及数据类型和所允许的最大长度依赖于基表,如下所示: SQL> DESC err$_dest Name Null? Type --------------------------------- -------- -------------- ORA_ERR_NUMBER$ NUMBER ORA_ERR_MESG$ VARCHAR2(2000) ORA_ERR_ROWID$ ROWID ORA_ERR_OPTYP$ VARCHAR2(2) ORA_ERR_TAG$ VARCHAR2(2000) ID VARCHAR2(4000) CODE VARCHAR2(4000) DESCRIPTION VARCHAR2(4000)1、INSERT 操作
INSERT INTO dest SELECT * FROM source; SELECT * * ERROR at line 2: ORA-01400: cannot insert NULL into ("TEST"."DEST"."CODE") --source 表为NULL的两行将引起整个insert 语句回滚,无论在错误之间有多少条语句被成功插入。通过添加DML error logging 子句,则允许我们 --对那些有效数据实现成功插入。 INSERT INTO dest SELECT * FROM source LOG ERRORS INTO err$_dest ('INSERT') REJECT LIMIT UNLIMITED; 99998 rows created. --那些未能成功插入的记录将被记录在ERR$_DEST中,并且也记录了错误的原因。 COLUMN ora_err_mesg$ FORMAT A70 SELECT ora_err_number$, ora_err_mesg$ FROM err$_dest WHERE ora_err_tag$ = 'INSERT'; ORA_ERR_NUMBER$ ORA_ERR_MESG$ --------------- --------------------------------------------------------- 1400 ORA-01400: cannot insert NULL into ("TEST"."DEST"."CODE") 1400 ORA-01400: cannot insert NULL into ("TEST"."DEST"."CODE") 2 rows selected.2、UPDATE 操作
UPDATE dest SET code = DECODE(id, 9, NULL, 10, NULL, code) WHERE id BETWEEN 1 AND 10; * ERROR at line 2: ORA-01407: cannot update ("TEST"."DEST"."CODE") to NULL --如我们所期待的那样,语句由于code列不允许为NULL而导致操作失败。同样,通过添加DML erorr logging子句允许我们完成有效记录的操作 UPDATE dest SET code = DECODE(id, 9, NULL, 10, NULL, code) WHERE id BETWEEN 1 AND 10 LOG ERRORS INTO err$_dest ('UPDATE') REJECT LIMIT UNLIMITED; 8 rows updated. --同样地,update操作失败的行以及失败原因被记录在ERR$_DEST 表 COLUMN ora_err_mesg$ FORMAT A70 SELECT ora_err_number$, ora_err_mesg$ FROM err$_dest WHERE ora_err_tag$ = 'UPDATE'; ORA_ERR_NUMBER$ ORA_ERR_MESG$ --------------- --------------------------------------------------------- 1400 ORA-01400: cannot insert NULL into ("TEST"."DEST"."CODE") 1400 ORA-01400: cannot insert NULL into ("TEST"."DEST"."CODE")3、MERGE 操作
DELETE FROM dest WHERE id > 50000; MERGE INTO dest a USING source b ON (a.id = b.id) WHEN MATCHED THEN UPDATE SET a.code = b.code, a.description = b.description WHEN NOT MATCHED THEN INSERT (id, code, description) VALUES (b.id, b.code, b.description); * ERROR at line 9: ORA-01400: cannot insert NULL into ("TEST"."DEST"."CODE") --merge操作同样由于not null约束导致导致操作失败并且回滚。 --下面为其添加DML error logging 允许merge操作完成 MERGE INTO dest a USING source b ON (a.id = b.id) WHEN MATCHED THEN UPDATE SET a.code = b.code, a.description = b.description WHEN NOT MATCHED THEN INSERT (id, code, description) VALUES (b.id, b.code, b.description) LOG ERRORS INTO err$_dest ('MERGE') REJECT LIMIT UNLIMITED; 99998 rows merged. --更新操作失败的行以及失败原因同样被记录在ERR$_DEST 表中 COLUMN ora_err_mesg$ FORMAT A70 SELECT ora_err_number$, ora_err_mesg$ FROM err$_dest WHERE ora_err_tag$ = 'MERGE'; ORA_ERR_NUMBER$ ORA_ERR_MESG$ --------------- --------------------------------------------------------- 1400 ORA-01400: cannot insert NULL into ("TEST"."DEST"."CODE") 1400 ORA-01400: cannot insert NULL into ("TEST"."DEST"."CODE") 2 rows selected.4、DELETE 操作
INSERT INTO dest_child (id, dest_id) VALUES (1, 100); INSERT INTO dest_child (id, dest_id) VALUES (2, 101); DELETE FROM dest; * ERROR at line 1: ORA-02292: integrity constraint (TEST.DEST_CHILD_DEST_FK) violated - child record found --对于Delete操作,同样可以添加DML error logging子句来记录错误使得整个语句成功执行 。 DELETE FROM dest LOG ERRORS INTO err$_dest ('DELETE') REJECT LIMIT UNLIMITED; 99996 rows deleted. --下面是Delete操作失败的日志以及错误原因。 COLUMN ora_err_mesg$ FORMAT A69 SELECT ora_err_number$, ora_err_mesg$ FROM err$_dest WHERE ora_err_tag$ = 'DELETE'; ORA_ERR_NUMBER$ ORA_ERR_MESG$ --------------- --------------------------------------------------------------------- 2292 ORA-02292: integrity constraint (TEST.DEST_CHILD_DEST_FK) violated - child record found 2292 ORA-02292: integrity constraint (TEST.DEST_CHILD_DEST_FK) violated - child record found 2 rows selected.
四、后记
1、DML error logging特性使用了自治事务,因此不论当前的主事务是提交或回滚,其产生的错误信息都将记录在对应的日志表。
2、DML error logging使得错误处理得以高效实现,尽管如此,如果在操作中,很多表需要DML操作,尤其是数据迁移时,使得每一个表都
需要创建一个对应的日志表。做了一个测试,可以将日志表的一些基表列删除,保留主要列,日志依然可以成功记录以缩小日志大小。
3、能否将多张日志表合并到一张日志表,然后每一行数据中添加对应的表名以及主键等信息以鉴别错误,这样子的话,仅仅用少量的日志
表即可实现记录多张表上的DML error。这个还没有来得及测试,This is a question。
五、使用FORALL 的SAVE EXCEPTIONS子句示例
FORALL 之 SAVE EXCEPTIONS 子句应用一例