Understanding Referential Integrity
corebeginnerReferential integrity is the guarantee that a foreign key value always points to a row that genuinely exists. PostgreSQL enforces it automatically for any column declared REFERENCES another table — an INSERT or UPDATE that would point at a nonexistent row is rejected outright.
Think of it as
Without referential integrity, a column merely "looks like" a reference — nothing stops it from holding a value that used to be valid but no longer corresponds to any real row, an orphaned pointer into nothing. A FOREIGN KEY constraint converts that hopeful convention into an enforced guarantee: every value in the column is checked against the referenced table on every write, and PostgreSQL simply refuses any write that would break the guarantee, rather than allowing a dangling reference to be created in the first place.
What we're doing: Compare a plain integer column against a real foreign key by attempting to reference a nonexistent customer in both.
- 2
- orders_unsafe.customer_id is a plain INT — it "means" a customer reference by convention only.
- 3
- orders_safe.customer_id has a real FOREIGN KEY — PostgreSQL enforces the reference.
- 7–8
- Customer 999 does not exist, but orders_unsafe has no way to know or care.
- 10–11
- The same insert against orders_safe is rejected outright — an orphaned reference is structurally prevented.
INSERT 0 1
ERROR: insert or update on table "orders_safe" violates foreign key constraint
DETAIL: Key (customer_id)=(999) is not present in table "customers".Why this works: orders_unsafe.customer_id is just an ordinary integer as far as PostgreSQL is concerned — nothing about its declaration links it to customers, so a value of 999 is accepted exactly like any other integer, regardless of whether a customer with that id exists. orders_safe.customer_id carries an actual FOREIGN KEY constraint, which PostgreSQL checks on every write by looking up the value in customers — this is the mechanical difference between a column that merely represents a relationship by convention and one where the relationship is genuinely enforced.
Using a plain integer column to represent a relationship instead of a real foreign key
Wrong
Better
What you see: Orphaned rows accumulate over time — orders referencing customers that were deleted through some code path that did not check first — discovered only when a report or join unexpectedly produces missing or NULL customer data.
Why: A column named customer_id with no FOREIGN KEY constraint communicates intent to human readers but enforces nothing — any bug, race condition, or overlooked code path that deletes a customer without also handling their orders produces silent data corruption that no error ever surfaces. A real FOREIGN KEY constraint moves that guarantee from "hopefully true, if every code path remembers" to "mechanically enforced by the database, from any code path," the same principle every earlier constraint concept in this course has established.
- customer_id INT (no constraint)
- nothing verifies the value refers to a real customer
- a deleted customer silently orphans every order that "referenced" them
- correctness depends entirely on application code remembering to check
- customer_id INT REFERENCES customers(id)
- every INSERT/UPDATE is checked against customers
- deleting a referenced customer is blocked (or cascaded/nulled, per ON DELETE)
- correctness is guaranteed by PostgreSQL itself
With and without an enforced foreign key
Together
Remember: Referential integrity is the guarantee that a foreign key value always points to a row that genuinely exists — a plain integer column only represents a relationship by convention; a real FOREIGN KEY constraint enforces it on every write.
See also: one to one one to many many to many · primary foreign unique not null check

