Each row is a type from SQL Server that PostgreSQL stores differently: what the PostgreSQL script gets, why, and the line the report writes about it. The rows come from the converter's own rules, so this page matches what it does.
Convert in the browser: SQL Server to PostgreSQL opens the converter set up for this pair. The SQL Server to PostgreSQL migration guide covers dumping, loading and the rules for keys, indexes and names.
A report line is a fix when the converter changed something and lists it, or a problem when a value or object is worth a look.
Numbers#
| In SQL Server |
In PostgreSQL |
Why |
Report line |
| tinyint |
smallint with a CHECK that it isn't negative |
SQL Server's tinyint runs from 0 to 255; PostgreSQL's smallest integer is signed, so a check keeps its range. |
unsigned_widened (fix) |
| decimal(p,s), money, smallmoney |
numeric(p,s); money as numeric(19,4) |
Every digit is kept, money with its four decimals. |
None |
Booleans and bits#
| In SQL Server |
In PostgreSQL |
Why |
Report line |
| bit |
boolean |
bit holds 1 and 0, which are PostgreSQL's true and false. |
None |
Text#
| In SQL Server |
In PostgreSQL |
Why |
Report line |
| char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), text |
varchar(n) and text |
PostgreSQL text holds every character, so varchar and nvarchar are the same type. |
None |
| NUL characters in text |
removed |
PostgreSQL text can't hold a NUL character. It is removed and the report counts the values. |
nul_removed (problem) |
| text that isn't valid in its encoding |
the bad bytes as the replacement character |
PostgreSQL text must be valid UTF-8. |
invalid_utf8_replaced (problem) |
Dates and times#
| In SQL Server |
In PostgreSQL |
Why |
Report line |
| datetime, datetime2, smalldatetime |
timestamp(p) |
A datetime is a wall-clock reading, like a timestamp without time zone. |
None |
| datetime2(7), time(7) and datetimeoffset(7) |
six digits of a second, the seventh cut |
PostgreSQL keeps microseconds; the seventh digit (100 nanoseconds) is cut, never rounded, so no value moves to the next second or day. |
fraction_cut (fix) |
| datetimeoffset |
timestamptz, the same instants |
timestamptz keeps the instant; PostgreSQL shows it in the session's time zone instead of the offset each value had. |
None |
| time |
time(p) |
The same times of day. |
None |
| dates that aren't valid |
NULL |
A value that isn't a date becomes NULL and the report counts it. |
date_out_of_range (problem) |
Binary#
| In SQL Server |
In PostgreSQL |
Why |
Report line |
| binary, varbinary, image |
bytea |
Bytes stay bytes. |
None |
| rowversion, timestamp |
bytea, empty |
SQL Server sets a row version by itself and SSMS scripts no values for it. |
rowversion_empty (fix) |
JSON, spatial and other types#
| In SQL Server |
In PostgreSQL |
Why |
Report line |
| json (SQL Server 2025) |
jsonb |
The same JSON values. |
None |
| uniqueidentifier |
uuid |
The same identifiers, written in lowercase. |
None |
| geometry, geography |
bytea with SQL Server's own bytes |
SSMS writes spatial values in SQL Server's serialization, which is kept byte for byte. |
spatial_as_native (fix) |
| sql_variant, hierarchyid, vector |
text |
PostgreSQL has no equivalent type; sql_variant keeps each value's text and hierarchyid its path, such as /1/2/. xml stays xml. |
type_replaced (fix) |
| types IntoSQL doesn't know |
text |
Text holds any value; change the type after loading. |
unknown_type (problem) |
Counters and identities#
| In SQL Server |
In PostgreSQL |
Why |
Report line |
| IDENTITY |
GENERATED BY DEFAULT AS IDENTITY, continuing after the highest id |
The copied ids load as they are, and the counter continues after the highest one. |
None |
Other pairs#