/* Here is a method for logging the changes to an xml table. The script demonstrates this with the commonly used purchaseorder example. Author: Trevor Fairhurst, trevgf@yahoo.com Date: 6-Apr-2005 Requires: Oracle 10g or up */ -- Step 1 create a sequence for listing the transactions. The below code checks it doesn't exist before creating it. DECLARE v_cnt NUMBER; begin Select count (*) into v_cnt from user_sequences where sequence_name = 'PURCHASEORDER_LOG_SEQ' ; IF v_cnt = 0 then Execute immediate ( 'CREATE SEQUENCE PURCHASEORDER_LOG_SEQ NOCACHE ORDER' ); END IF; end; / -- Now Create or Recreate our logging table. For the purposes of the demonstartion -- we will seperately log some elements as well as the whole xml doc. DECLARE v_cnt NUMBER; begin Select count (*) into v_cnt from user_tables where table_name = 'PURCHASEORDER_LOG' ; IF v_cnt != 0 then Execute immediate ( 'DROP TABLE PURCHASEORDER_LOG' ); END IF; end; / purge recyclebin; create table PURCHASEORDER_LOG as select PURCHASEORDER_LOG_SEQ.nextval LOG_SEQ, rowid PURCHASEORDER_rowid, sys_nc_rowinfo$ PURCHASEORDER_xmldata from PURCHASEORDER where rownum < 0.8; alter table PURCHASEORDER_LOG add REFERENCE varchar2(30); alter table PURCHASEORDER_LOG add USERNAME varchar2(128); alter table PURCHASEORDER_LOG add operation_type varchar2(1); -- Put this column in for use with procedures detailed later alter table PURCHASEORDER_LOG add operation_applied varchar2(1); CREATE OR REPLACE TRIGGER "TRIG_LOG_PURCHASEORDER" AFTER INSERT OR UPDATE OR DELETE ON "PURCHASEORDER" FOR EACH ROW DECLARE v_operation VARCHAR2(1); XMLDATA xmltype; v_REFERENCE VARCHAR2(1000); v_USER VARCHAR2(1000); BEGIN IF INSERTING THEN v_operation := 'I'; ELSIF UPDATING THEN v_operation := 'U'; ELSIF DELETING THEN v_operation := 'D'; END IF; IF INSERTING OR UPDATING THEN XMLDATA := :new.sys_nc_rowinfo$; select extractValue(XMLDATA, 'PurchaseOrder/Reference') into v_REFERENCE from dual ; select extractValue(XMLDATA, 'PurchaseOrder/User') into v_USER from dual ; INSERT INTO PURCHASEORDER_LOG values ( PURCHASEORDER_LOG_SEQ.nextval, :new.rowid, XMLDATA, v_REFERENCE, v_USER, v_operation, 'N' ); ELSIF DELETING THEN XMLDATA := :old.sys_nc_rowinfo$; select extractValue(XMLDATA, 'PurchaseOrder/Reference') into v_REFERENCE from dual ; select extractValue(XMLDATA, 'PurchaseOrder/User') into v_USER from dual ; INSERT INTO PURCHASEORDER_LOG values ( PURCHASEORDER_LOG_SEQ.nextval, :old.rowid, XMLDATA, v_REFERENCE, v_USER, v_operation, 'N' ); END IF; END TRIG_LOG_PURCHASEORDER; / -- The following will need to be changed to match your schema location insert into purchaseorder values (' Feed Me NOW SVOLLMAN 1967-08-13 Sarah J. Bell SBELL S30 Sarah J. Bell
400 Oracle Parkway Redwood Shores CA 94065 USA
650 506 7400
Air Mail A Night to Remember The Unbearable Lightness Of Being Sisters
') / insert into purchaseorder values (' Ref53223 QVOLLMAN 1967-11-13 Lara J. Bull SBELL S30 Sarah J. Bull
400 Oracle Parkway Red Shores CA 94065 USA
651 506 7400
Air Mail A Night to Remember The Unbearable Lightness Of Being Sisters
') / insert into purchaseorder values (' Ref43223 QPULLMAN 1947-11-13 Lara J. Bull ABELL S30 J. Bull
400 Oracle Parkway Red Shores CAL 94069 USA
651 506 7400
Air Mail A Night to Remember The Unbearable Lightness Of Being Sisters
') / UPDATE purchaseorder t SET value(t) = updateXML(value(t), '/PurchaseOrder/CostCenter/text()', 'Ref23223') WHERE rownum < 2 / UPDATE purchaseorder t SET value(t) = updateXML(value(t), '/PurchaseOrder/CostCenter/text()', 'S' || rownum) / delete from purchaseorder where rownum = 1; commit; -- Good point to check the log. -- An example of how this may be used, needing to repeat the changes against a copy of the table. -- For instance with a very large xml table running an insert into as select * from with updates -- still occuring on the primary table, this can be used to apply the updates to the secondry table. CREATE TABLE PURCHASEORDER2 AS SELECT * FROM PURCHASEORDER; CREATE OR REPLACE PROCEDURE "CPRS"."PURCHASEORDERMERGE" IS w_cursor_id integer; sql_text varchar2(4000); cursor get_op is select * from PURCHASEORDER_LOG where OPERATION_APPLIED = 'N' order by LOG_SEQ ; begin w_cursor_id:=dbms_sql.open_cursor; FOR op_rec in get_op LOOP IF op_rec.operation_type = 'I' THEN sql_text := 'INSERT INTO PURCHASEORDER2 SELECT PURCHASEORDER_xmldata from PURCHASEORDER_LOG WHERE LOG_SEQ = '||op_rec.log_seq ; insert into test values (sql_text); commit; execute immediate (sql_text); ELSIF op_rec.operation_type = 'U' THEN sql_text := 'delete from PURCHASEORDER2 WHERE (existsNode(object_value, ''/PurchaseOrder[Reference="'||op_rec.Reference||'" and User="'||op_rec.USERNAME||'"]'')=1 )'; insert into test values (sql_text); commit; execute immediate ( sql_text); sql_text := 'INSERT INTO PURCHASEORDER2 SELECT PURCHASEORDER_xmldata from PURCHASEORDER_LOG WHERE LOG_SEQ = '||op_rec.log_seq ; insert into test values (sql_text); commit; execute immediate (sql_text); ELSIF op_rec.operation_type = 'D' THEN sql_text := 'delete from PURCHASEORDER2 WHERE (existsNode(object_value, ''/PurchaseOrder[Reference="'||op_rec.Reference||'" and User="'||op_rec.USERNAME||'"]'')=1 )'; insert into test values (sql_text); commit; execute immediate (sql_text); END IF; UPDATE PURCHASEORDER_LOG SET OPERATION_APPLIED = 'Y' WHERE LOG_SEQ = op_rec.log_seq ; END LOOP; dbms_sql.close_cursor(w_cursor_id); end PURCHASEORDERMERGE ; / -- Further options, by recording the old data for an update or insert it is a small exercise to modify the -- above code to creare a procedure to rewind the changes made to an xml table. Very useful in tests run against -- XML datawarehoses