- Most tables convert mechanically: INT and BIGINT stay, BIT becomes boolean, NVARCHAR becomes varchar or text, DATETIME2 becomes timestamp, UNIQUEIDENTIFIER becomes uuid and VARBINARY becomes bytea.
- The real work is T-SQL: TOP, ISNULL, GETDATE, + for strings, #temp tables, TRY...CATCH and procedures that return result sets all need PostgreSQL equivalents.
- pgloader converts and loads a whole SQL Server database into PostgreSQL in one command, but it does not translate procedures or keep the target in sync.
- AWS DMS with DMS Schema Conversion converts schema and code and can replicate changes (CDC) for a low-downtime cutover into RDS or Aurora PostgreSQL.
- Babelfish for Aurora PostgreSQL lets SQL Server applications keep using T-SQL and the TDS protocol on PostgreSQL, with documented differences; assess your code with Babelfish Compass first.
Planning a SQL Server to PostgreSQL migration
A SQL Server to PostgreSQL migration is heterogeneous: the engines share standard SQL but differ in types, procedural language, identifier rules and system functions. Before choosing a tool, list the schemas and table sizes, count the stored procedures, functions, triggers and views, find SQL Server features with no direct PostgreSQL equivalent (SQL Agent jobs, linked servers, CLR objects, full-text catalogues), and record every application that connects. The SQL Server vs PostgreSQL comparison summarises the engine differences, and the database migration guide covers the general process.
There are three realistic routes:
- Convert to native PostgreSQL with pgloader (schema and data) or DMS Schema Conversion plus AWS DMS (schema, code and data, with optional CDC). Applications are then changed to PostgreSQL drivers and SQL.
- Babelfish for Aurora PostgreSQL, which accepts SQL Server clients over TDS on port 1433 and runs T-SQL with documented differences, so applications change less. It is specific to Amazon Aurora.
- Manual: script the schema yourself, export data with bcp and load it with PostgreSQL's COPY. Practical only for small schemas.
SQL Server to PostgreSQL data type mapping
The middle column shows what pgloader does by default; the right column is the usual hand-written choice. pgloader turns every character type into text and every datetime into timestamptz; PostgreSQL's text has no length limit, so if you rely on lengths for validation, keep varchar(n) instead.
| SQL Server | pgloader default | Typical PostgreSQL choice and notes |
|---|---|---|
| INT IDENTITY(1,1) | integer with a sequence | integer GENERATED BY DEFAULT AS IDENTITY; reset it after loading |
| TINYINT | smallint | smallint, the smallest PostgreSQL integer type |
| BIT | boolean | boolean; code comparing with 1 and 0 must change to true and false |
| DECIMAL(p,s), NUMERIC, MONEY | numeric | numeric(p,s); give MONEY an explicit scale such as numeric(19,4) |
| CHAR, VARCHAR, NCHAR, NVARCHAR | text | varchar(n) or text; create the database with UTF8 encoding so these columns hold Unicode text |
| NVARCHAR(MAX), XML | text | text, or PostgreSQL's xml type |
| DATETIME, DATETIME2 | timestamptz | timestamp (without time zone) matches SQL Server semantics; timestamptz if you store UTC |
| DATETIMEOFFSET | not listed | timestamptz |
| UNIQUEIDENTIFIER | uuid | uuid |
| BINARY, VARBINARY(MAX) | bytea | bytea |
| FLOAT, REAL | float, real | double precision, real |
T-SQL to PostgreSQL: what to rewrite
Procedural code is where most effort goes. The table lists the constructs that appear in almost every SQL Server code base and their PostgreSQL forms. PostgreSQL procedures (CREATE PROCEDURE) do not return result sets the way T-SQL procedures do, so procedures that end in a SELECT usually become functions that RETURNS TABLE; DMS Schema Conversion has a setting for exactly this choice.
| T-SQL (SQL Server) | PostgreSQL | Note |
|---|---|---|
SELECT TOP (10) ... | ... LIMIT 10 (or FETCH FIRST 10 ROWS ONLY) | LIMIT goes at the end of the query |
ISNULL(a, b) | COALESCE(a, b) | COALESCE also works in SQL Server |
GETDATE() | now() or current_timestamp | Returns the transaction start time, with time zone |
'a' + 'b' | 'a' || 'b' | || is the string concatenation operator |
[Order Details] | "Order Details" | Unquoted names fold to lower case; quoted names are case-sensitive |
SCOPE_IDENTITY() after INSERT | INSERT ... RETURNING id | RETURNING works on INSERT, UPDATE, DELETE and MERGE |
#temp tables | CREATE TEMP TABLE | Dropped automatically at the end of the session |
BEGIN TRY ... END TRY BEGIN CATCH ... END CATCH | BEGIN ... EXCEPTION WHEN ... THEN ... END | PL/pgSQL traps errors with an EXCEPTION clause |
MERGE | MERGE or INSERT ... ON CONFLICT | PostgreSQL supports MERGE; ON CONFLICT handles simple upserts |
| Procedure ending in SELECT | Function with RETURNS TABLE | Called with SELECT * FROM fn(...) |
Tools: pgloader, AWS DMS and Babelfish compared
pgloader's stable release is 3.6.9; its documentation now also describes a Java-based v4 published as a development build. For the AWS route step by step, including CDC prerequisites on SQL Server and pricing, see the AWS DMS guide. For Babelfish, AWS recommends running Babelfish Compass on your generated DDL to measure how much T-SQL is supported before committing.
| pgloader | AWS DMS + DMS Schema Conversion | Babelfish for Aurora PostgreSQL | |
|---|---|---|---|
| What it does | Reads the SQL Server catalogue, creates tables, indexes and keys, and loads data with COPY | Converts schema and code (assessment report, then convert), then migrates data with full load and optional CDC | Adds a T-SQL/TDS endpoint to Aurora PostgreSQL so SQL Server clients connect unchanged |
| Stored procedures | Not converted | Converted where the rules or generative AI can; the rest flagged for manual work | Run as T-SQL, within Babelfish's documented limits |
| Ongoing changes | No | Yes (needs Enterprise, Standard 2016+ or Developer source) | Data moved with DMS or export and import |
| Target | Any PostgreSQL | Amazon RDS or Aurora PostgreSQL | Amazon Aurora PostgreSQL only |
| Cost | Free, open source | Hourly DMS capacity; Schema Conversion free apart from S3 storage | Aurora pricing |
Worked example: one table and one procedure
The source is a SQL Server table with an identity key and a procedure that returns a customer's most recent orders. The PostgreSQL version uses an identity column, boolean, timestamp and a function returning a table. The load is done with a pgloader command file that copies the dbo schema into public; pgloader documents its default options for database sources as creating tables, indexes and foreign keys and resetting sequences.
CREATE TABLE dbo.orders (
order_id INT IDENTITY(1,1) PRIMARY KEY,
customer_id INT NOT NULL,
status NVARCHAR(20) NULL,
is_paid BIT NOT NULL DEFAULT 0,
total_amount DECIMAL(12,2) NOT NULL,
created_at DATETIME2 NOT NULL DEFAULT GETDATE()
);
GO
CREATE PROCEDURE dbo.get_recent_orders @customer_id INT, @top_n INT
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP (@top_n) order_id, ISNULL(status, N'new') AS status, created_at
FROM dbo.orders
WHERE customer_id = @customer_id
ORDER BY created_at DESC;
END;CREATE TABLE orders (
order_id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
customer_id integer NOT NULL,
status varchar(20),
is_paid boolean NOT NULL DEFAULT false,
total_amount numeric(12,2) NOT NULL,
created_at timestamp NOT NULL DEFAULT now()
);
CREATE OR REPLACE FUNCTION get_recent_orders(p_customer_id integer, p_top_n integer)
RETURNS TABLE (order_id integer, status text, created_at timestamp)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT o.order_id, COALESCE(o.status, 'new')::text, o.created_at
FROM orders AS o
WHERE o.customer_id = p_customer_id
ORDER BY o.created_at DESC
LIMIT p_top_n;
END;
$$;
-- T-SQL: EXEC dbo.get_recent_orders 42, 10; PostgreSQL:
SELECT * FROM get_recent_orders(42, 10);LOAD DATABASE
FROM mssql://<user>:<your-password>@<sqlserver-host>/mydb
INTO postgresql://<pg-user>:<your-password>@<pg-host>/mydb
ALTER SCHEMA 'dbo' RENAME TO 'public';SELECT COUNT(*) AS row_count, SUM(total_amount) AS total, MAX(order_id) AS max_id
FROM orders;
-- if you created the table yourself and loaded explicit ids,
-- move the identity past the highest copied value
SELECT setval(pg_get_serial_sequence('orders', 'order_id'),
(SELECT MAX(order_id) FROM orders));Testing, cutover and rollback
- Compare data table by table with counts, sums and key ranges, then compare sample rows column by column. Watch for strings that change length because of collation or trailing spaces, and timestamps shifted by a time zone if you chose timestamptz.
- Check case sensitivity. If your SQL Server collation is case-insensitive (its name contains
_CI_), queries such asWHERE email = @emailmatched regardless of case. On PostgreSQL, the documented options are comparinglower()on both sides, the citext extension (a case-insensitive text type that calls lower internally), or a nondeterministic collation. - Run the application test suite against PostgreSQL, including reports and scheduled jobs that used SQL Agent.
- Cut over with writes stopped, sequences reset, and connection strings switched; keep the SQL Server database untouched until the agreed rollback window closes.
For backups of the new database, see the related pg_dump guide; to manage it day to day, see pgAdmin or DBeaver. If you are learning PostgreSQL SQL after years of T-SQL, the SQL beginner course covers the shared basics.
Frequently asked questions
Can I migrate SQL Server to PostgreSQL for free?
Yes. pgloader is free and open source and converts tables and data in one command; you then rewrite stored procedures by hand. Paid routes such as AWS DMS add code conversion assistance and change replication.
How do I convert T-SQL stored procedures to PostgreSQL?
Rewrite them in PL/pgSQL: replace TOP with LIMIT, ISNULL with COALESCE, GETDATE with now(), + with ||, TRY...CATCH with an EXCEPTION block, and turn procedures that return rows into functions with RETURNS TABLE. DMS Schema Conversion can convert much of this automatically and flags what it cannot.
What is Babelfish for PostgreSQL?
A feature of Amazon Aurora PostgreSQL that understands the SQL Server wire protocol (TDS) and T-SQL, so applications built for SQL Server can connect with their existing drivers and run most queries with few changes. AWS documents the differences from SQL Server.
Does pgloader migrate SQL Server views and procedures?
pgloader can migrate views as if they were tables (materialising their data), but it does not convert procedures, functions or triggers. Convert those separately.
What does SQL Server NVARCHAR map to in PostgreSQL?
To varchar(n) or text. In a UTF8-encoded PostgreSQL database these types hold Unicode text, so a separate national character type is not needed. pgloader maps NVARCHAR to text by default.
Sources
- pgloader documentation: MS SQL to Postgres
- pgloader releases (GitHub)
- pgloader documentation: Installing pgloader
- AWS DMS User Guide: SQL Server to PostgreSQL conversion settings
- AWS DMS User Guide: DMS Schema Conversion
- AWS DMS User Guide: Sources for data migration
- Aurora User Guide: Babelfish for Aurora PostgreSQL
- Aurora User Guide: Migrating a SQL Server database to Babelfish
- Aurora User Guide: Differences between Babelfish and SQL Server
- Babelfish project site
- PostgreSQL: CREATE TABLE
- PostgreSQL: citext module
- pgloader documentation: MySQL to Postgres (default options)
- PostgreSQL: Character types
- PostgreSQL: UUID type
- PostgreSQL: Binary data types
- PostgreSQL: Lexical structure (identifiers)
- PostgreSQL: SELECT (LIMIT)
- PostgreSQL: Conditional expressions (COALESCE)
- PostgreSQL: Date/time functions
- PostgreSQL: String functions and operators
- PostgreSQL: Returning data from modified rows
- PostgreSQL: MERGE
- PostgreSQL: PL/pgSQL control structures
- PostgreSQL: CREATE PROCEDURE
- PostgreSQL: System information functions
- PostgreSQL: Sequence functions
- Microsoft Learn: TOP (Transact-SQL)
- Microsoft Learn: ISNULL (Transact-SQL)
- Microsoft Learn: GETDATE (Transact-SQL)
- Microsoft Learn: TRY...CATCH (Transact-SQL)
- Microsoft Learn: IDENTITY property
- Microsoft Learn: uniqueidentifier
Checked 8 October 2026.
How we research guides: our editorial method. We link only to official downloads and never host installers.