---
title: "SQL Server to PostgreSQL data types"
description: "Every SQL Server data type that PostgreSQL stores differently, what the converter writes for it, when, and the report line."
canonical: https://intosql.com/docs/types/sql-server-to-postgresql
last_updated: 2026-10-15
---

# SQL Server to PostgreSQL data types

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](/sql-server-to-postgresql) opens the converter set up for this pair. The [SQL Server to PostgreSQL migration guide](/docs/migrations/sql-server-to-postgresql) 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

- SQL Server into [MySQL](/docs/types/sql-server-to-mysql), [MariaDB](/docs/types/sql-server-to-mariadb), [SQLite](/docs/types/sql-server-to-sqlite), [SQL Server](/docs/types/sql-server-to-sql-server), [Oracle](/docs/types/sql-server-to-oracle)
- Into PostgreSQL from [MySQL](/docs/types/mysql-to-postgresql), [MariaDB](/docs/types/mariadb-to-postgresql), [PostgreSQL](/docs/types/postgresql-to-postgresql), [SQLite](/docs/types/sqlite-to-postgresql), [Oracle](/docs/types/oracle-to-postgresql)
- [Every pair](/docs/types#from-one-database-to-another)
