Row counts only prove I moved the right quantity of data. This phase proves the actual values, the constraints, and real query behavior all migrated correctly too — across three separate layers of validation.
I ran three layers of validation: aggregate checksums beyond row counts, constraint enforcement testing with intentionally bad inserts, and representative query diffing against the live source. Every check passed.
For every table I compared sums, distinct counts, min/max values, and null counts between PostgreSQL and SQL Server — a lightweight but effective way to catch silent data corruption a row count alone would miss.
| Table | Check | Result |
|---|---|---|
| cms.providers | distinct NPIs, states, sum(ruca), null counts | Exact match |
| cms.provider_services | min/max id, sum(tot_benes), sum(tot_srvcs), sum(avg_sbmtd_chrg) | Exact match |
| cms.user_state_access | distinct usernames, states | Exact match |
| cms.staging_raw | row count, distinct NPIs | Exact match |
Matching data is only half the story — I needed to prove SQL Server enforces the same rules PostgreSQL did. I ran four intentionally-bad inserts, each wrapped in TRY/CATCH.
All four were rejected with the expected, specific error message, and row counts on every affected table were confirmed unchanged afterward.
I ran three realistic, application-style queries against both systems: top 10 states by provider count, top 10 billing codes by service volume, and a full record lookup for the single busiest provider. All three matched exactly — same values, same order, same result.
One difference worth noting, not a defect: an AVG() result showed more decimal places in PostgreSQL than SQL Server. Since the underlying SUM() values matched exactly in the aggregate checks, this is purely a display-precision difference between engines' AVG() implementations, not data corruption.