Referential Integrity, Cascades and Constraint Management
What happens to child rows when a parent is deleted or updated, how to choose between RESTRICT, CASCADE and SET NULL, and how to add, drop and disable constraints.
- SQL Basics
- DDL
- DML
- SELECT
- WHERE
- Functions
- NULL and Logic
- Aggregate Functions
- GROUP BY
- JOIN
- Subqueries
- Set Operations
- CTEs
- Constraints
- Keys
- Relationships
- Database Design
- Normalisation
- Views
- Window Functions
- Advanced SQL
- Procedures and Functions
- Triggers
- Temporary Tables
- Transactions
- Isolation and Locking
- Indexes
- Query Performance
- SQL Security
- SQL Dialects
- Practical SQL
Concept
Referential integrity is the guarantee that every foreign key value points at a row that exists. The database enforces it on insert, on update and on delete - and the ON DELETE and ON UPDATE clauses decide how.
The referential actions
| Action | On deleting or updating the parent | Use when |
|---|---|---|
RESTRICT / NO ACTION | Refuse while children exist | Default. Safe for almost everything. |
CASCADE | Delete or update the children too | Children have no meaning without the parent - order lines, note tags |
SET NULL | Set the child's foreign key to NULL | The link is optional - an employee keeps existing without a department |
SET DEFAULT | Set the child to the column default | Rare; unsupported in MySQL/InnoDB |
Syntax
CONSTRAINT fk_order_items_order FOREIGN KEY (order_id)
REFERENCES orders(id)
ON DELETE CASCADE
ON UPDATE RESTRICTExample
-- Line items cannot exist without their order
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT NOT NULL,
PRIMARY KEY (order_id, product_id),
CONSTRAINT fk_oi_order FOREIGN KEY (order_id)
REFERENCES orders(id) ON DELETE CASCADE
);
-- An employee survives their department being closed
CREATE TABLE employees_v2 (
id INT PRIMARY KEY,
name VARCHAR(60) NOT NULL,
dept_id INT NULL,
CONSTRAINT fk_emp_dept_v2 FOREIGN KEY (dept_id)
REFERENCES departments(id) ON DELETE SET NULL
);Explanation
The choice is a business decision, not a technical one. Ask: if the parent disappears, does the child still mean anything?
- An order line without an order is meaningless -
CASCADE. - An employee without a department is a real employee -
SET NULL. - A customer with orders should not be deletable at all -
RESTRICT.
SET NULL requires the child column to be nullable; combining it with NOT NULL is a contradiction the database rejects at creation time.
Cascades are recursive. Deleting a customer withON DELETE CASCADEto orders, which cascades to order items, removes all three levels from one statement. That is powerful and easy to underestimate - which is why this notes application's own schema uses cascades for note tags and links, but a softdeleted_atflag for notes themselves.
Managing constraints
-- Add a constraint to an existing table (the data must already satisfy it)
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers(id);
-- Drop it - MySQL and MariaDB
ALTER TABLE orders DROP FOREIGN KEY fk_orders_customer;
-- Drop it - PostgreSQL, SQL Server, Oracle
ALTER TABLE orders DROP CONSTRAINT fk_orders_customer;
-- Temporarily suspend checking during a bulk load (MySQL)
SET FOREIGN_KEY_CHECKS = 0;
-- ... load data ...
SET FOREIGN_KEY_CHECKS = 1;Disabling checks is a loading tool, not an operating mode. MySQL does not revalidate existing rows when you turn checks back on, so any orphan created while they were off stays there silently. Validate afterwards:
-- Find orphans that should not exist
SELECT o.id, o.customer_id
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id
WHERE o.customer_id IS NOT NULL AND c.id IS NULL;Important rules
RESTRICTandNO ACTIONbehave the same in MySQL; in PostgreSQLNO ACTIONcan be deferred to the end of the transaction andRESTRICTcannot.ON DELETE SET NULLrequires a nullable child column.- Cascading deletes fire recursively and do not fire
DELETEtriggers in every engine - check before relying on audit triggers. - Adding a foreign key to a populated table fails if any orphan exists.
- Constraint names must be unique per schema in MySQL, and per table in some other products.
Common mistakes
- Using
CASCADEeverywhere and losing a large subtree from one careless delete. - Turning off
FOREIGN_KEY_CHECKSfor a bulk load and never validating afterwards. - Combining
ON DELETE SET NULLwith aNOT NULLcolumn. - Assuming a cascade fires application level logic. It does not - it happens inside the database.
- Dropping constraints to make a migration pass, then forgetting to restore them.
Best practices
- Default to
RESTRICT. ChooseCASCADEonly for rows that are genuinely part of the parent. - Prefer soft deletes for business records humans may need back.
- After any bulk load with checks disabled, run an orphan query per foreign key.
- Keep constraint definitions in migration files so the rules are reviewable in version control.
Practice
- Choose an action for each:
order_items.order_id,employees.manager_id,orders.customer_id. Justify each choice in one line. - Write the orphan detection query for
assignments.project_id. - What happens to
assignmentswhen a project is deleted, given the sample schema?