SQL Data Type Converter

Pick a source engine and a type — or scan the full mapping table below — to get the equivalent data type in SQL Server, PostgreSQL, MySQL, Oracle, and SQLite, plus the pitfalls that break migrations: Oracle DATE stores time-of-day, MySQL TIMESTAMP stops at 2038, tinyint flips sign between engines. Everything runs in your browser.

The complete cross-engine type mapping

One row per canonical type, one column per engine. This is the same canonical type model Jam SQL Studio uses when it migrates a table or runs a cross-engine schema compare — every cell is what the tool actually emits, not a wiki copy. Numbered markers point to the notes below: that is where the lossy and surprising mappings live. For the SQL Server ↔ PostgreSQL pair in full depth (both directions, value coercion rules), see the MSSQL ↔ PostgreSQL data type mapping reference; for the procedural side of that move, see T-SQL vs PL/pgSQL.

Integers

TypeSQL ServerPostgreSQLMySQLOracleSQLite
tinyint (8-bit) 1tinyintsmallinttinyintnumber(3)TINYINT
smallint (16-bit) 2smallintsmallintsmallintnumber(4)SMALLINT
int (32-bit) 2 3intintegerintnumber(9)INTEGER
bigint (64-bit) 2bigintbigintbigintnumber(19)BIGINT

Exact decimals

TypeSQL ServerPostgreSQLMySQLOracleSQLite
decimal(p,s) / numeric(p,s) 3 4decimal(p,s)numeric(p,s)decimal(p,s)number(p,s)NUMERIC(p,s)

Floating point

TypeSQL ServerPostgreSQLMySQLOracleSQLite
real (4-byte float) 5realrealfloatbinary_floatREAL
float / double (8-byte float) 5 6floatdouble precisiondoublebinary_doubleREAL

Boolean

TypeSQL ServerPostgreSQLMySQLOracleSQLite
boolean / bit 7 8 9bitbooleantinyint(1)number(1)BOOLEAN

Character strings

TypeSQL ServerPostgreSQLMySQLOracleSQLite
char(n) 10char(n)character(n)char(n)char(n)CHAR(n)
varchar(n) 11varchar(n)character varying(n)varchar(n)varchar2(n)VARCHAR(n)
varchar(max) / text / clob 12varchar(max)texttextclobTEXT

Unicode strings

TypeSQL ServerPostgreSQLMySQLOracleSQLite
nchar(n) 13nchar(n)character(n)char(n)nchar(n)NCHAR(n)
nvarchar(n) 13nvarchar(n)character varying(n)varchar(n)nvarchar2(n)NVARCHAR(n)
nvarchar(max) / ntext / nclob 12 13nvarchar(max)texttextnclobTEXT

Binary

TypeSQL ServerPostgreSQLMySQLOracleSQLite
binary(n) 14 15binary(n)byteabinary(n)raw(n)BLOB
varbinary(n) 14 15varbinary(n)byteavarbinary(n)raw(n)BLOB
varbinary(max) / blob / bytea 16varbinary(max)byteablobblobBLOB

Date & time

TypeSQL ServerPostgreSQLMySQLOracleSQLite
date 17datedatedatetimestampDATE
time 18time(n)time(n)time(n)timestampTIME
datetime / timestamp (no time zone) 17 19 20datetime2(n)timestamp(n)datetime(n)timestamp(n)DATETIME
datetimeoffset / timestamptz 20 21 22datetimeoffset(n)timestamp(n) with time zonetimestamp(n)timestamp(n) with time zoneDATETIME

UUID

TypeSQL ServerPostgreSQLMySQLOracleSQLite
uniqueidentifier / uuid 23uniqueidentifieruuidchar(36)raw(16)TEXT

JSON

TypeSQL ServerPostgreSQLMySQLOracleSQLite
json / jsonb 24jsonjsonbjsonjsonJSON

XML

TypeSQL ServerPostgreSQLMySQLOracleSQLite
xml 25xmlxmltextxmltypeTEXT

Money

TypeSQL ServerPostgreSQLMySQLOracleSQLite
money (SQL Server) 26moneynumeric(19,4)decimal(19,4)number(19,4)NUMERIC(19,4)
smallmoney (SQL Server) 26smallmoneynumeric(10,4)decimal(10,4)number(10,4)NUMERIC(10,4)
money (PostgreSQL) 26decimal(19,2)moneydecimal(19,2)number(19,2)NUMERIC(19,2)

Other

TypeSQL ServerPostgreSQLMySQLOracleSQLite
rowversion / timestamp (SQL Server) 27rowversionbyteabinary(8)raw(8)BLOB

Types with no faithful cross-engine equivalent are not in the table because there is nothing honest to put in the other columns: SQL Server sql_variant, hierarchyid and the spatial types; PostgreSQL arrays, ranges, interval, inet/cidr, hstore, tsvector and enums; MySQL enum, set and year; Oracle rowid/urowid and interval types. Jam SQL Studio carries these verbatim and flags them for manual review.

Notes

  1. tinyint sign flip: SQL Server tinyint is unsigned (0 to 255); MySQL TINYINT is signed (-128 to 127) — TINYINT UNSIGNED is the true match. PostgreSQL and Oracle have no 1-byte integer: PostgreSQL widens tinyint to smallint, and it stays smallint on a round-trip.
  2. Oracle has one exact numeric type, NUMBER(p,s). A NUMBER with scale 0 (and INTEGER/INT, which Oracle stores as NUMBER(*,0)) is an integer — Jam SQL Studio picks the width by precision: p <= 4 becomes smallint, p <= 9 becomes int, larger or unspecified becomes bigint. Going the other way it emits number(3), number(4), number(9), number(19).
  3. SQLite declared types are advisory (type affinity): every integer spelling shares INTEGER affinity, NUMERIC(p,s) does not enforce precision or scale, and VARCHAR(n) does not enforce length. INTEGER PRIMARY KEY is special — it becomes the table's rowid.
  4. Precision and scale are preserved across engines, but the caps differ: SQL Server and Oracle allow up to 38 digits, MySQL up to 65, PostgreSQL is effectively unbounded.
  5. SQLite REAL is always 8-byte, so a single-precision real silently widens to double. MySQL FLOAT is the 4-byte type, but MySQL REAL is an alias for DOUBLE by default (the REAL_AS_FLOAT mode flips it).
  6. Oracle FLOAT is NUMBER-based (decimal, not IEEE); the IEEE floating types are BINARY_FLOAT and BINARY_DOUBLE. Cross-engine, Oracle FLOAT is treated as an 8-byte double. Never store currency in floating point.
  7. PostgreSQL bit and bit varying are bit strings, not booleans — only boolean participates in the boolean mapping. SQL Server bit stores 0/1; values are coerced to TRUE/FALSE when rows are copied to PostgreSQL.
  8. MySQL BOOLEAN and BOOL are aliases for TINYINT(1); there is no true boolean type.
  9. Oracle only gained a native BOOLEAN column type in 23ai. On earlier versions the idiom is NUMBER(1) with a 0/1 check constraint — which is what cross-engine migrations emit.
  10. Length caps differ: MySQL CHAR maxes out at 255 characters, Oracle CHAR at 2000 bytes. All engines blank-pad fixed char, but comparison semantics around trailing spaces differ subtly.
  11. Oracle VARCHAR2 caps at 4000 bytes (32767 with extended MAX_STRING_SIZE), and lengths are bytes by default, not characters. An unbounded string does not fit VARCHAR2 at all — it becomes CLOB.
  12. MySQL TEXT stores up to 64 KB — use MEDIUMTEXT (16 MB) or LONGTEXT (4 GB) for larger content. Legacy SQL Server text/ntext and Oracle LONG map to the modern unbounded types here, but should not be used for new columns.
  13. PostgreSQL has no N-prefix concept: character data is always Unicode via the database encoding (typically UTF-8), so nvarchar(50) and varchar(50) both become character varying(50), and PostgreSQL strings migrate back to SQL Server as N-types. In MySQL, Unicode is the column charset (utf8mb4), not the type name. Oracle's NCHAR/NVARCHAR2/NCLOB use the national character set; on an AL32UTF8 database plain VARCHAR2/CLOB already store Unicode, which is why Jam SQL Studio emits the plain spellings when migrating to Oracle.
  14. PostgreSQL has exactly one binary type — bytea, unbounded. Declared binary lengths are dropped on migration.
  15. Oracle RAW is variable-length and caps at 2000 bytes; Oracle has no fixed-length binary type. SQLite has only BLOB.
  16. MySQL BLOB stores up to 64 KB; use LONGBLOB (4 GB) for larger payloads. SQL Server image is legacy — varbinary(max) is the modern spelling.
  17. Oracle DATE includes time-of-day (to the second). It maps to datetime/timestamp in other engines — never to date — and a date-only column migrated to Oracle becomes TIMESTAMP (or DATE with an implicit 00:00:00).
  18. Oracle has no standalone TIME type; TIMESTAMP is the closest carrier. PostgreSQL time with time zone (timetz) has no cross-engine equivalent — the zone is dropped. MySQL TIME doubles as an interval and accepts values from -838:59:59 to 838:59:59.
  19. SQL Server legacy datetime has 3.33 ms precision (values round to .000/.003/.007) — datetime2 is the modern default with 100 ns precision. Cross-engine, datetime maps to timestamp(3) and smalldatetime to timestamp(0).
  20. MySQL TIMESTAMP is UTC-normalized (converted through the session time zone on write and read) and capped at 2038-01-19; DATETIME covers years 1000-9999 with no conversion. For wall-clock values prefer DATETIME — it is what cross-engine migrations emit.
  21. PostgreSQL timestamptz stores a UTC instant, not a zoned value: the original offset is discarded and values are rendered in the session TimeZone on read. SQL Server datetimeoffset actually preserves the offset per row, so a round-trip through PostgreSQL loses it.
  22. SQLite has no time-zone concept; store ISO-8601 text with an explicit offset if you need one.
  23. Only SQL Server (uniqueidentifier) and PostgreSQL (uuid) have native 16-byte UUID types — that mapping is lossless in both directions. MySQL: char(36), or binary(16) with UUID_TO_BIN; Oracle: RAW(16) by convention; SQLite: TEXT.
  24. Native JSON column types: MySQL 5.7+, PostgreSQL (jsonb recommended — binary and indexable; json preserves the exact text), Oracle 21c+, SQL Server 2025 (earlier versions store JSON in nvarchar(max) with an ISJSON check), SQLite (TEXT affinity plus built-in JSON functions).
  25. There is no faithful cross-engine XML mapping — Jam SQL Studio carries xml columns verbatim with a warning. MySQL and SQLite have no XML type; the practical fallback is unbounded text validated by the application.
  26. Money types are exact decimals with engine-specific rounding and formatting: SQL Server money is fixed at (19,4), PostgreSQL money is locale-formatted with 2 decimal places. Neither spelling survives a migration — both become DECIMAL. For portable currency, declare DECIMAL(19,4) yourself.
  27. SQL Server timestamp is not a point in time — it is rowversion, an auto-incrementing 8-byte binary used for optimistic concurrency. It maps to 8-byte binary (bytea, binary(8), raw(8)), never to a temporal type. This naming collision causes more migration bugs than any other type.

Pitfalls that actually break cross-engine migrations

Oracle DATE is a datetime. The most common surprise in Oracle migrations, in both directions: Oracle DATE stores time-of-day to the second. Move an Oracle DATE to PostgreSQL date and you silently truncate every timestamp; move a date-only column into Oracle DATE and equality joins start missing rows the first time something writes a non-midnight time. Treat Oracle DATE as datetime, always.

MySQL TIMESTAMP ends in 2038. MySQL TIMESTAMP is stored as UTC seconds and capped at 2038-01-19 — a contract expiry date or a 30-year mortgage schedule simply does not fit. It is also converted through the session time zone on every read and write, so two clients with different time_zone settings see different wall-clock values. Migrating datetime2 or timestamp columns into MySQL should target DATETIME unless you specifically want UTC normalization.

tinyint flips sign. SQL Server tinyint holds 0–255; MySQL TINYINT holds −128–127. A straight tinyintTINYINT copy overflows on any value above 127 — the correct MySQL target is TINYINT UNSIGNED, and the correct PostgreSQL target is smallint, because PostgreSQL has no 1-byte integer at all.

SQL Server timestamp is not a timestamp. It is rowversion — an auto-incrementing 8-byte binary for optimistic concurrency, nothing to do with dates. Naive migrations map it to a temporal type and then fail on insert. It belongs in an 8-byte binary column (bytea, binary(8), raw(8)), and usually deserves to be dropped and regenerated rather than copied.

SQLite will accept anything. SQLite declared types are advisory: VARCHAR(10) happily stores a 10,000-character string and NUMERIC(10,2) enforces nothing. That makes migrating into SQLite forgiving — and migrating out dangerous, because the declared types make promises the data never kept. Validate lengths and scales before moving SQLite data to a strict engine.

Frequently asked questions

What is the PostgreSQL equivalent of NVARCHAR(MAX)?

text. PostgreSQL stores all character data in the database encoding (typically UTF-8), so every varchar and text column is already Unicode — there is no N-prefixed type. nvarchar(n) maps to character varying(n) and nvarchar(max) maps to text.

What is the Oracle equivalent of SQL Server datetime2?

TIMESTAMP(n), preserving the fractional-seconds precision. datetimeoffset(n) maps to TIMESTAMP(n) WITH TIME ZONE. Oracle DATE is closest to the legacy datetime family: it stores date and time to the second, with no fractional seconds.

Does MySQL have a BOOLEAN type?

Not a real one — BOOLEAN and BOOL are aliases for TINYINT(1). MySQL stores 0 and 1 (any non-zero value is true), and SHOW CREATE TABLE reports tinyint(1). When migrating, SQL Server bit and PostgreSQL boolean both map to tinyint(1) on MySQL.

What replaces uniqueidentifier in PostgreSQL and MySQL?

PostgreSQL has a native uuid type — a lossless 16-byte match for uniqueidentifier. MySQL has no UUID column type: use char(36) for readability, or binary(16) with UUID_TO_BIN and BIN_TO_UUID for compact storage. Oracle convention is RAW(16); SQLite stores UUIDs as TEXT.

Should I use MONEY types?

Generally no. Money types are exact decimals (no floating-point error) but engine-specific: SQL Server money is fixed at 4 decimal places with its own rounding, PostgreSQL money is locale-formatted with 2, and MySQL, Oracle, and SQLite have no money type at all. DECIMAL(19,4) is the portable choice — and it is what money becomes in any cross-engine migration.

How do I convert a whole table's schema between engines automatically?

Jam SQL Studio's Migrate Table to Connection (a Pro feature) does it in two clicks: right-click a table, pick a target connection on any of the five engines, and preview the translated CREATE TABLE with every column mapped by the rules on this page — types with no faithful equivalent are flagged for manual review. Cross-engine Schema Compare uses the same canonical type model to diff tables across engines.

Migrating a real schema between engines?

Jam SQL Studio's cross-engine Schema Compare and table migration apply this exact mapping automatically — right-click a table, pick a target engine, and preview the translated DDL with every unmappable type flagged.

Free for personal use • No account required • Mac, Windows, Linux

More free SQL tools