Published: 2026-06-13

SQL Server ↔ PostgreSQL Data Type Mapping: The Complete Reference (Both Directions)

When you migrate between SQL Server and PostgreSQL, the first thing you hit is the type system. bit isn't boolean. nvarchar has no Postgres twin. datetime2, datetimeoffset, uniqueidentifier, money, rowversion — each needs a deliberate decision. This is the exact mapping Jam SQL Studio uses when it migrates a table or runs a cross-engine schema compare, documented in both directions, including the lossy edges most cheat-sheets skip. It's not a generic table copied from a wiki — every row here is what the tool actually emits.

Jam SQL Studio's Migrate Table to Connection preview showing a SQL Server table's columns translated into PostgreSQL types

The trick: route everything through a canonical type

Jam SQL Studio doesn't keep an N×N grid of "MSSQL type X → Postgres type Y" rules. Instead, every engine defines two functions against a single canonical type model: toCanonical (native type → canonical) and fromCanonical (canonical → native). A migration is just source.toCanonicalCanonicalTypetarget.fromCanonical.

The canonical kinds are deliberately small and engine-neutral: boolean, sized integers (int8/int16/int32/int64), decimal, float32/float64, char/varchar/text, binary/varbinary/blob, uuid, date/time/timestamp/timestamptz, json, and unknown for everything with no faithful cross-engine equivalent. Because the hub is symmetric, the mapping works for any pair — SQL Server, PostgreSQL, MySQL, Oracle, SQLite — not just this one. This page focuses on the MSSQL↔Postgres pair because it's the most common cross-engine move.

SQL Server → PostgreSQL

What each SQL Server type becomes when you migrate to PostgreSQL:

SQL Server typePostgreSQL typeNotes
bitboolean0/1 coerced to FALSE/TRUE
tinyintsmallintwidened — Postgres has no 1-byte integer
smallintsmallint
intinteger
bigintbigint
realrealsingle precision
floatdouble precision
decimal(p,s) / numeric(p,s)numeric(p,s)precision/scale preserved
moneynumeric(19,4)lossy: money spelling dropped
smallmoneynumeric(10,4)
char(n) / nchar(n)character(n)N-type byte width → char count
varchar(n) / nvarchar(n)character varying(n)
varchar(max) / nvarchar(max) / text / ntexttextunbounded character data
uniqueidentifieruuidlossless both ways
datedate
time(n)time(n)fractional-seconds precision preserved
datetime2(n)timestamp(n)without time zone
datetimetimestamp(3)legacy fixed precision
smalldatetimetimestamp(0)minute precision
datetimeoffset(n)timestamp(n) with time zonetz-aware on both sides
binary(n) / varbinary(n) / imagebytealength dropped; Postgres bytea is unbounded
rowversion / timestampbyteaSQL Server timestamp is an 8-byte row-version, not temporal
json (SQL Server 2025)jsonb
xml, sql_variant, hierarchyid, geography, geometry, CLR/UDTverbatim + warningno canonical equivalent — review by hand

PostgreSQL → SQL Server

Going the other way — what each PostgreSQL type becomes when you migrate to SQL Server. Note the standout: because every Postgres character type is Unicode (the database encoding, typically UTF-8), they come back as N-prefixed SQL Server types.

PostgreSQL typeSQL Server typeNotes
booleanbitTRUE/FALSE1/0
smallint / int2smallint
integer / int4 / serialintserial identity is not carried — warning
bigint / int8 / bigserialbigint
numeric(p,s) / decimal(p,s)decimal(p,s)precision/scale preserved
moneydecimal(19,2)lossy: locale-scaled, money spelling dropped
real / float4real
double precision / float8float
character(n) / char(n)nchar(n)Postgres char is Unicode → N-type
character varying(n) / varchar(n)nvarchar(n)Unicode → N-type
textnvarchar(max)idiomatic modern unbounded Unicode
byteavarbinary(max)
uuiduniqueidentifier
datedate
time(n)time(n)
time(n) with time zone / timetztime(n)lossy: tz dropped (no canonical timetz)
timestamp(n) (without tz)datetime2(n)
timestamp(n) with time zone / timestamptzdatetimeoffset(n)
json / jsonbjsonneeds SQL Server 2025 native json on the target
bit / bit varyingverbatim + warningPostgres bit is a bit string, not a boolean
arrays, ranges, inet/cidr, interval, hstore, tsvector, xml, enumsverbatim + warningno canonical equivalent — review by hand

The edge cases worth knowing

These are the conversions that surprise people — the ones a naive "find and replace the type name" approach gets wrong.

  • bit is not a boolean — in either engine the obvious-looking name is a trap. SQL Server bit is a boolean (and maps to Postgres boolean), but PostgreSQL bit / bit varying are bit strings. So Postgres bit does not map to SQL Server bit — it's carried verbatim with a warning. Only boolean participates in the boolean mapping.
  • SQL Server timestamp is not a timestamp. It's rowversion — an 8-byte automatically-incrementing binary value, nothing to do with dates. It maps to bytea, not a temporal type. (This single naming collision causes more migration bugs than any other.)
  • Postgres character types return as N-types. Because Postgres stores character data in the database encoding (UTF-8), every varchar/char/text is effectively Unicode — so migrating to SQL Server yields nvarchar/nchar/nvarchar(max), never the non-Unicode variants.
  • tinyint widens. Postgres has no 1-byte integer, so tinyint becomes smallint. This round-trips to smallint, not back to tinyint — the only safe direction-stable choice.
  • money always loses its spelling. SQL Server moneynumeric(19,4); Postgres moneydecimal(19,2). The numeric precision is faithful, but neither side preserves the money type name or its locale formatting — which is usually what you want, since money's locale behavior is a frequent source of bugs.
  • json needs a modern target. Native json mapping assumes SQL Server 2025 (the first version with a native json column type). Migrating Postgres jsonb to an older SQL Server means storing it as nvarchar(max) instead — declare the column accordingly.

Values get coerced, not just types

A correct type mapping isn't enough — the stored values have to be valid literals in the target dialect too. When Jam SQL Studio copies rows it coerces them as it builds the INSERT:

  • Booleans: SQL Server bit 0/1 → Postgres FALSE/TRUE (and back).
  • Binary: written as Postgres bytea hex literals ('\x…').
  • Dates / timestamps: emitted as ISO-8601 string literals.
  • JSON: emitted as a quoted JSON string the target's JSON type accepts.
  • Everything else: escaped per the target dialect's quoting rules.

See it on your own schema

This table is the reference; the migration tool shows you the result on your actual columns. Right-click a SQL Server table, choose Migrate Table to Connection, pick a Postgres target, and the preview renders the translated CREATE TABLE — every column already mapped, every unmapped type flagged. The same canonical model drives cross-engine Schema Compare, so you can diff a SQL Server table against its Postgres counterpart and get a green "identical" verdict for the columns that translated cleanly.

Frequently asked questions

What does SQL Server bit map to in PostgreSQL?

SQL Server bit maps to PostgreSQL boolean. The stored values 0 and 1 are coerced to FALSE and TRUE when rows are copied, because a PostgreSQL boolean literal is TRUE/FALSE, not 0/1. Note that PostgreSQL's own bit and bit varying types are bit strings, not booleans, so they are not a mapping target.

What is the PostgreSQL equivalent of SQL Server uniqueidentifier?

SQL Server uniqueidentifier maps to PostgreSQL uuid. Both are native 16-byte UUID types, so the mapping is lossless in both directions.

How does nvarchar map to PostgreSQL?

nvarchar(n) maps to character varying(n) and nvarchar(max) maps to text. PostgreSQL columns are Unicode by virtue of the database encoding (typically UTF-8), so there is no separate N-prefixed type to map to. The N-prefix byte width is converted to a character count so nvarchar(50) and varchar(50) both become character varying(50).

What does datetime2 map to in PostgreSQL?

datetime2(n) maps to timestamp(n) without time zone, preserving the fractional-seconds precision. SQL Server datetimeoffset(n) maps to timestamp(n) with time zone. Legacy datetime maps to timestamp(3) and smalldatetime to timestamp(0).

Is the SQL Server to PostgreSQL type mapping reversible?

Yes, the mapping is symmetric: it routes every type through a canonical type model, so the same engine maps PostgreSQL back to SQL Server (and between any pair of MSSQL, PostgreSQL, MySQL, Oracle, and SQLite). A few cases are documented as lossy — money loses its spelling, tinyint widens to smallint, and PostgreSQL char types come back as N-prefixed SQL Server types because they are Unicode.

Apply This Mapping in Two Clicks

Right-click a table, pick a target engine, preview the translated DDL. MSSQL, PostgreSQL, MySQL, Oracle, SQLite. Free for personal use.

Related