Design a Table to Keep Historical Changes in Database

A classical problem: how to design a table so that it can keep historical changes in database?

Photo by Fredy Jacob on Unsplash

Imagine you have a demands table, which has the following fields:


Here comes the business requirements: the users want to keep the historical changes for the demands table for future usage, e.g. data analysis / auditing.

How to design such a table which can keep the historical changes?

I have 3 different approaches to solve the problem.

1. Use Effective From and Effective To Fields

I DON’T like this approach but I’ve seen people practicing it, struggling with it but still insisting to do it. Hence, I would like to demonstrate why it’s a bad approach doing so.

The idea is to have EffectiveFrom and EffectiveTo fields inside of the tables, which looks like this:


The EffectiveFrom and EffectiveTo fields are for validating the effectiveness of the records. For example, today is 2020-06–09, if the record has the EffectiveFrom being 2020-06-01 and EffectiveTo being 2020-06-30, then this record is active. If today's date is out of the range, then the record is inactive / disabled.

In that case, the effectiveness of the record is indirectly determined by these 2 fields. Hence, the following situation may happen

Once the value is changed, the EffectiveTo field is set to a previous date, and a duplicated record is inserted into the table which contains the latest information, and DIFFERENT ID.

Such design actually meet the requirement of retaining historical changes in database, however, it’s actually bad. By duplicating the record, the ID is changed from 100 to 101. As a developer, we know that the 2 demands are the same demand, it's just one is newer and the other one is older. But now there are 2 IDs to represent 1 record, which becomes a disaster for other tables linking to it.

For example, if you have a DemandDetails table containing the demands details, which needs Demand ID to establish the relationship (foreign key), you would end up into troubles.
Let's say the client1 demanded 5 product of phone1 in demand 100. Now the demand is updated to 101, but the record in DemandDetails is still linking to 100.

To solve such an issue, you need to do more queries to identify which is the latest active ID and then link to it, which significantly increase the complexity.

And it’s bad.

2. Use a History Table

What if I want to keep a whole record as a history but doesn’t want to affect my actual table? Then you may want to create a history table for Demand, which has the same fields as Demand table itself.


The same example happens here: there is a demand looks like this:

Then a user with id 20 modifies the quantity to 2 and price to 1000.

Now, you only have to insert one record, which is a duplicated record of the one in Demand table, into DemandHistory table, which looks like this:

Lastly, update the original record in Demand table into.

In this case, you are trying to save the old record completely into another table and then apply changes in the original table. It’s better if the users want to have a full picture of how the record is changing.

However, the down side of this approach is that, redundant information is stored. For example, if you have a large number of fields, but only one or two fields are updated every time, it’s actually a huge waste of space.

3. Use an Audit Table

A better solution is to create an audit table to record every single change in every field, which saves the spaces by eliminating redundant information. The table looks like this:


For example, there is a demand looks like this:

Then a user with id 20 modifies the quantity to 2 and price to 1000.

Hence, there are 3 fields changed, Quantity and Price. Respectively, there should be 2 records added into the audit table as shown below.

Lastly, update the original record in Demand table into

In that case, it’s very easy to query for field changes. For example, if I want to find out all the price changes for the demand with ID 100, I can just query by

`Select * from DemandAudit where id = 100 and field = "price"

The downside of this approach is the possible huge increase of records. Since every change in different fields is one record in the Audit table, it may grow drastically fast such as tens of changes resulting in hundreds of audit records. In this case, table indexing plays a vital role for enhancing the querying performance.


In this article, I talked about 3 approaches to keep historical changes in database, which are effective from and effective to fields, history table and audit table.

Effective from and effective to fields is NOT recommend to use, and I recommend to use history table or audit table to solve the problem depending on cases.

Here is the comparison:

Audit Table


  • Record only data in changed fields
  • Not affecting actual table
  • No Redundant information


  • Number of records may increase significantly
  • Suitable for: actual table has many fields, but often only a few fields are changes

History Table


  • Record the entire old record
  • Simple query to get the complete history
  • Not affecting actual table


  • Redundant information is stored
  • Suitable for: A lot of fields are changed in one time
  • Suitable for: Generating a change report with full record history is needed.

Should we finally save our history? In fact, we do. We can therefore see the path taken by an entity to arrive at what it is today.

Photo by Paul Gaudriault on Unsplash