1. Introduction
In modern enterprise systems, data versioning and auditing are essential requirements.
Every business application needs to answer questions like:
“Who modified this record?”
“What was the previous value?”
“When was it changed?”
Traditionally, developers used triggers or manual history tables to track such changes.
While functional, these approaches are error-prone and hard to maintain.
To solve this, SQL Server introduced Temporal Tables (also known as System-Versioned Tables) starting from SQL Server 2016.
Temporal Tables provide built-in data auditing and versioning with minimal coding effort.
2. What Are Temporal Tables?
A temporal table automatically keeps a full history of data changes (INSERT, UPDATE, DELETE) in a separate history table, allowing you to query data as it existed at any point in time.
This feature is also called System-Versioned Temporal Tables because SQL Server manages version history automatically.
3. Why Use Temporal Tables?
| Advantage | Description |
|---|---|
| Automatic Auditing | SQL Server tracks changes automatically; no manual triggers are needed. |
| Data Recovery | You can restore a row to any previous state. |
| Simplified History Queries | Retrieve historical data using simple FOR SYSTEM_TIME syntax. |
| Compliance | Helps meet audit/compliance requirements like ISO, GDPR, etc. |
| Performance Efficient | Optimized by SQL Server engine — minimal overhead. |
4. Basic Concept
A temporal table consists of two tables:
Current Table (System-Versioned Table): Holds current active records.
History Table: Automatically created and maintained by SQL Server, stores old versions.
Each temporal table contains two datetime2 columns:
ValidFrom(Start time)ValidTo(End time)
These columns define the validity period for each record.
5. Technical Workflow (Flowchart)
┌────────────────────────────┐
│ INSERT Data │
└─────────────┬──────────────┘
│
▼
┌────────────────────────┐
│ Current Table Stores │
│ Active Record │
└─────────────┬──────────┘
│
┌─────────────────────┴────────────────────┐
│ UPDATE │
└─────────────────────┬────────────────────┘
│
▼
┌────────────────────────┐
│ Old Record Moved to │
│ History Table │
└─────────────┬──────────┘
│
▼
┌────────────────────────┐
│ New Record Inserted │
│ in Current Table │
└─────────────┬──────────┘
│
▼
┌────────────────────────┐
│ DELETE → Move to │
│ History Table │
└────────────────────────┘
6. Step-by-Step Implementation
Let’s understand temporal tables through a real example.
Step 1: Create a Temporal Table
We’ll create an Employee table that tracks all data changes automatically.
CREATE TABLE Employee
(
EmployeeID INT IDENTITY(1,1) PRIMARY KEY,
EmployeeName NVARCHAR(100),
Designation NVARCHAR(50),
Salary DECIMAL(10,2),
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.EmployeeHistory));
Explanation
ValidFromandValidTomark the time period for which each row is valid.PERIOD FOR SYSTEM_TIMEdefines these columns as the temporal range.SYSTEM_VERSIONING = ONenables temporal tracking.EmployeeHistorywill be automatically created by SQL Server.
Step 2: Insert Data
INSERT INTO Employee (EmployeeName, Designation, Salary)
VALUES ('Rajesh Kumar', 'Software Engineer', 60000.00),
('Amit Patel', 'Project Manager', 85000.00);
Now the Employee table holds two active rows, and the EmployeeHistory table is empty initially.
Step 3: Update Data (Versioning Begins)
UPDATE Employee
SET Salary = 65000.00WHERE EmployeeName = 'Rajesh Kumar';
What happens internally
SQL Server moves the old row (with salary 60000) to EmployeeHistory.
The new record (with salary 65000) remains in the Employee table.
The
ValidFromandValidTotimestamps are updated automatically.
Step 4: Delete Data (Audited Automatically)
DELETE FROM Employee
WHERE EmployeeName = 'Amit Patel';
The deleted record is moved to the history table, not lost.
You can still query it later using time-based queries.
7. Querying Temporal Data
7.1 Current Data
SELECT * FROM Employee;
Returns only currently active rows.
7.2 Full Historical Data
SELECT * FROM EmployeeHistory;
Shows all past versions of data.
7.3 Point-in-Time Query
You can view the data as it existed at any point in time.
SELECT *FROM Employee
FOR SYSTEM_TIME AS OF '2025-11-01T12:00:00'ORDER BY EmployeeID;
This shows data exactly as it was on 1st November 2025, 12:00 PM.
7.4 Historical Interval Query
Fetch all changes between two timestamps.
SELECT *FROM Employee
FOR SYSTEM_TIME FROM '2025-10-01' TO '2025-11-01'WHERE EmployeeID = 1;
7.5 Combine Current + History
You can join both for complete audit trails:
SELECT EmployeeID, EmployeeName, Designation, Salary, ValidFrom, ValidTo
FROM Employee
FOR SYSTEM_TIME ALLORDER BY EmployeeID, ValidFrom;
8. Temporarily Disabling System Versioning
If you need to modify the structure or reinitialize data:
ALTER TABLE Employee SET (SYSTEM_VERSIONING = OFF);
GO
-- Perform required changes, e.g., add a columnALTER TABLE Employee ADD Department NVARCHAR(50);
GO
ALTER TABLE Employee
SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory));
9. Restoring Old Versions
If a user accidentally updates or deletes a record, you can restore it easily:
INSERT INTO Employee (EmployeeName, Designation, Salary)
SELECT EmployeeName, Designation, Salary
FROM EmployeeHistory
WHERE EmployeeID = 1AND ValidTo = (
SELECT MAX(ValidTo)
FROM EmployeeHistory
WHERE EmployeeID = 1
);

Join the conversation! Your thoughts help the community grow.