tutorials/postgresql/truncate-table

Postgres TRUNCATE TABLE with CASCADE and RESTART IDENTITY

There are two ways to empty a PostgreSQL table. Truncate it visually in 1bench from the sidebar, or write a TRUNCATE statement when you need CASCADE, RESTART IDENTITY or several tables at once.

Updated Nov 2, 2026Tested against PostgreSQL 16.13 and 18.6

Quick answer

In 1bench
Right-click the table in the sidebar, choose Truncate Table, type TRUNCATE in the confirmation box and click Truncate Table.
In SQL
sql
TRUNCATE orders, order_items RESTART IDENTITY;

List every table that references the one you are emptying, or add CASCADE, otherwise PostgreSQL refuses with a foreign key error.

1bench
No SQL needed

Truncate a table visually in 1bench

1bench runs a plain TRUNCATE TABLE on one table from the sidebar, behind a confirmation you have to type. It suits tables nothing else points at, such as logs, staging tables and test data.

Prerequisite: download 1bench. Free 7-day trial, no sign-up.

  1. 1

    Find the table

    Connect to your PostgreSQL database in 1bench and expand the schema in the sidebar until you see the table.

  2. 2

    Choose Truncate Table

    Right-click the table and choose Truncate Table. The item is not shown for views or materialized views, and it is greyed out on a read-only connection.

  3. 3

    Type TRUNCATE to confirm

    The Truncate Table dialog says "This will permanently delete all data in the table. The table structure will be preserved." The Truncate Table button stays disabled until you type TRUNCATE in the box.

  4. 4

    Run it

    Click Truncate Table, or press Enter. A toast reads Truncated public.orders (with your table's name), and the open grid and the table list refresh.

Good to know
  • 1bench runs TRUNCATE TABLE "schema"."table" with no CASCADE and no RESTART IDENTITY. Truncating a table that another table references fails, and the toast shows Failed: with PostgreSQL's foreign key error.
  • Sequences keep counting after a truncate from the menu, so the next new row gets the next id, not 1. Run TRUNCATE ... RESTART IDENTITY from the Query panel's SQL editor when you want ids to start over.
  • The menu empties one table at a time. For several tables, CASCADE or a whole schema, use the SQL below in the Query panel.
The manual way

Truncate with SQL

removes every row from a table in one step. It does not scan rows the way does; it gives the table new, empty data files, so a table of millions of rows empties in milliseconds. The keyword is optional.

sql
TRUNCATE TABLE audit_log_v2;

The examples use the seed-demo shop schema: , (references ) and (references ). Every statement and message below came from runs on PostgreSQL 16.13 and 18.6, and both versions behaved the same.

TRUNCATE syntax

The full statement takes a list of tables and two optional switches. Defaults are and .

sql
TRUNCATE [ TABLE ] [ ONLY ] name [ * ] [, ...]
    [ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ];

There is no clause and no . Both are syntax errors, and a missing table fails with "relation "nope" does not exist":

text
TRUNCATE audit_log WHERE id > 5;
ERROR:  syntax error at or near "WHERE"

TRUNCATE TABLE IF EXISTS audit_log;
ERROR:  syntax error at or near "EXISTS"

To remove only some rows, use .

TRUNCATE CASCADE and the foreign key error

A table that another table references cannot be truncated on its own, even when the referencing table is already empty. This is the error, word for word:

text
TRUNCATE TABLE orders;
ERROR:  cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "order_items" references "orders".
HINT:  Truncate table "order_items" at the same time, or use TRUNCATE ... CASCADE.

follows the foreign keys and empties every table that references the target, and the tables that reference those, in the same statement. PostgreSQL prints a notice for each one.

sql
TRUNCATE orders CASCADE;
-- NOTICE:  truncate cascades to table "order_items"

follows the whole chain, so would also empty , , and . Read the notices, or check for its "Referenced by" list first. is the default and only spells out the refusal above.

RESTART IDENTITY vs CONTINUE IDENTITY

By default the sequences behind and columns keep their value, so after emptying a table of 300 orders the next order still gets id 301. Add to start them over at 1.

sql
TRUNCATE orders, order_items RESTART IDENTITY;
INSERT INTO orders (user_id, total_amount) VALUES (1, 5) RETURNING id;
-- id = 1
OptionWhat happens to the id sequence
(default)Keeps counting: the next row after 300 gets 301
Resets every sequence owned by a column of the truncated tables

With , the reset reaches the cascaded tables too: set both and back to 1. A sequence the table does not own, used through , is not reset. Reset it yourself:

sql
SELECT setval('ticket_seq', 1, false);

Truncate multiple tables at once

List the tables separated by commas. They are emptied together in one statement, which is also how you satisfy the foreign key check without : every referencing table is in the list.

sql
TRUNCATE orders, order_items;

Example: clear the shop's order data and start the ids over, leaving untouched.

sql
TRUNCATE order_items, orders RESTART IDENTITY;

TRUNCATE vs DELETE vs DROP

All three get rid of data, but they work differently. Timings are from a 1,000,000 row table on the same machine.

TRUNCATEDELETE FROMDROP TABLE
RemovesAll rowsRows matching WHERE, or allRows and the table itself
WHERE clauseNoYesNo
Speed on 1M rows18 to 45 ms309 to 400 ms6 to 7 ms
Disk spaceFreed at commitKept until VACUUMFreed at commit
Row triggersNot fired; ON TRUNCATE statement triggers fireON DELETE row triggers fireNone
LockACCESS EXCLUSIVEROW EXCLUSIVE, readers keep workingACCESS EXCLUSIVE
SequencesKept, unless RESTART IDENTITYKeptOwned sequences dropped
RollbackYes, inside a transactionYesYes, inside a transaction

Triggers are the difference that bites. A delete trigger, such as one that writes to an audit table, does not run on . Add a statement trigger, with a trigger function of your own, if you need to react to it:

sql
CREATE TRIGGER orders_truncated
AFTER TRUNCATE ON orders
FOR EACH STATEMENT EXECUTE FUNCTION log_truncate();

is also not MVCC-safe. A transaction that took its snapshot before another session's truncate committed sees the table as empty afterwards, not the rows that were there when it started. does not have this problem.

on a referenced table fails the same way, with "cannot drop table users because other objects depend on it". then drops only the foreign key constraint on ("drop cascades to constraint orders_user_id_fkey on table orders"), and the orders keep their rows.

TRUNCATE in a transaction and rollback

is transactional in PostgreSQL, unlike in MySQL. Run it inside and a brings every row back.

sql
BEGIN;
TRUNCATE audit_log_v2;
SELECT count(*) FROM audit_log_v2;  -- 0
ROLLBACK;
SELECT count(*) FROM audit_log_v2;  -- original count again

is rolled back as well: the sequence goes back to its old value. Once the transaction commits, there is no undo.

Locks, and why TRUNCATE hangs

takes an lock on every table it empties, the strongest lock there is. It has to wait for every open transaction that has touched the table, even a plain in an idle transaction, and while it waits, new queries on the table queue up behind it. That is the usual reason a seems to take forever on a tiny table.

Set a so it gives up instead of blocking the table:

sql
SET lock_timeout = '2s';
TRUNCATE audit_log_v2;
-- ERROR:  canceling statement due to lock timeout

Example: from a second session, find what the waiting truncate is blocked by.

sql
SELECT pid, wait_event_type, pg_blocking_pids(pid) AS blocked_by, query
FROM pg_stat_activity
WHERE query ILIKE 'truncate%';

The column lists the process ids holding the table. Finish or cancel those sessions, for example with . Inside a transaction the lock is held until , so keep a truncate near the end of a long transaction, not the start.

TRUNCATE permission

Truncating needs the privilege on the table, or ownership. rights are not enough:

text
ERROR:  permission denied for table audit_log

Grant it per table, or on every existing table in a schema:

sql
GRANT TRUNCATE ON orders, order_items TO app_user;
GRANT TRUNCATE ON ALL TABLES IN SCHEMA public TO app_user;

SELECT has_table_privilege('app_user', 'orders', 'TRUNCATE');

covers tables that exist now. For tables created later, add .

Truncate all tables in a schema or database

There is no command. Build one with every table in the list, so the foreign keys are satisfied without , and run it in a block:

sql
DO $$
DECLARE
  tables text;
BEGIN
  SELECT string_agg(format('%I.%I', schemaname, tablename), ', ')
    INTO tables
    FROM pg_tables
   WHERE schemaname = 'public';
  EXECUTE 'TRUNCATE ' || tables || ' RESTART IDENTITY';
END $$;

For every table in the database, swap the filter for . To keep a table, such as a migrations table, add . If the filter matches no tables, the block fails with "query string argument of EXECUTE is null", which is a safe way to fail.

Example: in psql, runs each generated statement in turn, which is handy when you want to see them.

sql
SELECT format('TRUNCATE %I.%I RESTART IDENTITY CASCADE', schemaname, tablename)
FROM pg_tables
WHERE schemaname = 'public' \gexec

Partitioned tables

Truncating a partitioned table empties all of its partitions. Truncate a single partition by name to clear one range, which is a fast way to drop old data.

sql
TRUNCATE events_2025;
TRUNCATE events;

is refused on the parent: "cannot truncate only a partitioned table", with the hint to drop or truncate the partitions directly.

Work with PostgreSQL in one window

1bench is a desktop GUI for PostgreSQL. Browse tables, filter rows, run SQL and export what you see, with the SQL still there when you want it.

Open PostgreSQL in 1bench

Frequently asked questions

Can you undo a TRUNCATE in Postgres?
Only before it commits. Inside BEGIN, a ROLLBACK brings every row back. Once the transaction commits, the old data files are gone and there is no undo command, so the only way back is a backup: restore a pg_dump of the table, or use point-in-time recovery from a base backup and WAL archive.
Does TRUNCATE reset the sequence in Postgres?
Not by default. CONTINUE IDENTITY is the default, so a serial or identity column carries on from its last value. RESTART IDENTITY resets only sequences the table owns; a shared sequence used through DEFAULT nextval('ticket_seq') is left alone, and you reset it with SELECT setval('ticket_seq', 1, false).
Can TRUNCATE ignore foreign keys?
No. The foreign key check for TRUNCATE is not trigger based, so SET session_replication_role = replica, the usual trick for skipping constraint triggers, still ends in "cannot truncate a table referenced in a foreign key constraint". Truncate the referencing tables in the same statement, use CASCADE, or drop the constraint and add it back afterwards.
Do I need to VACUUM after TRUNCATE?
No. TRUNCATE swaps in new, empty data files, so the space goes back to the operating system at commit. In our test a 1,000,000 row table went from 95 MB to 16 kB right away, while DELETE FROM left it at 95 MB until a VACUUM. Running ANALYZE afterwards still helps the planner if you reload the table.
Can you truncate a view or materialized view in Postgres?
No. Both return an error such as ERROR: "v_audit" is not a table. Empty the base tables behind a view instead. A materialized view can be emptied with REFRESH MATERIALIZED VIEW name WITH NO DATA, after which reading it fails with "has not been populated" until you refresh it again.