Can I copy :OLD and :NEW pseudo-records in/to an Oracle stored procedure?
I have an AFTER INSERT OR UPDATE OR DELETE
trigger that I'm writing to store every record revision that occurs in a c开发者_运维问答ertain table, by copying the INSERT
and UPDATE
:NEW
values into a mirror table, and for DELETE
the :OLD
values.
I could un-clutter my code considerably by conditionally passing either the :NEW
or :OLD
record into a procedure which would then do the insert into my history table. Unfortunately I cannot seem to find a way to pass the entire :OLD
or :NEW
record.
Am I missing something or is there no way to avoid enumerating every :NEW
and :OLD
column as I invoke my insert procedure?
I want to do the following:
DECLARE
PROCEDURE LOCAL_INSERT(historyRecord in ACCT.ACCOUNTS%ROWTYPE) IS
BEGIN
INSERT INTO ACCT.ACCOUNTS_HISTORY (ID, NAME, DESCRIPTION, DATE) VALUES (historyRecord.ID, historyRecord.NAME, historyRecord.DESCRIPTION, SYSDATE);
END;
BEGIN
IF INSERTING OR UPDATING THEN
LOCAL_INSERT(:NEW);
ELSE --DELETING
LOCAL_INSERT(:OLD);
END IF;
END;
But I'm stuck doing this:
DECLARE
PROCEDURE LOCAL_INSERT(id in ACCT.ACCOUNTS.ID%TYPE,
name in ACCT.ACCOUNTS.NAME%TYPE,
description in ACCT.ACCOUNTS.DESCRIPTION%TYPE) IS
BEGIN
INSERT INTO ACCT.ACCOUNTS_HISTORY (ID, NAME, DESCRIPTION, DATE) VALUES (id, name, description, SYSDATE);
END;
BEGIN
IF INSERTING OR UPDATING THEN
LOCAL_INSERT(:NEW.ID, :NEW.NAME, :NEW.DESCRIPTION);
ELSE --DELETING
LOCAL_INSERT(:OLD.ID, :OLD.NAME, :OLD.DESCRIPTION);
END IF;
END;
Okay, so it doesn't look like a big difference, but this is just an example with 3 columns rather than dozens.
It isn't. You have to do it yourself through enumeration.
The reasons it can't/doesn't work automatically include:
the
:old
and:new
are default conventions; you can name the:old
and:new
references to be whatever you want through theREFERENCING
clause of theCREATE TRIGGER
statement.you'd have to have a public declaration of a type (through
CREATE TYPE
or through a package declaration) to be able to use it as an argument to another piece of code.trigger code is interpreted code, not compiled code.
I don't think it's possible like that. Documentation doesn't mention anything like that.
This would certainly cost performance, but you could try to define your trigger AFTER INSERT
and another one BEFORE UPDATE OR DELETE
, and in the trigger do something like:
SELECT *
INTO rowtype_variable
FROM accounts
WHERE accounts.id = :NEW.id; -- :OLD.id for UPDATE and DELETE
and then call your procedure with that rowtype_variable
.
Use SQL to generate the SQL;
select ' row_field.'||COLUMN_NAME||' := :new.'||COLUMN_NAME||';' from
ALL_TAB_COLUMNS cols
where
cols.TABLE_NAME = 'yourTableName'
order by cols.column_name.
Then copy and paste output.
If you use AFTER trigger you can use rowid as parameter to call procedure
insert into t_hist
select * from t where rowid = r;
If you use BEFORE trigger you will get ORA-04091 mutating table, BUT you solution can be (http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm):
- Don't use triggers - The best way to avoid the mutating table error is not to use triggers. While the object-oriented Oracle provides "methods" that are associated with tables, most savvy PL/SQL developers avoid triggers unless absolutely necessary.
- Use an "after" or "instead of" trigger - If you must use a trigger, it's best to avoid the mutating table error by using an "after" trigger, to avoid the currency issues associated with a mutating table. For example, using a trigger ":after update on xxx", the original update has completed and the table will not be mutating.
- Re-work the trigger syntax - Dr. Hall has some great notes on mutating table errors, and offers other ways to avoid mutating tables with a combination of row-level and statement-level triggers.
- Use autonomous transactions - You can avoid the mutating table error by marking your trigger as an autonomous transaction, making it independent from the table that calls the procedure.
精彩评论