A complete guide for enterprise teams migrating from SQL Server to PostgreSQL β from schema conversion and T-SQL translation to data migration and production cutover.
Paste your SQL Server CREATE TABLE statements and get PostgreSQL-ready SQL in seconds.
Translate SQL Server β PostgreSQL Diff Two SchemasEnterprise teams are increasingly moving from SQL Server to PostgreSQL for these reasons:
Use SQL Server Management Studio (SSMS) to generate schema scripts:
Alternatively, use sqlcmd or mssql-scripter from the command line:
mssql-scripter -S localhost -d MyDatabase -U sa --schema-and-data false \
> sqlserver_schema.sql
Review the exported file for SQL Server-specific syntax:
[Bracketed] identifiers β PostgreSQL uses double quotesIDENTITY(1,1) β Will become SERIAL or GENERATED ALWAYS AS IDENTITYNVARCHAR / NCHAR β PostgreSQL uses VARCHAR with UTF-8DATETIME2 / SMALLDATETIME β PostgreSQL uses TIMESTAMPUNIQUEIDENTIFIER β PostgreSQL uses UUIDCLUSTERED / NONCLUSTERED indexes β PostgreSQL uses different index typesWITH (PAD_INDEX = ON ...) β Storage clauses that PostgreSQL ignoresSQL Server and PostgreSQL have fundamentally different type systems, identifier rules, and default behaviors.
Use the SQL Dialect Translator to convert your SQL Server schema export to PostgreSQL syntax. It handles type mapping, identity column conversion, and identifier quoting.
If you're modernizing the schema as part of migration, paste your SQL Server schema and target PostgreSQL schema into SchemaLens. It shows structural differences and generates migration SQL.
For small schemas, manually rewrite CREATE TABLE statements using the type mapping table below.
The SQL Dialect Translator converts SQL Server schemas to PostgreSQL in seconds β including NVARCHARβVARCHAR, IDENTITYβSERIAL, and bracketed identifier conversion.
Try the Translator βAfter translating the schema and creating tables in PostgreSQL, move the data.
If migrating from Azure SQL Database or Azure SQL Managed Instance, use Azure DMS. It handles schema conversion, data movement, and cutover with minimal downtime.
For on-premise SQL Server, use the tds_fdw foreign data wrapper to connect PostgreSQL directly to SQL Server:
-- In PostgreSQL
CREATE EXTENSION tds_fdw;
CREATE SERVER sqlserver_server
FOREIGN DATA WRAPPER tds_fdw
OPTIONS (servername 'sqlserver_host', port '1433', database 'MyDB');
CREATE USER MAPPING FOR postgres
SERVER sqlserver_server
OPTIONS (username 'sa', password 'password');
-- Import foreign schema
IMPORT FOREIGN SCHEMA dbo
FROM SERVER sqlserver_server
INTO public;
For complex transformations, use Python with pyodbc and SQLAlchemy:
import pandas as pd
from sqlalchemy import create_engine
sqlserver = create_engine('mssql+pyodbc://user:pass@dsn')
postgres = create_engine('postgresql://user:pass@host/db')
# Read from SQL Server
df = pd.read_sql('SELECT * FROM dbo.Users', sqlserver)
# Transform if needed (e.g., convert UNIQUEIDENTIFIER to UUID string)
df['id'] = df['id'].astype(str)
# Write to PostgreSQL
df.to_sql('users', postgres, if_exists='append', index=False)
Use SQL Server Integration Services (SSIS) with a PostgreSQL ODBC driver. This is common in enterprise environments that already have SSIS packages.
SQL_Latin1_General_CP1_CI_AS) determines case sensitivity and sorting. PostgreSQL uses collation at the database or column level. Ensure your PostgreSQL collation matches application expectations.
Enterprise migrations require rigorous validation:
DATETIME min: 1753-01-01, PostgreSQL TIMESTAMP min: 4713 BC)Export both schemas and diff them:
-- SQL Server (via SSMS or sqlcmd)
-- Generate Scripts β Schema Only
-- PostgreSQL
pg_dump --schema-only -U postgres mydb > postgres_schema.sql
Then paste both into SchemaLens to catch any remaining differences.
SchemaLens compares your SQL Server and PostgreSQL schemas side-by-side and highlights tables, columns, indexes, and constraints that didn't migrate correctly.
Compare Schemas Free β| SQL Server Type | PostgreSQL Type | Notes |
|---|---|---|
INT IDENTITY(1,1) | SERIAL | Use BIGSERIAL for BIGINT identity |
BIGINT IDENTITY(1,1) | BIGSERIAL | Direct equivalent |
BIT | BOOLEAN | SQL Server BIT can be 0, 1, or NULL |
TINYINT | SMALLINT | PostgreSQL has no unsigned types |
SMALLINT | SMALLINT | No change needed |
INT | INTEGER | No change needed |
BIGINT | BIGINT | No change needed |
FLOAT | DOUBLE PRECISION | Or REAL for 4-byte float |
REAL | REAL | No change needed |
DECIMAL(p,s) | NUMERIC(p,s) | No change needed |
MONEY | NUMERIC(19,4) | PostgreSQL has MONEY but it's discouraged |
SMALLMONEY | NUMERIC(10,4) | No direct equivalent |
VARCHAR(n) | VARCHAR(n) | No change needed |
NVARCHAR(n) | VARCHAR(n) | PostgreSQL uses UTF-8 natively |
CHAR(n) | CHAR(n) | No change needed |
NCHAR(n) | CHAR(n) | PostgreSQL uses UTF-8 natively |
TEXT | TEXT | No change needed |
NTEXT | TEXT | Deprecated in SQL Server anyway |
VARCHAR(MAX) | TEXT | PostgreSQL TEXT is unlimited |
NVARCHAR(MAX) | TEXT | PostgreSQL TEXT is unlimited |
DATETIME | TIMESTAMP | Or TIMESTAMPTZ with timezone |
DATETIME2 | TIMESTAMP | Higher precision than DATETIME |
SMALLDATETIME | TIMESTAMP | Less precision, same mapping |
DATETIMEOFFSET | TIMESTAMPTZ | Timezone-aware timestamp |
DATE | DATE | No change needed |
TIME | TIME | No change needed |
UNIQUEIDENTIFIER | UUID | Use gen_random_uuid() as default |
BINARY(n) | BYTEA | Fixed-length binary |
VARBINARY(n) | BYTEA | Variable-length binary |
IMAGE | BYTEA | Deprecated in SQL Server |
VARBINARY(MAX) | BYTEA | For large binary objects |
XML | XML | PostgreSQL has native XML type |
SQL_VARIANT | JSONB or TEXT | No direct equivalent; redesign recommended |
citext extension or explicit lower()/upper() in queries.SELECT TOP 10 becomes SELECT ... LIMIT 10. The syntax is different and TOP with ORDER BY behaves slightly differently.NULL + 'text' = NULL. PostgreSQL: NULL || 'text' = NULL. Same behavior, but CONCAT() in SQL Server treats NULL as empty string.SELECT * FROM OtherDB.dbo.Table doesn't work in PostgreSQL. Use dblink or postgres_fdw for cross-database access.#temp tables are session-scoped. PostgreSQL temporary tables (CREATE TEMP TABLE) behave similarly but have different visibility rules.TRY...CATCH becomes PL/pgSQL BEGIN...EXCEPTION. Syntax and behavior differ.For a 50-100 table schema with under 100GB of data, plan for 1-2 weeks including testing. Large enterprise schemas (1000+ tables) with TB-scale data can take 1-3 months. The schema translation is typically 20% of the effort; data migration, stored procedure rewriting, and application testing consume the rest.
Yes, using logical replication or change data capture (CDC). Tools like Debezium can stream SQL Server changes to PostgreSQL in real-time. Azure DMS also supports online migration for Azure SQL Database.
SQL Server Agent jobs must be rewritten. In PostgreSQL, use pg_cron extension for scheduled tasks, or external schedulers like Linux cron, Airflow, or Dagster.
SSRS doesn't exist in PostgreSQL. Options include: Metabase, Apache Superset, Grafana, or commercial tools like Tableau and Power BI (which connect to PostgreSQL natively).
Yes, if you're already in Azure. It's fully managed PostgreSQL with built-in backups, monitoring, and scaling. However, self-managed PostgreSQL on Azure VMs or AKS gives you more control over extensions and configuration.
Most likely yes. T-SQL and PL/pgSQL are different languages. Simple procedures may translate automatically, but complex logic with cursors, dynamic SQL, or CLR integration will need manual rewriting. Consider moving business logic to the application layer where possible.
Convert your SQL Server CREATE TABLE statements to PostgreSQL syntax in seconds, then diff the result to catch anything the translator missed.
Translate Schema β Diff & Validate β