20 Foreign Keys and REFERENCES
A foreign key is a constraint that says “the value in this column must already exist as a primary key (or unique key) in some other table.” It is how relational databases enforce referential integrity — you cannot orphan a child row by pointing it at a non-existent parent, and (depending on the rules you choose) the database can cascade or restrict updates to the parent. Every relational model that involves multiple tables needs foreign keys.
What you’ll learn
- Declaring a foreign key with
REFERENCES - The
ON DELETEandON UPDATEactions:NO ACTION,RESTRICT,CASCADE,SET NULL,SET DEFAULT - Why an index on the FK column is almost always a good idea
- How
DEFERRABLEconstraints let you batch changes inside a transaction - Inspecting foreign keys in the catalog
Example
REFERENCES other_table(column) declares the foreign key. The referenced column must be the primary key or a unique constraint of the other table. If you omit the column name, the referenced primary key is assumed: REFERENCES authors_example is equivalent to REFERENCES authors_example(id) here.
The ON DELETE and ON UPDATE actions decide what happens when the parent row’s key changes:
NO ACTION(default): error if any child still references the parent. Checked at the end of the statement.RESTRICT: likeNO ACTIONbut checked immediately (cannot be deferred).CASCADE: delete or update the child rows too. Useful when the child rows are owned by the parent.SET NULL: set the child column to NULL (the column must be nullable).SET DEFAULT: set the child column to its default.
Foreign keys are checked at every INSERT/UPDATE/DELETE — they are not free. They are also one of the most important correctness features in your schema; the cost almost always pays for itself in avoided data corruption.
Run the example
To run:
Common pitfalls
- Forgetting an index on the FK column: when you delete a parent row, PostgreSQL must check (and possibly cascade to) child rows; without an index on the FK column this is a sequential scan. Always index the FK column.
- Choosing
CASCADEtoo eagerly: a deletion in production can cascade across millions of rows. Audit yourON DELETE CASCADEs, especially on user-facing tables. - Importing data with cross-table dependencies: load the parent first, then the child. Or use
DEFERRABLE INITIALLY DEFERREDso the FK check happens at commit instead of per-row. - Cycles between FKs require deferrable constraints. PostgreSQL supports them, but design around the need when possible.
DEFERRABLE INITIALLY DEFERRED makes a constraint check at COMMIT rather than statement end. Combined with SET CONSTRAINTS ALL DEFERRED, this lets you make several inter-related changes inside a transaction without ordering them perfectly.
Try to insert a comment with a non-existent author: INSERT INTO comments_example (id, author_id, body) VALUES (10, 99, 'orphan'); and observe the FK violation. Then add a second author and a comment, and use DELETE on the author to see ON DELETE CASCADE in action.
Downloads
SQL file: foreign-keys.sql · All lesson files · Setup guide