MySQL UNSIGNED Integer → PostgreSQL Migration
Why PostgreSQL has no unsigned integer types, and how to pick a signed type that covers the same value range without silent truncation.
BLOCKER
Quick answer
PostgreSQL has no unsigned integer family at all. Move to the next-larger signed type that comfortably covers your real maximum value, or add a CHECK (col >= 0) constraint if the current type is already large enough.
Why it breaks
A MySQL "INT UNSIGNED" column stores 0 to ~4.29 billion; PostgreSQL's INTEGER stores roughly -2.1 billion to +2.1 billion — only about half the positive range. Renaming the type one-for-one silently caps every value above ~2.1 billion, which fails on insert or, worse, wraps/overflows depending on how the load tool handles it.
Real examples
Bumping to the next-larger signed type
CREATE TABLE users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
); CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY,
PRIMARY KEY (id)
);
-- INT UNSIGNED (max ~4.29B) needs BIGINT (max ~9.2 quintillion), not INTEGER (max ~2.1B) CHECK constraint when the type already has headroom
CREATE TABLE inventory (
quantity SMALLINT UNSIGNED NOT NULL
); CREATE TABLE inventory (
quantity INTEGER NOT NULL CHECK (quantity >= 0)
); Safe migration options
- Bump to the next-larger signed type: TINYINT UNSIGNED → SMALLINT, SMALLINT UNSIGNED → INTEGER, INT UNSIGNED → BIGINT, BIGINT UNSIGNED → NUMERIC(20,0) (no signed 64-bit type covers the full unsigned BIGINT range).
- If the column is already comfortably within range for the smaller signed type, keep the type and add CHECK (column_name >= 0) to preserve the non-negative constraint.
- For a primary/foreign key specifically, prefer the type bump over the CHECK constraint — join performance depends on matching types on both sides.
PostgreSQL solution
There is no PostgreSQL syntax equivalent to UNSIGNED. The fix is a type decision made per column based on the actual data range, not a keyword swap.
Validation
Before cutover
Before cutover, run: SELECT MAX(col) FROM table; against the source MySQL table and confirm the chosen PostgreSQL type's max value comfortably exceeds it — don't just check the current max, budget headroom for growth.
Scan your own schema
This page documents detector rule UNSIGNED in the DevEquiv analyzer — paste or
upload your real MySQL/MariaDB schema to find every occurrence of this (and 19 other
documented issues) automatically, with a prioritized readiness report.
References
- MySQL Manual — Integer Types (v8.0) — checked 2026-07-30
- PostgreSQL Manual — Numeric Types (v17) — checked 2026-07-30
Last verified 2026-07-30.