Constraints — Introduction
Rules the database enforces on every row, no matter who writes the data. The six PostgreSQL constraint types, the difference between column-level and table-level, and how to add or drop them on a live table.
Constraints are rules that the database itself enforces about the data inside a table. They sit alongside the column definitions and make sure every row obeys them — no matter who's writing, from which application, with what code. Constraints are the most reliable way to enforce data quality, because they're checked at the storage level, not in some application layer that might have a bug or get bypassed.
The five everyday constraints in PostgreSQL — NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY, CHECK — cover the vast majority of integrity rules you'll ever need. PostgreSQL also offers a sixth, the EXCLUSION constraint, for special-purpose use cases.
| Constraint | What it enforces |
|---|---|
| NOT NULL | The column must have a value — NULL is not allowed. |
| UNIQUE | Every value in the column (or set of columns) is distinct across rows. |
| PRIMARY KEY | NOT NULL + UNIQUE — the row's permanent identifier; one per table. |
| FOREIGN KEY | Values must match an existing row in another table — cross-table integrity. |
| CHECK | A boolean expression that must be true for every row. |
| EXCLUSION | No two rows may match a defined "conflict" condition (e.g. overlapping ranges). |
Most constraints can be written in two places:
- Column-level — attached directly to a column definition. Simple and idiomatic for single-column constraints.
- Table-level — declared after all the column definitions. Required when the constraint involves multiple columns.
-- Column-level: each constraint sits next to its column
CREATE TABLE products (
product_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price NUMERIC(10,2) CHECK (price >= 0)
);
-- Table-level: constraints listed at the end
CREATE TABLE orders (
order_id INT,
customer_id INT,
qty INT,
CONSTRAINT orders_pk PRIMARY KEY (order_id),
CONSTRAINT orders_qty_chk CHECK (qty > 0),
CONSTRAINT orders_cust_fk FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
orders_qty_check, etc.), error messages and migrations get harder to follow. Use CONSTRAINT name_ explicitly for anything important.You don't have to declare every constraint at table-creation time. ALTER TABLE lets you add or drop them on a live table.
-- Add a CHECK to an existing table
ALTER TABLE products
ADD CONSTRAINT price_positive CHECK (price >= 0);
-- Drop it again
ALTER TABLE products
DROP CONSTRAINT price_positive;
Adding a constraint scans the existing rows and rejects the change if any row already violates it. So get your data in order first, then enforce.
To see what's defined on a table, query the system catalog or use \d table_name in psql:
SELECT conname, contype, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid = 'orders'::regclass;
- NOT NULL — make a column required.
- UNIQUE — guarantee no duplicates.
- PRIMARY KEY — the row's identity.
- FOREIGN KEY — link tables together.
- CHECK — your own boolean rule.