Oracle - Triggers to create a history row on update

后端 未结 7 681
天命终不由人
天命终不由人 2021-02-04 16:28

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

7条回答
  •  感动是毒
    2021-02-04 16:34

    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.

提交回复
热议问题