17 Column Types and Constraints
A real table picks a type for every column and often adds constraints — rules the database enforces on every row. The most common constraints are NOT NULL (the column must have a value), DEFAULT (use this value if none is given), CHECK (a boolean expression that must be true), UNIQUE, PRIMARY KEY, and REFERENCES (foreign key). Each gets its own lesson; here we combine several into one realistic table.
What you’ll learn
- Combining multiple types and constraints in one
CREATE TABLE - Inline
DEFAULTandNOT NULL CHECKconstraints for domain rules- Using
timestamptzwithcurrent_timestampas a default - Inspecting constraints in the catalog
Example
Each constraint is one promise the database makes about your data. NOT NULL says the value is always present. DEFAULT supplies a value when an INSERT omits the column. CHECK (expr) rejects any row where expr is false (NULLs pass). PRIMARY KEY implies NOT NULL and UNIQUE, and creates an index.
Constraints can be inline (after the column type, as above) or table-level (after all the columns). Table-level syntax is required for composite PRIMARY KEY, UNIQUE, and foreign-key constraints. A CHECK can reference several columns in either position, and both column and table constraints can be named with CONSTRAINT name for clearer errors.
timestamptz NOT NULL DEFAULT current_timestamp is one of the most common column patterns — the row’s creation time, stored in UTC, never null, set automatically.
Run the example
To run:
Common pitfalls
- Forgetting
NOT NULLon columns that should never be null — adding it later requires backfilling and may take a long lock on a large table. CHECKaccepts a row when the expression isNULL. To reject NULLs, combineNOT NULLandCHECK.- Using
now()as aDEFAULT: it works, but inside a long transaction every default is the transaction-start time. Usually that is fine; for true per-row wall-clock useclock_timestamp(). - Adding a
DEFAULTandNOT NULLto a new column on a huge table used to rewrite the entire table; modern PostgreSQL (11+) avoids that rewrite when the default is a constant, but volatile defaults still rewrite.
Name your constraints. The default constraint names PostgreSQL generates are functional but cryptic. A name like products_price_nonneg is much more helpful when it appears in an error message in production.
Try INSERT INTO products (id, name, price) VALUES (4, ' ', 5); and observe the name_not_blank check rejection. Try INSERT INTO products (id, name, price) VALUES (5, 'Negative', -1); to see the price check trigger. Then add a constraint after the fact: ALTER TABLE products ADD CONSTRAINT category_valid CHECK (category IN ('misc','parts','tools'));.
Downloads
SQL file: column-types-and-constraints.sql · All lesson files · Setup guide