Temporal tables

What is a Temporal Table?

  • A system versioned user table that maintains the entire history of data changes
  • Introduced SQL:2011, proposed 1992
  • Active data table is mirrored by historical mirror table controlled by database

Temporal Table Details

  • Two timestamps columns are required to establish record validity for a period
  • Tracking is at record level
  • Designed to be compatible with current data models, code, and tools
  • Supported by all major databases (Postgres, Microsoft SQL Server, MariaDB, Oracle, IBM DB2, etc)

Why Temporal Tables?

  • Reconstruction of application at any point is fully realizable
  • Auditing data changes
  • Soft deletes are built in
  • Investigation and data analysis is immediately available
  • Application tuning for hot/cold storage
Images from Microsoft

Example structure: SQL 2016

  • Two timestamps columns are required to establish record validity for a period
  • Tracking is at record level
  • Designed to be compatible with current data models, code, and tools
CREATE TABLE [dbo].[HappyPlaces]
([Id] [INT] NOT NULL IDENTITY(1,1) CONSTRAINT [PK_HappyPlaces_Id] PRIMARY KEY,
[Name] NVARCHAR(100) NULL,
[ValidFrom] DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
[ValidTo] DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME ([ValidFrom],[ValidTo]))
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[HappyPlacesHistory]));

Insert some data

INSERT INTO [dbo].[HappyPlaces] ([Name])
VALUES 
  ('Ilulissat'),
  ('Bora-Bora');
Images from Microsoft

Query

SELECT *
FROM [dbo].[HappyPlaces]
  • Current SQL query code on active table remains the same
  • Expanded query space to access temporal records with operators on Timestamp columns

Results

[
  {
    "Id": 1,
    "Name": "Ilulissat",
    "ValidFrom": "2021-05-25T03:17:33.6257877",
    "ValidTo": "9999-12-31T23:59:59.9999999"
  },
  {
    "Id": 2,
    "Name": "Bora-Bora",
    "ValidFrom": "2021-05-25T03:17:33.6257877",
    "ValidTo": "9999-12-31T23:59:59.9999999"
  }
]

Query Expanded

Delete a record (poor Ilulissat melted)

DELETE 
FROM [dbo].[HappyPlaces]
WHERE [Name] = 'Ilulissat';

Query the table with normal syntax

SELECT *
FROM [dbo].[HappyPlaces];

Results

[
  {
    "Id": 2,
    "Name": "Bora-Bora",
    "ValidFrom": "2021-05-25T03:17:33.6257877",
    "ValidTo": "9999-12-31T23:59:59.9999999"
  }
]

It's gone, but is it really, use the expanded temporal keyword syntax to get the full history against the common table

SELECT *
FROM [dbo].[HappyPlaces]
FOR SYSTEM_TIME
ALL;

Results

[
  {
    "Id": 2,
    "Name": "Bora-Bora",
    "ValidFrom": "2021-05-25T03:17:33.6257877",
    "ValidTo": "9999-12-31T23:59:59.9999999"
  },
  {
    "Id": 1,
    "Name": "Ilulissat",
    "ValidFrom": "2021-05-25T03:17:33.6257877",
    "ValidTo": "2021-05-25T03:22:48.1764475"
  }
]

Ilulissat is in the history which is the union of the current active table and it's history table.  Look carefully at the valid datetimes corresponding to when the Ilulissat record was active in the primary table.  This will give you a complete history of any particular record

Awesome!  This is only one option under the temporal keywords explore the others   and see what you come up with

  • AS OF <date_time>
  • FROM <start_date_time> TO <end_date_time>
  • BETWEEN <start_date_time> AND <end_date_time>
  • CONTAINED IN (<start_date_time> , <end_date_time>)
  • ALL

Convert a table to temporal

Example table (note you must have a primary key defined for the table)

CREATE TABLE [dbo].[HappyPlaces](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[Name] [nvarchar](100) NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[HappyPlaces] ADD  CONSTRAINT [PK_HappyPlaces_Id] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)
GO

Add the temporal columns and the history table

ALTER TABLE [dbo].[HappyPlaces]
	ADD	[ValidFrom] [DATETIME2](7) GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),
		[ValidTo] [DATETIME2](7) GENERATED ALWAYS AS ROW END NOT NULL DEFAULT CAST('9999-12-31 23:59:59.9999999' AS DATETIME2),
		PERIOD FOR SYSTEM_TIME ([ValidFrom],[ValidTo]);
GO

ALTER TABLE [dbo].[HappyPlaces]
	SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[HappyPlacesHistory]));
GO

The temporal accords are in effect now :)