First, we currently have the behavior that\'s desired, but it\'s not trivial to maintain when any changes to the database are needed. I\'m looking for anything simpler, more ef
In case someone has the same highly specialized case we do (Linq access making single table history much cleaner/easier, this is what I ended up doing to simplify what we have, welcome any improvements....this is just a script that will run whenever the database changes, regenerating the audit triggers, the main change being PRAGMA AUTONOMOUS_TRANSACTION; placing the history generating on an autonomous transaction and not caring about mutation (which doesn't matter for how we audit):
Declare
cur_trig varchar(4000);
has_ver number;
Begin
For seq in (Select table_name, sequence_name
From user_tables ut, user_sequences us
Where sequence_name = replace(table_name, '_','') || '_SEQ'
And table_name Not Like '%$%'
And Exists (Select 1
From User_Tab_Columns utc
Where Column_Name = 'ID' And ut.table_name = utc.table_name)
And Exists (Select 1
From User_Tab_Columns utc
Where Column_Name = 'DATE_START' And ut.table_name = utc.table_name)
And Exists (Select 1
From User_Tab_Columns utc
Where Column_Name = 'DATE_MODIFIED' And ut.table_name = utc.table_name))
Loop
--ID Insert Triggers (Autonumber for oracle!)
cur_trig := 'CREATE OR REPLACE TRIGGER ' || seq.table_name || 'CR' || chr(10)
|| 'BEFORE INSERT ON ' || seq.table_name || chr(10)
|| 'FOR EACH ROW' || chr(10)
|| 'BEGIN' || chr(10)
|| ' SELECT ' || seq.sequence_name || '.NEXTVAL INTO :new.ID FROM DUAL;' || chr(10)
|| ' IF(:NEW.ENTITY_ID = 0) THEN' || chr(10)
|| ' SELECT sysdate, sysdate, ''31-DEC-9999'' INTO :NEW.DATE_CREATED, :NEW.DATE_START, :NEW.DATE_MODIFIED FROM DUAL;' || chr(10)
|| ' END IF;' || chr(10)
|| 'END;' || chr(10);
Execute Immediate cur_trig;
--History on update Triggers
cur_trig := 'CREATE OR REPLACE TRIGGER ' || seq.table_name || '_HIST' || chr(10)
|| ' BEFORE UPDATE ON ' || seq.table_name || ' FOR EACH ROW' || chr(10)
|| 'DECLARE' || chr(10)
|| ' PRAGMA AUTONOMOUS_TRANSACTION;' || chr(10)
|| 'BEGIN' || chr(10)
|| ' INSERT INTO ' || seq.table_name || ' (' || chr(10)
|| ' DATE_MODIFIED ' || chr(10)
|| ' ,ENTITY_ID ' || chr(10);
For col in (Select column_name
From user_tab_columns ut
Where table_name = seq.table_name
And column_name NOT In ('ID','DATE_MODIFIED','ENTITY_ID')
Order By column_name)
Loop
cur_trig := cur_trig || ' ,' || col.column_name || chr(10);
End Loop;
cur_trig := cur_trig || ') VALUES ( --ID is Automatic via another trigger' || chr(10)
|| ' SYSDATE --DateModified Set' || chr(10)
|| ' ,:old.ID --EntityID Set' || chr(10);
has_ver := 0;
For col in (Select column_name
From user_tab_columns ut
Where table_name = seq.table_name
And column_name NOT In ('ID','DATE_MODIFIED','ENTITY_ID')
Order By column_name)
Loop
cur_trig := cur_trig || ' ,:old.' || col.column_name || chr(10);
If Upper(col.column_name) = 'VERSION' Then
has_ver := 1;
End If;
End Loop;
cur_trig := cur_trig || ');' || chr(10)
|| ':new.DATE_MODIFIED := ''31-DEC-9999'';' || chr(10)
|| ':new.DATE_START := SYSDATE;' || chr(10);
If has_ver = 1 Then
cur_trig := cur_trig || ':new.version := :old.version + 1;' || chr(10);
End If;
cur_trig := cur_trig || 'COMMIT;' || chr(10)
|| 'END;' || chr(10);
Execute Immediate cur_trig;
End Loop;
End;
/
If you can improve, feel free...I've only written a handful of PL/SQL scripts, the need doesn't arise often...probably a lot left to be desired there.
Answer credit to APC for getting me to look at this a bit harder. I don't recommend this history layout unless it its the rest of your model/application/stack extremely well. For this application, we constantly show a mix of history and current, and filtering is far simpler than combining when it comes to a Linq-to-SQL style access. Thanks for all the answers guys, all good suggestions...and when I have more time and am not crunched by a release schedule, this is something I'll revisit to see if it can be improved further.