Numbers
| Type | Use |
|---|---|
SMALLINT |
−32k … 32k |
INTEGER / INT |
±2.1 billion — the default |
BIGINT |
very large ids, counters |
NUMERIC(p,s) |
exact decimals — money |
REAL / DOUBLE PRECISION |
approximate floats — science, not money |
SERIAL / BIGSERIAL |
auto-incrementing integer |
Money goes in NUMERIC, never in a float:
SQL
SELECT 0.1::float + 0.2::float; -- 0.30000000000000004
SELECT 0.1::numeric + 0.2::numeric; -- 0.3Identity columns
SERIAL is the old way. Modern Postgres prefers standard identity
columns:
SQL
CREATE TABLE books (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL
);Text
| Type | Notes |
|---|---|
TEXT |
unlimited — use this by default |
VARCHAR(n) |
limited length |
CHAR(n) |
fixed, space-padded — avoid |
In Postgres, TEXT and VARCHAR perform identically; there is no
efficiency reason to pick VARCHAR(255). Use TEXT plus a CHECK
constraint if you need a limit.
Dates and times
| Type | Stores |
|---|---|
DATE |
date only |
TIME |
time only |
TIMESTAMP |
date+time, no timezone |
TIMESTAMPTZ |
date+time, timezone-aware |
INTERVAL |
a duration |
Use TIMESTAMPTZ for almost everything. Plain TIMESTAMP records a
wall-clock reading with no idea what zone it came from, which becomes
unfixable later.
SQL
SELECT now(); -- current timestamptz
SELECT now() + INTERVAL '7 days';
SELECT age(now(), '1990-01-01');
SELECT date_trunc('month', now()); -- start of this month
SELECT EXTRACT(DOW FROM now()); -- day of weekBoolean
SQL
CREATE TABLE t (is_active BOOLEAN NOT NULL DEFAULT true);Accepts true/false, 't'/'f', 'yes'/'no', 1/0.
UUID
SQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
);UUIDs avoid exposing row counts and let clients generate ids offline, at the cost of being larger and less index-friendly than integers.
Arrays
SQL
CREATE TABLE posts (tags TEXT[]);
INSERT INTO posts (tags) VALUES (ARRAY['nepali', 'fiction']);
SELECT * FROM posts WHERE 'fiction' = ANY(tags);
SELECT * FROM posts WHERE tags @> ARRAY['nepali']; -- containsENUM
SQL
CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'cancelled');
CREATE TABLE orders (status order_status NOT NULL DEFAULT 'new');Type-safe, but adding a value requires ALTER TYPE. Many teams prefer
TEXT with a CHECK constraint for easier evolution.
Casting
SQL
SELECT '42'::INTEGER;
SELECT CAST('42' AS INTEGER);
SELECT price::TEXT FROM books;