Lesson 6 of 9 · 18m
Constraints Are Your Application's Spine
The database is the last line of correctness — check constraints, foreign keys, exclusion constraints, and generated columns encode invariants no application bug can break.
Every application layer that validates data eventually has a bug, a race, or a migration script that bypasses it. The database is the only place invariants can't be skipped — encode them there and the app can be wrong without corrupting anything.
Check constraints encode business rules
ALTER TABLE bookings ADD CONSTRAINT sensible_range
CHECK (check_out > check_in);
ALTER TABLE orders ADD CONSTRAINT positive_total
CHECK (total_cents >= 0);total_cents < 0 becomes impossible — not "probably handled in the service layer," impossible. Retroactively adding one to dirty data fails loudly, which is the point: NOT VALID lets you add the constraint to a huge table without scanning it (ALTER TABLE ... ADD CONSTRAINT ... NOT VALID then VALIDATE CONSTRAINT in a second, lock-light pass).
Foreign keys are referential integrity
ON DELETE CASCADE removes dependent rows; ON DELETE SET NULL orphans the reference politely; default RESTRICT refuses the delete — choose per relationship, and always index the referencing column or cascades become table scans.
Exclusion constraints for overlap rules
"No two bookings overlap the same room" can't be written as a unique constraint:
ALTER TABLE bookings ADD CONSTRAINT no_overlap
EXCLUDE USING gist (room_id WITH =, slot WITH &&);This rejects double-bookings at the storage layer — with a lockless GiST index, no transaction ordering required. The class of bug (race during check-then-insert) simply ceases to exist.
Generated columns denormalize safely
ALTER TABLE products ADD COLUMN search_text tsvector
GENERATED ALWAYS AS (to_tsvector('english', name || ' ' || coalesce(description,''))) STORED;A maintained-in-database derived column — always consistent, indexable, and no application code can forget to update it.
Field notes
UNIQUEis an index plus a constraint — the constraint is what protects you; use it.- Adding
NOT NULLto an existing column needs aCHECK ... NOT VALID+ backfill +VALIDATE+SET NOT NULLon big tables to avoid a full-table scan under lock.