When you attempt to delete or update a row that is still referenced by a foreign key defined with RESTRICT, PostgreSQL raises:
ERROR: update or delete on table "orders" violates foreign key constraint "order_items_order_id_fkey" on table "order_items"
DETAIL: Key (id)=(42) is still referenced from table "order_items".
SQLSTATE: 23001
The SQLSTATE code is 23001 and the condition name is restrict_violation. It belongs to the Class 23 — Integrity Constraint Violation family of errors.
What This Error Means
RESTRICT is one of four referential actions you can specify in a REFERENCES / FOREIGN KEY clause (RESTRICT, NO ACTION, CASCADE, SET NULL, SET DEFAULT). When a foreign key is declared with ON DELETE RESTRICT or ON UPDATE RESTRICT, PostgreSQL actively prevents any operation on the referenced (parent) table row if at least one matching row exists in the referencing (child) table.
The practical difference between RESTRICT and the default NO ACTION is subtle but important: RESTRICT is checked immediately within the statement, whereas NO ACTION defers the check to the end of the statement (and can be further deferred to end-of-transaction if the constraint is declared DEFERRABLE). In practice, for most single-row operations the two behave identically. The distinction matters for operations that affect multiple rows — with NO ACTION a single statement can delete a parent and its children in the right order; RESTRICT will abort as soon as it sees any referencing row, even within the same statement.
After the error is raised, the current statement is rolled back. If you are inside an explicit transaction, the transaction is left in an aborted state (ERROR status) and must be rolled back with ROLLBACK before any further work can proceed.
Common Causes
Deleting a parent row that still has child rows. The most common trigger:
DELETE FROM customers WHERE id = 5fails becauseorders.customer_idreferencescustomers.idwithON DELETE RESTRICTand there are outstanding orders for that customer.Updating a primary/unique key that child rows point to. If you change the value of a referenced column (
UPDATE products SET id = 99 WHERE id = 1) and the foreign key isON UPDATE RESTRICT, the update is blocked because existing rows in the child table still hold the old value.Bulk import or data-migration scripts that process parent rows before child rows. Truncating or deleting parent tables in the wrong order during a migration without temporarily disabling constraints will hit this error.
Application code that assumes cascade deletes happen automatically. The foreign key was defined with
RESTRICT(or the defaultNO ACTION) instead ofCASCADE, so the child records are never cleaned up before the parent is deleted.
How to Fix restrict_violation
Delete or reassign child rows first. The cleanest fix is to remove or update the dependent rows before touching the parent:
-- Remove the children, then the parent DELETE FROM order_items WHERE order_id = 42; DELETE FROM orders WHERE id = 42;Wrap the operations in a transaction. When deleting multiple related rows, do it inside a single transaction so partial work does not become visible:
BEGIN; DELETE FROM order_items WHERE order_id = 42; DELETE FROM orders WHERE id = 42; COMMIT;Change
RESTRICTtoCASCADEif automatic propagation is appropriate. If child rows should be removed whenever the parent is removed, redefine the constraint:ALTER TABLE order_items DROP CONSTRAINT order_items_order_id_fkey, ADD CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE;Use this only when deleting child rows automatically is genuinely the right business rule.
Use
SET NULLorSET DEFAULTwhen children should be kept but the link removed:ALTER TABLE order_items DROP CONSTRAINT order_items_order_id_fkey, ADD CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE SET NULL;Temporarily defer constraints during bulk migrations. If the foreign key is
DEFERRABLE, you can defer the check to end-of-transaction:BEGIN; SET CONSTRAINTS order_items_order_id_fkey DEFERRED; -- ... bulk delete / insert operations in any order ... COMMIT;Note: this only works if the constraint was created with
DEFERRABLE INITIALLY IMMEDIATEorDEFERRABLE INITIALLY DEFERRED. A plainRESTRICTconstraint is not deferrable.Find referencing rows before deleting. To understand the scope of the problem first:
SELECT * FROM order_items WHERE order_id = 42;Or query
pg_constraintto discover all foreign keys that reference a given table:SELECT conrelid::regclass AS child_table, conname AS constraint_name, pg_get_constraintdef(oid) AS definition FROM pg_constraint WHERE confrelid = 'orders'::regclass AND contype = 'f';
Additional Information
- SQLSTATE 23001 has been present in PostgreSQL since foreign key support was introduced (PostgreSQL 7.1). The behavior of
RESTRICTvsNO ACTIONhas been stable since PostgreSQL 7.4. - Related SQLSTATE codes in Class 23:
- 23000 —
integrity_constraint_violation(generic) - 23502 —
not_null_violation - 23503 —
foreign_key_violation(raised byNO ACTIONchecks and other FK failures) - 23505 —
unique_violation - 23514 —
check_violation
- 23000 —
- Most PostgreSQL drivers surface this as a subclass of their integrity-error exception. In Python's
psycopg2/psycopg3it ispsycopg2.errors.RestrictViolation; in Java (JDBC) it is aPSQLExceptionwith SQLState23001; in ActiveRecord (Rails) it is wrapped asActiveRecord::InvalidForeignKey. RESTRICTis more strict thanNO ACTIONfor intra-statement ordering. If you hit23001but not23503, the constraint is likely explicitlyRESTRICTrather than the default.
Frequently Asked Questions
What is the difference between RESTRICT and NO ACTION in PostgreSQL?
Both prevent deletion or update of a referenced row while dependent rows exist. The difference is timing: RESTRICT blocks the operation immediately (within the current SQL statement), while NO ACTION waits until the end of the statement — or the end of the transaction if the constraint is DEFERRABLE. For simple single-statement operations they behave the same; the difference appears when a single statement deletes both a parent and its children.
Why does my ORM raise InvalidForeignKey instead of RestrictViolation?
Most ORMs (ActiveRecord, SQLAlchemy, Hibernate) map all Class 23 FK errors — including both 23001 and 23503 — to a single "foreign key violation" exception type for convenience. Inspect the underlying database error message or the pgcode attribute of the exception to distinguish 23001 from 23503.
Can I make a RESTRICT foreign key deferrable?
No. PostgreSQL does not allow RESTRICT constraints to be deferred — RESTRICT is always checked immediately. If you need deferrable behavior, define the constraint as NO ACTION DEFERRABLE INITIALLY IMMEDIATE (or INITIALLY DEFERRED). SET CONSTRAINTS ... DEFERRED only works on constraints defined as DEFERRABLE.
How do I find all tables that could block a delete from a given table?
Query pg_constraint for all foreign keys that reference the target table:
SELECT conrelid::regclass AS referencing_table,
conname AS fk_name,
pg_get_constraintdef(oid) AS fk_definition
FROM pg_constraint
WHERE confrelid = 'your_table'::regclass
AND contype = 'f'
ORDER BY referencing_table;
This lists every table whose rows could block a delete or key-update on your_table.