PostgreSQLMySQLSQL ServerSQLite

Foreign key constraint fails

You tried to insert or update a row that points at a parent row which doesn't exist, or delete a parent row something still references. Plus the one thing every SQLite user eventually gets caught by — it isn't enforced unless you turn it on.

  • MySQL: Cannot add or update a child row: a foreign key constraint fails (...)
  • PostgreSQL: ERROR: insert or update on table "orders" violates foreign key constraint "orders_customer_id_fkey" DETAIL: Key (customer_id)=(99) is not present in table "customers".
  • SQL Server: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_orders_customers". The conflict occurred in database "...", table "dbo.customers", column 'customer_id'.
  • SQLite: FOREIGN KEY constraint failed

A scratch schema with the constraint actually declared, since the shared seed tables on this site don't have one:

SQL playground
Loading editor…
⌘/Ctrl + Enter

The two directions this happens

Inserting or updating a child row that points at a parent that doesn't exist — the case above. The fix is almost always upstream: whatever created customer_id = 99 on the order either has a bug, or is running before the customer row it depends on has been committed (a common one in batch loads and multi-step application logic — insert parents first).

Deleting a parent row something still references — same constraint, the other direction. scratch_orders has a real row pointing at customer 1 from the block above, so deleting that customer is blocked too:

SQL playground
Loading editor…
⌘/Ctrl + Enter

If the delete is intentional, you generally have three honest options, declared on the constraint itself rather than worked around in every query that might delete a parent:

  • ON DELETE CASCADE — delete the children too.
  • ON DELETE SET NULL — orphan the children (nullify the foreign key column, which has to be nullable).
  • ON DELETE RESTRICT (the default behavior above) — refuse, which is usually right unless you've deliberately decided otherwise.

The SQLite-specific trap: it's off by default

This is the one genuinely SQLite-specific gotcha on this page, and it surprises people from the opposite direction of every other error here — instead of erroring too eagerly, SQLite doesn't enforce foreign keys at all unless you turn it on for the connection — explicitly turning it back off here so this block is honest about the default, regardless of what ran earlier on this page:

SQL playground
Loading editor…
⌘/Ctrl + Enter

That inserted an order pointing at a customer that doesn't exist, with no error — the exact FOREIGN KEY REFERENCES clause that would stop this in every other engine did nothing, because SQLite requires PRAGMA foreign_keys = ON to be set on every connection, every time — it's not a one-time schema setting, and most application frameworks and drivers don't turn it on for you. If you're relying on SQLite's foreign keys to catch bad data during development, verify the pragma is actually being set before trusting a clean run.

Add ON DELETE CASCADE to the foreign key above and rerun the delete to see the difference.Open the playground →