While @WW. answer is a good answer another way is to make a version column and keep all your versions in the same table.
For one table approach you either:
- Use a flag to indicate the latest ala Word Press
- OR do a nasty greater than version
outer join
.
An example SQL of the outer join
method using revision numbers is:
SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL
AND tc.path = '/stuff' -- path in this case is our natural id.
The bad news is the above requires an outer join
and outer joins can be slow. The good news is that creating new entries is theoretically cheaper because you can do it in one write operation with out transactions (assuming your database is atomic).
An example making a new revision for '/stuff'
might be:
INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now()
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL
AND tc.path = '/stuff' -- {path}
)
We insert by using the old data. This is particularly useful if say you only wanted to update one column and avoid optimistic locking and or transactions.
The flag approach and history table approach requires two rows to be inserted/updated.
The other advantage with the outer join
revision number approach is that you can always refactor to the multiple table approach later with triggers because your trigger should essentially to do something like the above.