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;
PropertyDetail
ImpliesUNIQUE + NOT NULL
Per tableOnly one PK
Backed byUnique 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);
ClauseEffect
NULLS DISTINCTDefault; multiple NULLs allowed
NULLS NOT DISTINCTTreat 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;
FlagEffect
NOT VALIDOnly enforced for new rows; no scan
VALIDATE CONSTRAINTBackground 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

ActionBehavior on Parent Delete
NO ACTIONDefault; deferred error
RESTRICTImmediate error
CASCADEDelete child rows
SET NULLSet FK column NULL
SET DEFAULTSet FK column to default
SET NULL (col)Subset of FK columns PG 15+

8. Using ON UPDATE Actions

ActionBehavior on Parent PK Change
NO ACTIONDeferred error if children
CASCADEPropagate new key value
SET NULL / SET DEFAULTAs above

9. Creating Named Constraints

ALTER TABLE products
  ADD CONSTRAINT products_price_nonneg CHECK (price >= 0);
ReasonBenefit
PredictableEasier to DROP/VALIDATE
Diff-friendlyStable 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;
ModeCheck When
NOT DEFERRABLEPer-statement (default)
DEFERRABLE INITIALLY IMMEDIATEPer-statement; can defer per-tx
DEFERRABLE INITIALLY DEFERREDAt 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

ConstraintHow To Bypass
FKDrop+recreate or session_replication_role='replica'
CHECKNo disable; drop or use NOT VALID + skip validation
Triggers / FKsALTER TABLE ... DISABLE TRIGGER ALL
Warning: PostgreSQL has no DISABLE CONSTRAINT; the closest is session_replication_role (superuser-only) or temporarily dropping the constraint.