Before writing a single line of T-SQL, I built the infrastructure this migration runs on and fully inventoried the PostgreSQL source database it's built to replace. That meant standing up a Hyper-V VM, installing SQL Server 2025, and discovering along the way that Microsoft's own migration tool doesn't support PostgreSQL anymore.
I built the entire target environment from scratch on a single Windows 11 Pro laptop (64 GB RAM) using Hyper-V — a Windows Server 2025 VM hosting SQL Server 2025 Enterprise Developer Edition, networked to my existing native PostgreSQL 18 installation. I deliberately provisioned this VM as the future Always On Availability Group primary replica for a follow-on Enterprise DBA project, so the environment serves double duty rather than being rebuilt later.
Once the environment was live, I connected to the source healthcare_dba PostgreSQL database and ran a full inventory: every table, column, data type, constraint, index, trigger, function, and sequence across two schemas — cms for core provider data and audit for logging.
Every configuration choice was deliberate rather than defaulted, since this environment needs to support both this migration and a follow-on enterprise DBA project.
I ran a complete inventory against healthcare_dba, covering every category of object that would need to convert to T-SQL.
| Category | Finding |
|---|---|
| Schemas | 2 — cms (core data) and audit (logging) |
| Tables | 5, totaling ~20.5 million rows and ~5.5 GB |
| Primary Keys | 4, including one composite key on user_state_access |
| Foreign Keys | 1 — provider_services.rndrng_npi → providers.rndrng_npi |
| Indexes | 8 secondary indexes, all standard B-tree |
| Triggers | 2, both calling the same generic audit-logging function |
| Sequences | 2, standard bigint identity-style sequences |
| Extensions | 2 (plpgsql, pg_stat_statements) — neither needs migrating |
Two column types had no direct SQL Server equivalent and needed a mapping decision in Phase 2: jsonb (used for the audit log's before/after row snapshots) and inet (used for the client IP address on audit records). I also noticed cms.staging_raw stores nearly every column — even numeric ones — as character varying, telling me it's a raw landing table rather than a true relational entity.
My original plan was to use Microsoft's SQL Server Migration Assistant (SSMA), the standard free tool for this kind of migration. When I went to download it, I found that the current SSMA documentation lists exactly five supported sources: Access, Db2, MySQL, Oracle, and SAP ASE. PostgreSQL isn't one of them — several third-party blogs still describe "SSMA for PostgreSQL" as current, but none link to a working Microsoft download, and Microsoft's own source list doesn't include it.
psycopg2 and pyodbc was a fully viable path, and one that demonstrates more hands-on schema-conversion and ETL skill than a GUI wizard would have.