PostgreSQL foreign key violation (ERROR 23503) occurs when you try to INSERT, UPDATE, or DELETE a row that violates a foreign key constraint. This means the referenced value does not exist in the parent table, or you're trying to delete a parent row that still has referencing child rows. Fixing requires ensuring referential integrity by inserting parent records first or using appropriate constraint actions like CASCADE.
PostgreSQL Error 23503 (Foreign Key Constraint Violation) occurs when a foreign key constraint is violated. Foreign keys enforce referential integrity by ensuring that values in a child table must match existing values in a parent table. The error typically looks like: "ERROR: insert or update on table "weather" violates foreign key constraint "weather_city_fkey" DETAIL: Key (city)=(Berkeley) is not present in table "cities"." This error can occur in three scenarios: 1. **INSERT into child table**: You're inserting a row with a foreign key value that doesn't exist in the parent table 2. **UPDATE child table**: You're changing a foreign key value to one that doesn't exist in the parent table 3. **DELETE from parent table**: You're trying to delete a parent row that still has child rows referencing it (with ON DELETE NO ACTION) PostgreSQL enforces these constraints automatically at the database level, preventing orphaned records and maintaining data consistency.
The error message includes the constraint name and the table/column involved.
-- Error example:
-- ERROR: insert or update on table "orders" violates foreign key constraint "orders_customer_id_fkey"
-- DETAIL: Key (customer_id)=(999) is not present in table "customers".
-- Query the constraint details:
SELECT constraint_name, table_name, column_name
FROM information_schema.key_column_usage
WHERE table_name = 'orders' AND constraint_name LIKE '%customer_id%';
-- Get the referenced table and column:
SELECT
kcu.table_name,
kcu.column_name,
ccu.table_name AS referenced_table,
ccu.column_name AS referenced_column
FROM information_schema.key_column_usage kcu
JOIN information_schema.constraint_column_usage ccu
ON kcu.constraint_name = ccu.constraint_name
WHERE kcu.table_name = 'orders';Note the constraint name, the child table, and which parent table it references.
Check if the parent value you're trying to reference actually exists in the parent table.
-- Check if the parent ID exists:
SELECT * FROM customers WHERE id = 999;
-- Find all distinct foreign key values in the child table:
SELECT DISTINCT customer_id FROM orders;
-- Find child records referencing non-existent parent IDs:
SELECT DISTINCT o.customer_id
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
WHERE c.id IS NULL;If the parent record doesn't exist, either:
- Insert the missing parent record first
- Change the child row to reference an existing parent
- Delete the invalid child row
When inserting data, always insert parent table records before child table records. If doing a bulk import, reorder the inserts.
-- WRONG ORDER - child before parent:
INSERT INTO orders (id, customer_id, amount) VALUES (1, 999, 100.00);
INSERT INTO customers (id, name) VALUES (999, 'John');
-- Result: ERROR 23503 on first insert
-- CORRECT ORDER - parent before child:
INSERT INTO customers (id, name) VALUES (999, 'John');
INSERT INTO orders (id, customer_id, amount) VALUES (1, 999, 100.00);
-- Success
-- For bulk imports from a CSV/JSON, sort by table dependencies:
-- 1. Load all parent tables first
-- 2. Then load child tables
-- 3. Wrap in a transaction to ensure atomicity:
BEGIN;
INSERT INTO customers (id, name) SELECT id, name FROM staging_customers;
INSERT INTO orders (id, customer_id, amount) SELECT id, customer_id, amount FROM staging_orders;
COMMIT;In concurrent scenarios, wrap related INSERT and DELETE operations in a single transaction to prevent race conditions.
-- Race condition WITHOUT transaction:
-- Session A: DELETE FROM customers WHERE id = 999;
-- Session B: INSERT INTO orders (customer_id) VALUES (999); -- Fails!
-- Solution: Use transactions to ensure atomicity:
BEGIN;
-- Ensure no one is inserting children while we delete the parent
DELETE FROM customers WHERE id = 999;
-- This succeeds only if no pending inserts reference customer 999
COMMIT;
-- Or if adding a child, lock the parent first:
BEGIN;
SELECT * FROM customers WHERE id = 999 FOR UPDATE; -- Lock the row
-- Now other sessions can't delete this customer while we insert an order
INSERT INTO orders (customer_id, amount) VALUES (999, 100.00);
COMMIT;Transactions ensure isolation and prevent race conditions that would violate constraints.
Foreign key constraints define what happens when a parent row is deleted. Understanding these helps prevent violations.
-- View the constraint definition:
SELECT
constraint_name,
table_name,
column_name,
referenced_table_name,
referenced_column_name
FROM information_schema.key_column_usage
WHERE table_name = 'orders';
-- Get the full constraint definition including actions:
SELECT
con.conname as constraint_name,
con.confdeltype as delete_action,
con.confupdtype as update_action
FROM pg_constraint con
WHERE con.conrelid = 'orders'::regclass;Actions:
- NO ACTION (default): Reject the parent deletion if children exist
- RESTRICT: Same as NO ACTION but can't be deferred
- CASCADE: Automatically delete child rows when parent is deleted
- SET NULL: Set the foreign key to NULL when parent is deleted
- SET DEFAULT: Set the foreign key to its default value when parent is deleted
If you're getting delete violations, either delete the children first or use CASCADE when defining the constraint.
If you're deleting a parent and getting a foreign key violation, the parent still has referencing child rows.
-- Option A: Delete children first, then parent:
DELETE FROM orders WHERE customer_id = 999;
DELETE FROM customers WHERE id = 999;
-- Option B: Use CASCADE to auto-delete children:
-- (This requires recreating the constraint)
ALTER TABLE orders DROP CONSTRAINT orders_customer_id_fkey;
ALTER TABLE orders ADD CONSTRAINT orders_customer_id_fkey
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE;
-- Now deleting parent auto-deletes children:
DELETE FROM customers WHERE id = 999;
-- Option C: Find which children reference the parent:
SELECT * FROM orders WHERE customer_id = 999;Warning: CASCADE deletes can have cascading effects. If orders has a foreign key to items, deleting the customer cascades through orders to items. Always test in development first.
For high-concurrency scenarios, use application-level idempotency to handle potential violations gracefully.
-- Ensure parent exists with INSERT...ON CONFLICT:
INSERT INTO customers (id, name)
VALUES (999, 'John')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;
-- Then safely insert the child:
INSERT INTO orders (customer_id, amount)
VALUES (999, 100.00);
-- Or use deferred constraints in a transaction:
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
INSERT INTO orders (customer_id, amount) VALUES (999, 100.00);
INSERT INTO customers (id, name) VALUES (999, 'John');
-- Constraint checking happens at COMMIT
COMMIT;Deferred constraints allow you to insert children before parents in the same transaction, as long as the parent exists by COMMIT time.
Deferred Constraints: By default, foreign key constraints are checked immediately. You can defer checking to the end of a transaction with SET CONSTRAINTS constraint_name DEFERRED or define the constraint as DEFERRABLE INITIALLY DEFERRED. This allows you to insert children before parents in the same transaction, as long as all references are satisfied by COMMIT.
Cascade Options: CASCADE is powerful but dangerous. If you delete a customer with CASCADE, all their orders and order items cascade-delete. In deeply nested foreign keys, this can delete more data than intended. Always audit cascade paths: SELECT constraint_name, table_name FROM information_schema.referential_constraints WHERE referenced_table_name = 'customers';
Partial Unique Constraints with Foreign Keys: Sometimes you want a foreign key that's unique only under certain conditions. PostgreSQL doesn't support conditional foreign keys directly, but you can work around this with triggers or by using application-level validation.
Self-Referencing Foreign Keys: A table can have a foreign key to itself (e.g., employees with a manager_id foreign key to employees.id). Ensure you insert the root record (manager with no parent) first, then subordinates. Deleting is reverse order.
Circular Foreign Keys: If two tables reference each other (A→B and B→A), use transactions and deferred constraints to handle inserts: insert both in the same transaction with SET CONSTRAINTS ALL DEFERRED.
Performance: Foreign key checks add overhead. On very large tables, check constraint definitions are optimized but still require index lookups. Ensure the referenced column has an index (primary keys automatically do).
ERROR: syntax error at end of input
Syntax error at end of input in PostgreSQL
Bind message supplies N parameters but prepared statement requires M
Bind message supplies N parameters but prepared statement requires M in PostgreSQL
Multidimensional arrays must have sub-arrays with matching dimensions
Multidimensional arrays must have sub-arrays with matching dimensions
ERROR: value too long for type character varying
Value too long for type character varying
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL