PostgreSQL Restrict Violation (SQLSTATE 23001)

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

  1. Deleting a parent row that still has child rows. The most common trigger: DELETE FROM customers WHERE id = 5 fails because orders.customer_id references customers.id with ON DELETE RESTRICT and there are outstanding orders for that customer.

  2. 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 is ON UPDATE RESTRICT, the update is blocked because existing rows in the child table still hold the old value.

  3. 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.

  4. Application code that assumes cascade deletes happen automatically. The foreign key was defined with RESTRICT (or the default NO ACTION) instead of CASCADE, so the child records are never cleaned up before the parent is deleted.

How to Fix restrict_violation

  1. 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;
    
  2. 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;
    
  3. Change RESTRICT to CASCADE if 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.

  4. Use SET NULL or SET DEFAULT when 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;
    
  5. 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 IMMEDIATE or DEFERRABLE INITIALLY DEFERRED. A plain RESTRICT constraint is not deferrable.

  6. Find referencing rows before deleting. To understand the scope of the problem first:

    SELECT *
    FROM   order_items
    WHERE  order_id = 42;
    

    Or query pg_constraint to 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 RESTRICT vs NO ACTION has been stable since PostgreSQL 7.4.
  • Related SQLSTATE codes in Class 23:
    • 23000integrity_constraint_violation (generic)
    • 23502not_null_violation
    • 23503foreign_key_violation (raised by NO ACTION checks and other FK failures)
    • 23505unique_violation
    • 23514check_violation
  • Most PostgreSQL drivers surface this as a subclass of their integrity-error exception. In Python's psycopg2/psycopg3 it is psycopg2.errors.RestrictViolation; in Java (JDBC) it is a PSQLException with SQLState 23001; in ActiveRecord (Rails) it is wrapped as ActiveRecord::InvalidForeignKey.
  • RESTRICT is more strict than NO ACTION for intra-statement ordering. If you hit 23001 but not 23503, the constraint is likely explicitly RESTRICT rather 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.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.