When dealing with Slowly Changing Dimension Type 2 (SCD2) in a data warehouse, a common practice is to use a staging pattern to handle the loading and processing of data before it is inserted into the final dimension table. This staging pattern helps manage historical changes and ensures that only the necessary updates are applied to the dimension table.
Here’s a simplified example of a staging pattern in SQL for handling SCD2:
Step 1 – DimCustomer table, that will maintain the history of customer changes:
CREATE TABLE DimCustomer(
Customer_SNK INT IDENTITY PRIMARY KEY, -- SuperNaturalKey
Customer_BK INT, -- BusinessKey
FirstName VARCHAR(100),
LastName VARCHAR(100),
EffectiveDate DATE,
EndDate DATE DEFAULT '9999-12-31',
IsCurrent CHAR(1)
);Step 2 – FactSalesTransaction table, with records of sales transactions:
CREATE TABLE FactSalesTransaction(
TransactionID INT PRIMARY KEY,
Customer_SNK INT,
SalesDate DATE,
SalesAmount MONEY
);Step 3 – Staging_Customer table to fetch updates from the source system:
CREATE TABLE Staging_Customer(
CustomerID INT,
FirstName VARCHAR(100),
LastName VARCHAR(100),
LoadDate DATE,
);Step 4 – Data load into the staging table from source system.
-- Truncate the staging table
TRUNCATE TABLE Staging_Customer;
-- Insert data into the staging table
INSERT INTO Staging_Customer (
CustomerID,
FirstName,
LastName,
LoadDate
)
SELECT
CustomerID,
FirstName,
LastName,
GETDATE() -- get the current date and time
FROM source_table;Step 5 – Update EndDate of existing records in dimension table where changes are found:
UPDATE d
SET EndDate = s.LoadDate, IsCurrent = 'N'
FROM DimCustomer d
JOIN Staging_Customer s
ON d.Customer_BK = s.CustomerID
WHERE d.IsCurrent = 'Y'
AND (d.FirstName <> s.FirstName
OR d.LastName <> s.LastName);Step 6 – Insert new records into dimension table for new data:
INSERT INTO DimCustomer (
Customer_BK,
FirstName,
LastName,
EffectiveDate,
IsCurrent
)
SELECT
CustomerID,
FirstName,
LastName,
LoadDate,
'Y'
FROM Staging_Customer s
WHERE NOT EXISTS (
SELECT 1
FROM DimCustomer d
WHERE d.Customer_BK = s.CustomerID AND d.IsCurrent = 'Y'
);
"""
The WHERE clause with NOT EXISTS is saying:
If matching records are found,
the NOT EXISTS condition becomes FALSE,
and the respective record from the staging table
is not inserted into the main table.
"""Test Data
Step 1 – DimCustomer with initial data:
--Populating DimCustomer
INSERT INTO DimCustomer (Customer_BK, FirstName, LastName, EffectiveDate, IsCurrent)
VALUES
(101, 'Bob', 'Weber', '2023-01-01', 'Y'),
(102, 'Alice', 'Miller', '2023-01-01', 'Y');Step 2 – FactSalesTransaction with initial data (associating transactions with the customers Bob Weber and Alice Maier):
--Populating FactSalesTransaction
INSERT INTO FactSalesTransaction (TransactionID, Customer_SNK, SalesDate, SalesAmount)
VALUES
(1, 1, '2023-01-02', 100.00), -- Bob Weber made a $100 transaction
(2, 2, '2023-01-02', 200.00); -- Alice Maier made a $200 transactionStep 3 – New data arrives into the Staging_Customer table. Let’s assume that Alice changes her last name to Smith:
--Populating Staging_Customer
INSERT INTO Staging_Customer (CustomerID, FirstName, LastName, LoadDate)
VALUES
(101, 'Bob', 'Weber', '2023-01-02'), --Existing record, no change
(102, 'Alice', 'Smith', '2023-01-02'); --Existing record, name changeStep 4 – Run the SCD Type 2 updates to DimCustomer:
First, customers that have changes will be marked as non-current:
UPDATE d
SET EndDate = s.LoadDate - 1, IsCurrent = 'N'
FROM DimCustomer d
JOIN Staging_Customer s
ON d.Customer_BK = s.CustomerID
WHERE d.IsCurrent = 'Y'
AND (d.FirstName <> s.FirstName OR d.LastName <> s.LastName);Then, insert new records for the changed customers:
INSERT INTO DimCustomer (Customer_BK, FirstName, LastName, EffectiveDate, IsCurrent)
SELECT CustomerID, FirstName, LastName, LoadDate, 'Y'
FROM Staging_Customer s
WHERE NOT EXISTS (
SELECT 1
FROM DimCustomer d
WHERE d.Customer_BK = s.CustomerID AND d.IsCurrent = 'Y');Now, if you query the DimCustomer table, you’ll see that Alice Miller record has been marked as non-current and a new record has been added for Alice Smith.
For example, if Alice Smith makes a new transaction, you’d insert into FactSalesTransaction with the new Customer_SNK for Alice Smith (which would be 3 if you’re following this example):
INSERT INTO FactSalesTransaction (TransactionID, Customer_SNK, SalesDate, SalesAmount)
VALUES
(3, 3, '2023-01-03', 300.00);Walkthrough of how SCD 2 Type works
Initial Setup
DimCustomer Initial
| Customer_SNK (PK) | Customer_BK | FirstName | LastName | EffectiveDate | EndDate | IsCurrent |
|---|---|---|---|---|---|---|
| 1 | 101 | Bob | Weber | 2023-01-01 | NULL | ‚Y‘ |
| 2 | 102 | Alice | Miller | 2023-01-01 | NULL | ‚Y‘ |
Each row of the table represents a customer in your system. The ‚PK‘ in ‚Customer_SNK (PK)‘ designates it as the primary key of the table. ‚NULL‘ represents no set date, implying that the customer is currently active. ‚Y‘ and ‚N‘ in ‚IsCurrent‘ column represent whether the record is current or not.
FactSalesTransaction Initial
| TransactionID (PK) | Customer_SNK | Customer_BK | SalesDate | SalesAmount |
|---|---|---|---|---|
| 1 | 1 | 101 | 2023-01-02 | 100.00 |
| 2 | 2 | 102 | 2023-01-02 | 200.00 |
In this table, each row corresponds to a sale transaction. The ‚PK‘ in ‚TransactionID (PK)‘ designates it as the primary key of the table. SalesDate represents the date of the transaction and SalesAmount represents the amount of the transaction.
Staging_Customer Initial
| CustomerID (PK) | FirstName | LastName | LoadDate |
|---|---|---|---|
| 101 | Bob | Weber | 2023-01-02 |
| 102 | Alice | Miller | 2023-01-02 |
Each row of the table represents a customer in the staging area. The ‚PK‘ in ‚CustomerID (PK)‘ designates it as the primary key of the table. ‚LoadDate‘ is the date when the data was loaded into the staging area.
Event: Alice Miller Changes Her Last Name to Smith
Staging_Customer Update
| CustomerID (PK) | FirstName | LastName | LoadDate |
|---|---|---|---|
| 101 | Bob | Weber | 2023-01-03 |
| 102 | Alice | Smith | 2023-01-03 |
This table shows changes in customer data. It appears that the LastName of customer with CustomerID 102 has been updated from ‚Miller‘ to ‚Smith‘. The LoadDate has also been updated to ‚2023-01-03‘.
DimCustomer Update
We mark the old Alice Miller record as inactive and insert a new record for Alice Smith.
| Customer_SNK (PK) | Customer_BK | FirstName | LastName | EffectiveDate | EndDate | IsCurrent |
|---|---|---|---|---|---|---|
| 1 | 101 | Bob | Weber | 2023-01-01 | NULL | ‚Y‘ |
| 2 | 102 | Alice | Miller | 2023-01-01 | 2023-01-02 | ‚N‘ |
| 3 | 102 | Alice | Smith | 2023-01-03 | NULL | ‚Y‘ |
This table indicates that Alice Miller (Customer_BK 102) is no longer current (‚N‘) as of ‚2023-01-03‘. A new record has been created for Jane Smith (still Customer_BK 102) with a current status (‚Y‘) starting from ‚2023-01-03‘.
When New Transactions for Alice arrive, we associate the new transaction with the new Alice Smith record.
FactSalesTransaction after a New Transaction for Alice
| TransactionID | Customer_SNK | Customer_BK | SalesDate | SalesAmount |
|---|---|---|---|---|
| 1 | 1 | 101 | 2023-01-02 | 100.00 |
| 2 | 2 | 102 | 2023-01-02 | 200.00 |
| 3 | 3 | 102 | 2023-01-03 | 150.00 |
In this table, each row corresponds to a sales transaction. The new entry (last row) indicates that customer with Customer_SNK 3 (Alice Smith with Customer_BK 102) made a purchase on 2023-01-03 with SalesAmount of 150.00.




