Why This Error HappensYou tried to wipe a table clean with TRUNCATE, but PostgreSQL stopped you. This happens because another table relies on the data you're trying to delete through a foreign key constraint. Postgres is protecting you from creating 'orphaned' recordsâchild rows that point to parent IDs that no longer exist.
Unlike the DELETE command, which scans every row to check for dependencies, TRUNCATE is a blunt instrument. It bypasses row-level checks and simply deallocates the data pages. Because it doesn't look at individual rows, it can't verify if a specific record is safe to remove. To maintain data integrity, Postgres blocks the operation entirely if any foreign key points to the table.
Solution 1: The CASCADE SledgehammerThe fastest fix is to append the CASCADE keyword. This tells PostgreSQL to automatically truncate any table that references your target table, and any tables referencing those, and so on.
TRUNCATE TABLE users CASCADE;
Be careful: this is recursive. If users is linked to orders, and orders is linked to order_items, all three tables will be wiped instantly. On a large database with 10+ dependent tables, one command can clear millions of rows across your entire schema.
Solution 2: Truncate Multiple Tables at OnceIf you want more control, list every related table in a single command. Postgres allows a TRUNCATE to proceed if all sides of the foreign key relationship are cleared at the exact same moment.
TRUNCATE TABLE users, orders, order_items;
This approach is often safer than CASCADE. It forces you to acknowledge exactly which tables are being emptied, preventing you from accidentally nuking data in a table you forgot was linked.
Solution 3: The 'Replica' Role Trick (Power Users Only)Sometimes you need to clear a parent table while keeping the child tables intactâperhaps during a complex data migration or when re-seeding specific IDs. You can bypass constraint checks by changing the session_replication_role to replica.
BEGIN;
-- Temporarily ignore foreign keys
SET LOCAL session_replication_role = 'replica';
TRUNCATE TABLE users;
-- Return to normal behavior
SET LOCAL session_replication_role = 'origin';
COMMIT;
Use this with caution. If you don't immediately repopulate the users table with the correct IDs, your database will be in an inconsistent state. Your application will likely crash when it tries to join orders to users and finds nothing.
Solution 4: Use DELETE for Smaller DatasetsIf your table is relatively smallâsay, under 100,000 rowsâyou might not need TRUNCATE at all. A standard DELETE statement respects foreign key constraints and triggers.
DELETE FROM users;
While TRUNCATE takes milliseconds regardless of table size, DELETE performance scales with the number of rows. On a table with 5 million rows, DELETE might take 30 seconds and bloat your transaction log, whereas TRUNCATE would be nearly instantaneous.
How to Identify Dependent TablesBefore you run a CASCADE, it helps to know which tables are actually standing in your way. Run this query to find every table that currently references your target:
SELECT
rel_tco.table_name AS referencing_table,
rel_tco.constraint_name
FROM
information_schema.referential_constraints rco
JOIN
information_schema.table_constraints rel_tco
ON rco.constraint_name = rel_tco.constraint_name
JOIN
information_schema.table_constraints pk_tco
ON rco.unique_constraint_name = pk_tco.constraint_name
WHERE
pk_tco.table_name = 'users';

