I usually suggest having a second history_x
table for table x
. history_x
in this scenario is nearly identical to x
; it differs in that it's copy of x
's primary key is not primary (and not auto-incrementing even if 'x''s is), and it has it's own primary key and sometimes some sort of addition changed_when
datetime field.
Then two triggers are made:
AFTER INSERT ON x
basically just clones a new row in x
to history_x
AFTER UPDATE ON x
just clones the new state of row x
to history_x
How to handle DELETE varies. Often, if you're going as far as to actually delete the x
record, the corresponding history records can be deleted with it. If you're just flagging the x
as "retired", that is covered by the UPDATE handling. If you need to preserve the history after a delete, you can just add a x_deleted
"flag" field and a DELETE trigger that clones the last state of the row, but sets the x_deleted
flag in history to "true".
Also, this obviously doesn't track PK changes to x
, but could if history_x
has two copies of x
's PK; one would be the historical PK value captured by the triggers with the rest of the fields, and the second would be bound to a foreign key that would cascade all the old history to reference the new key.
Edit: If you can take advantage of the semi-global nature of session/@ variables, you can even add information such as who made the change; but often connection pooling can interfere with that (each connection is it's own session).
Edit#2/Warning: If you're storing large data such as BLOBs or large TEXT fields, they should probably NOT be cloned every update.
Oh yeah, the "changed_when" data can also be more useful if expressed as a valid_from
and valid_until
pair of fields. valid_until
should be null for the newest history record, and when a new history record in added the previous newest should have it's valid_until
field set. changed_when
is enough for a log, but if you need to actually use the old values WHERE ? >= valid_from and ? < valid_until
is a lot easier than WHERE valid_until < ? ORDER BY valid_until DESC LIMIT 1