top of page
SCD Type 2.png

1. The two CTEs (lines 1-9)

sql

WITH SourceClean AS ( SELECT DISTINCT * FROM [Veritas_WH].[stg].[New_Analyst_Roster] ),

  • WITH SourceClean AS (...) defines a temporary named result set that exists only for this statement.

  • SELECT DISTINCT * takes the incoming roster from the staging table and removes rows that are identical in every column.

sql

TargetClean AS ( SELECT DISTINCT * FROM [Veritas_WH].[ref].[Analysts] WHERE is_active = 1 )

  • This does the same for the target table, but keeps only current rows (is_active = 1).

  • Expired history rows are left out because the comparison should only be against each analyst’s current version.

2. The MERGE header (lines 10-11)

sql

MERGE INTO [Veritas_WH].[ref].[Analysts] AS T USING ( ... ) AS S

  • MERGE INTO ... AS T makes the real ref.Analysts table the target, aliased T. Updates and inserts happen here.

  • USING (...) AS S supplies the rows to apply, aliased S. Everything inside the parentheses builds that set.

3. The USING subquery, Branch 1 (lines 12-18)

sql

SELECT S.Analyst_ID, S.Name, S.Title, S.ManagerID, S.Analyst_ID AS MergedKey FROM SourceClean AS S INNER JOIN TargetClean AS T ON T.AnalystID = S.Analyst_ID WHERE T.Title <> S.Title OR T.ManagerID <> S.ManagerID

  • The SELECT returns the new values from the source, plus an extra column MergedKey set to the real Analyst_ID.

  • INNER JOIN keeps only analysts that exist in both source and active target.

  • WHERE keeps only those whose Title or ManagerID changed. Unchanged analysts drop out here.

  • Because MergedKey holds the real ID, these rows will match the existing active row in the MERGE. They are the “expire the old row” rows.

  • The T here is TargetClean, not the MERGE target. The alias is reused, but it is a separate scope inside the subquery.

4. UNION ALL (line 20)

  • It stacks Branch 1 and Branch 2 into one result set.

  • UNION ALL is used instead of UNION so no deduplication happens. The two branches intentionally produce different rows for the same analyst, one with a real key and one with NULL.

5. The USING subquery, Branch 2 (lines 22-28)

sql

SELECT S.Analyst_ID, S.Name, S.Title, S.ManagerID, NULL AS MergedKey FROM SourceClean AS S LEFT JOIN TargetClean AS T ON T.AnalystID = S.Analyst_ID WHERE T.Title <> S.Title OR T.ManagerID <> S.ManagerID OR T.AnalystID IS NULL

  • The SELECT returns the same columns, but MergedKey is NULL.

  • LEFT JOIN keeps every source row, even when there is no matching target row.

  • The first two WHERE conditions catch changed analysts, the same as Branch 1.

  • OR T.AnalystID IS NULL catches brand-new analysts. For them the left join gives NULL on the target side, so T.Title <> S.Title evaluates to unknown and would be filtered out without this condition.

  • Since MergedKey is NULL, these rows can never match in the MERGE. That makes them the “insert a new row” rows.

  • A changed analyst therefore appears twice in S: once from Branch 1 and once from Branch 2.

6. The ON clause (line 29)

sql

) AS S ON T.AnalystID = S.MergedKey AND T.is_active = 1

  • The match rule is: the target row’s AnalystID equals the source’s MergedKey, and the target row is currently active.

  • Branch 1 rows match. Branch 2 rows never do, because NULL = anything is not true.

  • T.is_active = 1 stops the MERGE from touching expired history rows.

7. WHEN MATCHED (lines 31-34)

sql

WHEN MATCHED AND (T.Title <> S.Title OR T.ManagerID <> S.ManagerID) THEN UPDATE SET T.end_date = DATEADD(DAY, -1, CAST(GETDATE() AS DATE)), T.is_active = 0

  • This runs for Branch 1 rows that found their active target row.

  • The extra condition re-checks that something changed. It is already guaranteed by Branch 1’s WHERE, so it is redundant but harmless.

  • GETDATE() returns the current datetime and CAST(... AS DATE) strips the time. DATEADD(DAY, -1, ...) subtracts one day, so end_date becomes yesterday.

  • is_active = 0 marks the old version as expired.

  • Only these two columns are updated. The old Title and Manager stay as they were, which is what preserves history.

8. WHEN NOT MATCHED (lines 35-38)

sql

WHEN NOT MATCHED THEN INSERT (AnalystID, Name, Title, ManagerID, start_date, end_date, is_active) VALUES (S.Analyst_ID, S.Name, S.Title, S.ManagerID, CAST(GETDATE() AS DATE), NULL, 1);

  • This runs for source rows with no matching target row, which means the NULL-key Branch 2 rows. These are new analysts and the new versions of changed analysts.

  • INSERT (...) lists the target columns. VALUES (...) fills them with the source values.

  • start_date is today, end_date is NULL because the row is open-ended, and is_active is 1 for current.

  • The trailing ; is mandatory because T-SQL requires MERGE statements to end with a semicolon.

bottom of page