Working with Constraints
1. Adding PRIMARY KEY Constraint
ALTER TABLE customers ADD CONSTRAINT customers_pkey PRIMARY KEY (id);
-- Concurrently (large tables)
CREATE UNIQUE INDEX CONCURRENTLY customers_pkey ON customers(id);
ALTER TABLE customers ADD CONSTRAINT customers_pkey PRIMARY KEY USING INDEX customers_pkey;
| Property | Detail |
|---|---|
| Implies | UNIQUE + NOT NULL |
| Per table | Only one PK |
| Backed by | Unique btree index |
2. Adding UNIQUE Constraint
ALTER TABLE customers ADD CONSTRAINT customers_email_key UNIQUE (email);
ALTER TABLE orders ADD CONSTRAINT orders_unique UNIQUE NULLS NOT DISTINCT (customer_id, code);
| Clause | Effect |
|---|---|
| NULLS DISTINCT | Default; multiple NULLs allowed |
| NULLS NOT DISTINCT | Treat NULLs as equal PG 15+ |
3. Adding NOT NULL Constraint
ALTER TABLE customers ALTER COLUMN email SET NOT NULL;
-- Safe pattern on large tables
ALTER TABLE customers ADD CONSTRAINT email_nn CHECK (email IS NOT NULL) NOT VALID;
ALTER TABLE customers VALIDATE CONSTRAINT email_nn;
4. Adding CHECK Constraint
ALTER TABLE orders
ADD CONSTRAINT orders_total_pos CHECK (total_cents >= 0) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT orders_total_pos;
| Flag | Effect |
|---|---|
| NOT VALID | Only enforced for new rows; no scan |
| VALIDATE CONSTRAINT | Background scan, ShareUpdateExclusiveLock |
5. Adding DEFAULT Values
ALTER TABLE orders ALTER COLUMN status SET DEFAULT 'new';
ALTER TABLE events ALTER COLUMN created_at SET DEFAULT now();
6. Adding FOREIGN KEY Constraint
ALTER TABLE order_items
ADD CONSTRAINT order_items_order_fk
FOREIGN KEY (order_id) REFERENCES orders(id)
ON DELETE CASCADE ON UPDATE NO ACTION;
Note: Always index the referencing column to avoid full scans on parent updates/deletes.
7. Using ON DELETE Actions
| Action | Behavior on Parent Delete |
|---|---|
| NO ACTION | Default; deferred error |
| RESTRICT | Immediate error |
| CASCADE | Delete child rows |
| SET NULL | Set FK column NULL |
| SET DEFAULT | Set FK column to default |
| SET NULL (col) | Subset of FK columns PG 15+ |
8. Using ON UPDATE Actions
| Action | Behavior on Parent PK Change |
|---|---|
| NO ACTION | Deferred error if children |
| CASCADE | Propagate new key value |
| SET NULL / SET DEFAULT | As above |
9. Creating Named Constraints
ALTER TABLE products
ADD CONSTRAINT products_price_nonneg CHECK (price >= 0);
| Reason | Benefit |
|---|---|
| Predictable | Easier to DROP/VALIDATE |
| Diff-friendly | Stable in migrations |
10. Adding Column Constraints
CREATE TABLE products (
id bigserial PRIMARY KEY,
sku text UNIQUE NOT NULL,
price numeric(12,2) CHECK (price >= 0)
);
11. Adding Table Constraints
CREATE TABLE bookings (
user_id int,
slot tstzrange,
PRIMARY KEY (user_id, slot),
CONSTRAINT no_overlap EXCLUDE USING gist (user_id WITH =, slot WITH &&)
);
12. Deferring Constraints
ALTER TABLE order_items
ALTER CONSTRAINT order_items_order_fk DEFERRABLE INITIALLY DEFERRED;
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
-- bulk swap parent/child rows
COMMIT;
| Mode | Check When |
|---|---|
| NOT DEFERRABLE | Per-statement (default) |
| DEFERRABLE INITIALLY IMMEDIATE | Per-statement; can defer per-tx |
| DEFERRABLE INITIALLY DEFERRED | At commit |
13. Using Exclusion Constraints
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE meetings (
room_id int NOT NULL,
slot tstzrange NOT NULL,
EXCLUDE USING gist (room_id WITH =, slot WITH &&)
);
14. Dropping Constraints
ALTER TABLE orders DROP CONSTRAINT orders_total_pos;
ALTER TABLE orders DROP CONSTRAINT IF EXISTS orders_total_pos;
15. Disabling Constraints
| Constraint | How To Bypass |
|---|---|
| FK | Drop+recreate or session_replication_role='replica' |
| CHECK | No disable; drop or use NOT VALID + skip validation |
| Triggers / FKs | ALTER TABLE ... DISABLE TRIGGER ALL |
Warning: PostgreSQL has no
DISABLE CONSTRAINT; the closest is session_replication_role (superuser-only) or temporarily dropping the constraint.