Beginner

PostgreSQL Data Types: Choosing the Right Column Type

The type you pick for a column decides what it can store, how it sorts, and how fast it queries. A practical tour of numbers, text, dates, booleans, JSON, and the choices that bite later.

A column’s type is a contract: it decides what values are allowed, how they sort and compare, what operations work, and how efficiently the column stores and indexes. Choosing well up front saves painful migrations later. This guide covers the PostgreSQL types you will actually use. Keep the SQL cheat sheet open as you read.

Why Types Matter

Store a price as text and you cannot do math on it, it sorts "100" before "20", and an index on it is useless for ranges. The right type makes correct behaviour automatic. PostgreSQL is strict about types, which catches bugs early instead of letting bad data in.

Numbers

Use integer types (smallint, integer, bigint) for whole numbers like counts and ids. Use numeric (also called decimal) for money and anything where exact precision matters. Avoid float/real for currency — binary floating point cannot represent values like 0.10 exactly.

quantity int           NOT NULL,  -- whole number
price    numeric(10,2) NOT NULL,  -- exact: up to 10 digits, 2 after the point
rating   real                     -- approximate float, fine for non-money

Text

In PostgreSQL, text and varchar are stored identically and perform the same — text has no length limit and is the simplest choice. Use varchar(n) only when you genuinely need to enforce a maximum length. Plain char(n) is almost never the right call; it pads with spaces.

name text        NOT NULL,  -- no length limit, idiomatic
code varchar(8)              -- only when a real max length applies

Dates & Times

Use date for a calendar day, and timestamptz (timestamp with time zone) for a moment in time. Prefer timestamptz over plain timestamp almost always — it stores an unambiguous instant and converts to the session time zone on display, which avoids a whole class of time-zone bugs.

birthday   date        NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()

Boolean & UUID

Use boolean for true/false flags rather than an integer 0/1 or a text 'Y'/'N' — it is clearer and self-documenting. Use uuid for globally unique identifiers when you need ids that are unguessable or generated outside the database.

is_active boolean NOT NULL DEFAULT true,
public_id uuid    NOT NULL DEFAULT gen_random_uuid()

JSON & Arrays

PostgreSQL can store semi-structured data with jsonb (binary JSON, indexable and faster than plain json) and even native arrays. They are powerful for flexible attributes — but if you find yourself querying deep into JSON constantly, that data probably wants real columns or tables instead.

metadata jsonb,                -- queryable, indexable JSON
tags     text[] NOT NULL DEFAULT '{}'  -- a real array of text

-- Query into jsonb
SELECT * FROM events WHERE metadata->>'source' = 'mobile';

Common Mistakes & Tips

  • Storing money as float or real — use numeric for exact arithmetic
  • Storing numbers or dates as text, which breaks sorting, math, and indexes
  • Using plain timestamp instead of timestamptz and inheriting time-zone bugs
  • Reaching for char(n) — it pads with spaces; prefer text or varchar
  • Dumping everything into jsonb instead of modelling data you actually query

Picking the right type is a small decision with long consequences. Practice on realistic, well-typed datasets to see how numeric, text, and timestamptz behave in real queries.