Complete guide to adding FOREIGN KEY constraints across PostgreSQL, MySQL, SQLite, SQL Server, and Oracle. Copy-paste ready scripts with safety warnings.
What is a Foreign Key?
A foreign key (FK) is a column (or set of columns) that establishes a link between data in two tables. It enforces referential integrity — ensuring that every value in the child table's foreign key column exists in the parent table's referenced column.
Without foreign keys, your database can contain orphan rows — data that references records that no longer exist. This leads to inconsistent query results, broken application logic, and data quality issues.
Basic Syntax
The standard SQL pattern for adding a foreign key to an existing table is:
When the parent row is updated or deleted, you can define what happens to the child rows:
ON DELETE CASCADE — Delete child rows automatically when parent is deleted.
ON DELETE SET NULL — Set the foreign key column to NULL when parent is deleted.
ON DELETE RESTRICT — Prevent deletion of the parent if children exist (default behavior).
ON UPDATE CASCADE — Update child foreign key values when parent key changes.
🔗 Add a Foreign Key Constraint
Enforce referential integrity between two tables. Includes naming conventions and referential actions.
-- Basic foreign key
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id);
-- With ON DELETE CASCADE
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_order_id
FOREIGN KEY (order_id) REFERENCES orders(id)
ON DELETE CASCADE;
-- With ON DELETE SET NULL
ALTER TABLE comments
ADD CONSTRAINT fk_comments_author_id
FOREIGN KEY (author_id) REFERENCES users(id)
ON DELETE SET NULL;
-- Large table: add without validation first, then validate
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
NOT VALID;
ALTER TABLE orders
VALIDATE CONSTRAINT fk_orders_user_id;
-- Basic foreign key
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id);
-- With ON DELETE CASCADE
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_order_id
FOREIGN KEY (order_id) REFERENCES orders(id)
ON DELETE CASCADE;
-- With ON DELETE SET NULL
ALTER TABLE comments
ADD CONSTRAINT fk_comments_author_id
FOREIGN KEY (author_id) REFERENCES users(id)
ON DELETE SET NULL;
-- InnoDB only supports CASCADE, SET NULL, RESTRICT, NO ACTION
-- MyISAM does not support foreign keys
-- SQLite supports foreign keys but they must be enabled:
PRAGMA foreign_keys = ON;
-- Add column with inline foreign key (SQLite 3.35.0+)
ALTER TABLE orders
ADD COLUMN user_id INTEGER REFERENCES users(id);
-- For full constraint syntax, recreate the table:
CREATE TABLE orders_new (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
total DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE
);
INSERT INTO orders_new SELECT * FROM orders;
DROP TABLE orders;
ALTER TABLE orders_new RENAME TO orders;
-- Basic foreign key
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id);
-- With ON DELETE CASCADE
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_order_id
FOREIGN KEY (order_id) REFERENCES orders(id)
ON DELETE CASCADE;
-- With ON DELETE SET NULL
ALTER TABLE comments
ADD CONSTRAINT fk_comments_author_id
FOREIGN KEY (author_id) REFERENCES users(id)
ON DELETE SET NULL;
-- Check existing data with NOCHECK (not recommended for production)
ALTER TABLE orders WITH NOCHECK
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id);
-- Basic foreign key
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id);
-- With ON DELETE CASCADE
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_order_id
FOREIGN KEY (order_id) REFERENCES orders(id)
ON DELETE CASCADE;
-- With ON DELETE SET NULL
ALTER TABLE comments
ADD CONSTRAINT fk_comments_author_id
FOREIGN KEY (author_id) REFERENCES users(id)
ON DELETE SET NULL;
-- Oracle also supports ON DELETE SET DEFAULT
⚠️ Safety Note: Adding a foreign key on a large table can lock both tables while existing data is validated. On PostgreSQL, use NOT VALID + VALIDATE CONSTRAINT to split the operation. On SQL Server, consider adding during a maintenance window. Always ensure existing data satisfies the constraint before adding it.
💡 Naming Convention: Use fk_<child_table>_<column_name> for clarity. This makes it easy to identify which table and column a constraint applies to when reading error messages or query plans.
Spot missing constraints before they reach production.Compare Schemas →
Best Practices
Index the foreign key column. Unindexed foreign keys can cause full table scans on DELETE/UPDATE of parent rows. Most databases do not auto-index FK columns.
Name your constraints. Auto-generated names like orders_user_id_fkey are hard to debug. Use explicit names.
Clean data before adding. Run a query to find orphan rows first: SELECT * FROM child WHERE fk_col NOT IN (SELECT id FROM parent).
Use CASCADE carefully. ON DELETE CASCADE can accidentally delete large amounts of data. Prefer SET NULL or RESTRICT for critical relationships.
Document your schema. Tools like SchemaLens compare schemas across environments and flag missing foreign keys before deployment.
Common Errors and Fixes
"foreign key constraint cannot be implemented" — The referenced column is not a PRIMARY KEY or UNIQUE. Fix the parent table first.
"violates foreign key constraint" — Orphan rows exist. Clean them or use NOT VALID + VALIDATE on PostgreSQL.
"cannot add foreign key on partitioned table" — Some databases restrict FKs on partitioned tables. Add the FK to each partition instead.
Related Migration Recipes
⚡ Add an Index
Speed up queries by adding indexes on frequently filtered or joined columns.