Temporal tables are cool, but keeping all that history can be stressful. Change Tracking adds overhead to every transaction, and requires Snapshot Isolation to be successful. Change Data Capture can also run into problems scanning the transaction log, depending on transaction volume, etc.
Change Data Capture is also a little unfortunate in that it doesn’t track schema changes like adding or dropping columns, or changing column data types. Change Tracking doesn’t either, it just tracks the keys of what changed when data is modified.
Temporal Tables are different, because it will add new, and drop removed columns from the history table as they change in the base table. It’ll also push data type changes across. But the history table will not have the various permutations tracked. It just mirrors current state.
Just The Columns, Ma’am
Let’s say your needs aren’t robust enough to need any one of those highly specialized features, or even triggers to move data around when it changes.
You’re perfectly free and able to add the tracking columns that temporal tables use to your base tables, but adding them is far from free. When I added them to the 17 million row Posts table, it took about 40 seconds. My laptop doesn’t suck, either.
To show you a little how it works, let’s create a copy of the Votes table from Stack Overflow.
CREATE TABLE dbo.Votes_Tracked ( Id int NOT NULL, PostId int NOT NULL, UserId int NULL, BountyAmount int NULL, VoteTypeId int NOT NULL, CreationDate datetime NOT NULL, LastModified datetime2 GENERATED ALWAYS AS ROW start NOT NULL CONSTRAINT DF_LastModified DEFAULT (SYSDATETIME()), JunkDate datetime2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL CONSTRAINT DF_JunkDate DEFAULT ('9999-12-31 23:59:59.9999999'), PERIOD FOR SYSTEM_TIME (LastModified, JunkDate), CONSTRAINT PK_Votes_Id PRIMARY KEY CLUSTERED (Id ASC) ); GO
Note that you need two columns to define the “period for system time”, and one of them will always be useless. That’s why I called it JunkDate, and not, like, whatever. But the good news is you can define that column as HIDDEN so that it doesn’t show up in all your queries.
Now we can stick some data in there and see how it works.
INSERT dbo.Votes_Tracked WITH (TABLOCK) ( Id, PostId, UserId, BountyAmount, VoteTypeId, CreationDate ) SELECT Id, PostId, UserId, BountyAmount, VoteTypeId, CreationDate FROM StackOverflow2013.dbo.Votes AS v WHERE v.CreationDate >= '20130101';
The table data looks like this:
If we run an update:
UPDATE v SET v.BountyAmount = 9999 FROM dbo.Votes_Tracked AS v WHERE v.VoteTypeId = 7;
Note that these screen caps were taken without the HIDDEN keyword added to the table definition: that was an after thought recommended by my friend Peter.
Of course, if you remove rows from the table, they’re just gone. You’d still need a trigger to cover deletes, if you need to track those.
And if you want to remove those columns later, it takes a little bit of tweaking.
ALTER TABLE dbo.Votes_Tracked DROP CONSTRAINT DF_LastModified, DF_JunkDate; ALTER TABLE dbo.Votes_Tracked DROP COLUMN JunkDate, LastModified; Msg 13588, Level 16, State 1, Line 63 Column 'JunkDate' in table 'Crap.dbo.Votes_Tracked' cannot be dropped because it is a part of period definition.
Of course, the table isn’t system versioned, so this command will also fail:
ALTER TABLE dbo.Votes_Tracked SET (SYSTEM_VERSIONING = OFF); Msg 13591, Level 16, State 1, Line 66 SYSTEM_VERSIONING is not turned ON for table 'Crap.dbo.Votes_Tracked'.
If you want to remove them, you’ll need to use this:
ALTER TABLE dbo.Votes_Tracked DROP PERIOD FOR SYSTEM_TIME;
Now you’ll be able to remove them.
Is This A Good Idea?
Well, it depends on what you need. If you just need to know when a row changed, and you don’t need to know what changed or who changed it, it can work in a more automated way than triggers. It does require an additional column, which isn’t ideal, but it’s not a gigantic column, and you don’t need to worry about indexing it because the data is junk.
I haven’t found any ridiculous downsides to this, but I’ll keep this updated if I do.
Thanks for reading!
If this is the kind of SQL Server stuff you love learning about, you’ll love my training. I’m offering a 75% discount to my blog readers if you click from here. I’m also available for consulting if you just don’t have time for that and need to solve performance problems quickly.
- How #Temporary Tables Can Cause Plan Cache Pollution In SQL Server
- LOB Data, Recompile, And Too Much tempdb Usage In SQL Server
- When Query Spills Cause tempdb Contention And Performance Issues In SQL Server
- SARGability Week: Using Temp Tables To Fix Non-SARGable Query Performance Problems In SQL Server
3 thoughts on “Tracking Row Changes With Temporal Columns In SQL Server”
A way to implement a ModifiedDate column, that auto-updates.
My idea was to use RowVersion, and a SQLAgent job and an inner join. It worked, but only with reduced precision.
Thank you very much for posting.
Glad you like it! Let me know how it works if you decide to use it.
Comments are closed.