How to Store Historical Records in a History Table in SQL Server

How to store historical records in a history table in SQL Server

Logging changes is something I've generally done using triggers on a base table to record changes in a log table. The log table has additional columns to record the database user, action and date/time.

create trigger Table-A_LogDelete on dbo.Table-A
for delete
as
declare @Now as DateTime = GetDate()
set nocount on
insert into Table-A-History
select SUser_SName(), 'delete-deleted', @Now, *
from deleted
go
exec sp_settriggerorder @triggername = 'Table-A_LogDelete', @order = 'last', @stmttype = 'delete'
go
create trigger Table-A_LogInsert on dbo.Table-A
for insert
as
declare @Now as DateTime = GetDate()
set nocount on
insert into Table-A-History
select SUser_SName(), 'insert-inserted', @Now, *
from inserted
go
exec sp_settriggerorder @triggername = 'Table-A_LogInsert', @order = 'last', @stmttype = 'insert'
go
create trigger Table-A_LogUpdate on dbo.Table-A
for update
as
declare @Now as DateTime = GetDate()
set nocount on
insert into Table-A-History
select SUser_SName(), 'update-deleted', @Now, *
from deleted
insert into Table-A-History
select SUser_SName(), 'update-inserted', @Now, *
from inserted
go
exec sp_settriggerorder @triggername = 'Table-A_LogUpdate', @order = 'last', @stmttype = 'update'

Logging triggers should always be set to fire last. Otherwise, a subsequent trigger may rollback the original transaction, but the log table will have already been updated. This is a confusing state of affairs.

What is the best way to store historical data in SQL Server 2005/2008?

it DEPENDS on the applications usage patterns... If usage patterns indicate that the historical data will be queried more often than the current values, then put them all in one table... But if Historical queries are the exception, (or less than 10% of the queries), and the performance of the more common current value query will suffer from putting all data in one table, then it makes sense to separate that data into it's own table...

How to Store Historical Data

Supporting historical data directly within an operational system will make your application much more complex than it would otherwise be. Generally, I would not recommend doing it unless you have a hard requirement to manipulate historical versions of a record within the system.

If you look closely, most requirements for historical data fall into one of two categories:

  • Audit logging: This is better off done with audit tables. It's fairly easy to write a tool that generates scripts to create audit log tables and triggers by reading metadata from the system data dictionary. This type of tool can be used to retrofit audit logging onto most systems. You can also use this subsystem for changed data capture if you want to implement a data warehouse (see below).

  • Historical reporting: Reporting on historical state, 'as-at' positions or analytical reporting over time. It may be possible to fulfil simple historical reporting requirements by quering audit logging tables of the sort described above. If you have more complex requirements then it may be more economical to implement a data mart for the reporting than to try and integrate history directly into the operational system.

    Slowly changing dimensions are by far the simplest mechanism for tracking and querying historical state and much of the history tracking can be automated. Generic handlers aren't that hard to write. Generally, historical reporting does not have to use up-to-the-minute data, so a batched refresh mechanism is normally fine. This keeps your core and reporting system architecture relatively simple.

If your requirements fall into one of these two categories, you are probably better off not storing historical data in your operational system. Separating the historical functionality into another subsystem will probably be less effort overall and produce transactional and audit/reporting databases that work much better for their intended purpose.

Storing Historical Data using History table in ASP.Net Core

For simple time-series data, keeping a separate table with a copy of the modifiable fields is a perfectly valid approach. Unfortunately, in your case you would also need to make a copy of each of the linked tables as well, so that you can maintain consistent foreign keys - e.g. DocumentHistory, ConLineHistory, LegHistory. It's a lot of repetitive code. And then you have to consider, what happens to all the historical records when the schema changes?

Personally, I would store this information as json, in a text column. All the fields that you search on should be in sql, so that you can index it, but the rest can be serialized to a json string:

public partial class ConHistory 
{
public Guid Id { get; set; }
public Guid ConId { get; set; }
public int TransactionId { get; set; }
public Guid ModifiedByUser { get; set; }
public DateTime Date { get; set; }

// Serialize the rest of the `ConHistory` fields to a json object, and store them here
public string Json { get; set; }
}

Sql also has the JSON_VALUE function, and there are some examples of using this in Entity Framework, if you do need to get values out of the json string for querying.

How to store historical master data in SQL Server?

You could use a Temporal Table to automatically capture historical versions of rows and enable you to query "as-of" previous points in time.

Creating a history table without using triggers

If your using tsql /SQL server and you can't use triggers, which is the only sure way to get every change, maybe use a stored procedure that is scheduled in job to run every x amount of time, the stored procedure using a MERGE statement with the two tables to get new records or changes. I would not suggest this if you need every single change without question.

CREATE TABLE dbo.TableA (id INT, Column1 nvarchar(30))
CREATE TABLE dbo.TableA_History (id INT, Column1 nvarchar(30), TimeStamp DateTime)

(this code isn't production, just the general idea)
Put the following code inside a stored procedure and use a Sql Server Job with a schedule on it.

MERGE INTO dbo.TableA_History
USING dbo.TableA
ON TableA_History.id = TableA.id AND TableA_History.Column1 = TableA.Column1
WHEN NOT MATCHED BY TARGET THEN
INSERT (id,Column1,TimeStamp) VALUES (TableA.id,TableA.Column1,GETDATE())

So basically if the record either doesn't exist or doesn't match meaning a column changed, insert the record into the history table.

Database structure for storing historical data

When I've encountered such problems one alternative is to make the order the history table. Its functions the same but its a little easier to follow

orders
------
orderID
customerID
address
City
state
zip



customers
---------
customerID
address
City
state
zip

EDIT: if the number of columns gets to high for your liking you can separate it out however you like.

If you do go with the other option and using history tables you should consider using bitemporal data since you may have to deal with the possibility that historical data needs to be corrected. For example Customer Changed his current address From A to B but you also have to correct address on an existing order that is currently be fulfilled.

Also if you are using MS SQL Server you might want to consider using indexed views. That will allow you to trade a small incremental insert/update perf decrease for a large select perf increase. If you're not using MS SQL server you can replicate this using triggers and tables.

Storing historical changes to data in SQL for reporting

Temporal tables allows you to track and analyze the full history of changes in your data, without the need for custom coding. Saving any historical data will use extra database storage and other CPU resources because every CRUD operations will be saved as a separate entity in temporal table.

Depending on whether you are starting new development or upgrading existing application, you will either create temporal tables or modify existing ones by adding temporal attributes. Perform these actions on either SQL Server Management Studio (SSMS) or as Transact-SQL statement.

  1. In SSMS, use Tables -> New -> System-Versioned Table... path to create the temporal tables for existing tables.

  1. Create temporal table using T-SQL.

Note: The mandatory elements of every temporal table are the PERIOD definition and the SYSTEM_VERSIONING clause with a reference to another user table that will store historical row versions:

CREATE TABLE WebsiteUserInfo
(
[UserID] int NOT NULL PRIMARY KEY CLUSTERED
, [UserName] nvarchar(100) NOT NULL
, [PagesVisited] int NOT NULL
, [ValidFrom] datetime2 (0) GENERATED ALWAYS AS ROW START
, [ValidTo] datetime2 (0) GENERATED ALWAYS AS ROW END
, PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.WebsiteUserInfoHistory));

You can also extend the existing table to become temporal, as shown in the following example:

ALTER TABLE WebsiteUserInfo
ADD
ValidFrom datetime2 (0) GENERATED ALWAYS AS ROW START HIDDEN
constraint DF_ValidFrom DEFAULT DATEADD(SECOND, -1, SYSUTCDATETIME())
, ValidTo datetime2 (0) GENERATED ALWAYS AS ROW END HIDDEN
constraint DF_ValidTo DEFAULT '9999.12.31 23:59:59.99'
, PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);

ALTER TABLE WebsiteUserInfo
SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.WebsiteUserInfoHistory));
GO

CREATE CLUSTERED COLUMNSTORE INDEX IX_WebsiteUserInfoHistory
ON dbo.WebsiteUserInfoHistory
WITH (DROP_EXISTING = ON);

Source: https://docs.microsoft.com/en-us/azure/azure-sql/temporal-tables



Related Topics



Leave a reply



Submit