title: Oracle - Triggers to create a history row on update
body: <p>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 efficient, or easier to maintain (anything that does any of those 3 would be most welcome).  When we perform an update, a history row is created that is a copy of the <strong>current</strong> row, and the current row's values are then updated.  The result being that we have a history record of how the row was before it was updated.</p>
  
  <p>Reasoning:  We have to be compliant with a number of federal rules, and went this route to have a full audit history of everything, as well as we can look at the database at any point in time and see how things looked (future requirement).  <strong>For similar reasons, I cannot change how history is recorded</strong>...any solution must result in the same data as the current triggers create.</p>
  
  <p>Here's what the current triggers look like for the <code>Contact</code> Table:<br>
  <em>(stripped useless fields for brevity, the number of fields doesn't matter)</em></p>
  
  <p>Before update (each row):</p>
  
  <pre><code>DECLARE
       indexnb number;
  BEGIN
    :new.date_modified := '31-DEC-9999';
    indexnb := STATE_PKG.newCONTACTRows.count + 1;
    :new.date_start := sysdate;
    :new.version := :old.version + 1;
    state_pkg.newCONTACTRows(indexnb).ID := :old.ID;
    state_pkg.newCONTACTRows(indexnb).PREFIX := :old.PREFIX;
    state_pkg.newCONTACTRows(indexnb).FIRST_NAME := :old.FIRST_NAME;
    state_pkg.newCONTACTRows(indexnb).MIDDLE_NAME := :old.MIDDLE_NAME;
    state_pkg.newCONTACTRows(indexnb).LAST_NAME := :old.LAST_NAME;
    --Audit columns after this
    state_pkg.newCONTACTRows(indexnb).OWNER := :old.OWNER;
    state_pkg.newCONTACTRows(indexnb).LAST_USER := :old.LAST_USER;
    state_pkg.newCONTACTRows(indexnb).DATE_CREATED := :old.DATE_CREATED;
    state_pkg.newCONTACTRows(indexnb).DATE_MODIFIED := sysdate;
    state_pkg.newCONTACTRows(indexnb).VERSION := :old.VERSION;
    state_pkg.newCONTACTRows(indexnb).ENTITY_ID := :old.id;
    state_pkg.newCONTACTRows(indexnb).RECORD_STATUS := :old.RECORD_STATUS;
    state_pkg.newCONTACTRows(indexnb).DATE_START := :old.DATE_START;
  END;
  </code></pre>
  
  <p>Before update (once for all rows):</p>
  
  <pre><code>BEGIN
    state_pkg.newCONTACTRows := state_pkg.eCONTACTRows;
  END;
  </code></pre>
  
  <p>After update (once for all rows):</p>
  
  <pre><code>DECLARE
  BEGIN
    for i in 1 .. STATE_PKG.newCONTACTRows.COUNT loop
      INSERT INTO "CONTACT" (
        ID, 
        PREFIX, 
        FIRST_NAME, 
        MIDDLE_NAME, 
        LAST_NAME, 
        OWNER, 
        LAST_USER, 
        DATE_CREATED, 
        DATE_MODIFIED, 
        VERSION, 
        ENTITY_ID, 
        RECORD_STATUS, 
        DATE_START)
      VALUES (
        CONTACT_SEQ.NEXTVAL, 
        state_pkg.newCONTACTRows(i).PREFIX,
        state_pkg.newCONTACTRows(i).FIRST_NAME,
        state_pkg.newCONTACTRows(i).MIDDLE_NAME,
        state_pkg.newCONTACTRows(i).LAST_NAME,
        state_pkg.newCONTACTRows(i).OWNER,
        state_pkg.newCONTACTRows(i).LAST_USER,
        state_pkg.newCONTACTRows(i).DATE_CREATED,
        state_pkg.newCONTACTRows(i).DATE_MODIFIED,
        state_pkg.newCONTACTRows(i).VERSION,
        state_pkg.newCONTACTRows(i).ENTITY_ID,
        state_pkg.newCONTACTRows(i).RECORD_STATUS,
        state_pkg.newCONTACTRows(i).DATE_START
      );
    end loop;
  END;
  </code></pre>
  
  <p>The package defined as (trimmed, full version is just copy of this per table):</p>
  
  <pre><code>PACKAGE STATE_PKG IS
    TYPE CONTACTArray IS TABLE OF CONTACT%ROWTYPE INDEX BY BINARY_INTEGER; 
    newCONTACTRows CONTACTArray; 
    eCONTACTRows CONTACTArray;
  END;
  </code></pre>
  
  <h2>The current result</h2>
  
  <p>Here's a resulting history sample:</p>
  
  <pre><code>ID    First Last   Ver  Entity_ID  Date_Start              Date_Modified  
  1196  John  Smith  5    0          12/11/2009 10:20:11 PM  12/31/9999 12:00:00 AM
  1201  John  Smith  0    1196       12/11/2009 09:35:20 PM  12/11/2009 10:16:49 PM
  1203  John  Smith  1    1196       12/11/2009 10:16:49 PM  12/11/2009 10:17:07 PM
  1205  John  Smith  2    1196       12/11/2009 10:17:07 PM  12/11/2009 10:17:19 PM
  1207  John  Smith  3    1196       12/11/2009 10:17:19 PM  12/11/2009 10:20:00 PM
  1209  John  Smith  4    1196       12/11/2009 10:20:00 PM  12/11/2009 10:20:11 PM
  </code></pre>
  
  <p>Each history record has an Entity_ID that's the ID of the current row, the Date_Start on the new record matches the Date_Modified of the last history row.  This allows us to do queries like <code>Where Entity_ID = :id Or ID = :id And :myDate &lt; Date_Modified And :myDate &gt;= Date_Start</code>.  History can be fetched by <code>Entity_ID = :current_id</code>.</p>
  
  <p><strong>Is there a better approach, hopefully more maintainable/flexible to do this?</strong>  The concept is simple, when updating a row, copy it to the same table via an insert with the old values, then update the current row...but actually doing that, I have yet to find a simpler way.  I'm hoping someone much trickier/wiser in Oracle has a better approach to this.  Speed doesn't matter much, we're 99% reads 1% writes like most web applications, and all bulk operations are inserts, not updates which wouldn't create any history.</p>
  
  <p>If anyone has any ideas to simplify the maintenance on this, I'd be extremely appreciative, thanks!</p>