Explicit cursors are rarely needed in PL/pgSQL. Use the simpler and faster implicit cursor of a FOR
loop:
Since table names are not unique per database, you have to schema-qualify table names to be sure. Also, I limit the function to the default schema 'public'. Adapt to your needs, but be sure to exclude the system schemas pg_*
and information_schema
.
Be very careful with these functions. They nuke your database. I added a child safety device. Comment the RAISE NOTICE
line and uncomment EXECUTE
to prime the bomb ...
CREATE OR REPLACE FUNCTION f_truncate_tables(_username text)
RETURNS void
LANGUAGE plpgsql AS
$func$
DECLARE
_tbl text;
_sch text;
BEGIN
FOR _sch, _tbl IN
SELECT schemaname, tablename
FROM pg_tables
WHERE tableowner = _username
AND schemaname = 'public'
LOOP
-- dangerous, test before you execute!
RAISE NOTICE '%', -- once confident, comment this line ...
-- EXECUTE -- ... and uncomment this one
format('TRUNCATE TABLE %I.%I CASCADE', _sch, _tbl);
END LOOP;
END
$func$;
format()
requires Postgres 9.1 or later. In older versions concatenate the query string like this:
'TRUNCATE TABLE ' || quote_ident(_sch) || '.' || quote_ident(_tbl) || ' CASCADE';
Single command, no loop
Since we can TRUNCATE
multiple tables at once we don't need any cursor or loop at all:
Aggregate all table names and execute a single statement. Simpler, faster:
CREATE OR REPLACE FUNCTION f_truncate_tables(_username text)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
-- dangerous, test before you execute!
RAISE NOTICE '%', -- once confident, comment this line ...
-- EXECUTE -- ... and uncomment this one
(SELECT 'TRUNCATE TABLE '
|| string_agg(format('%I.%I', schemaname, tablename), ', ')
|| ' CASCADE'
FROM pg_tables
WHERE tableowner = _username
AND schemaname = 'public'
);
END
$func$;
Call:
SELECT truncate_tables('postgres');
Refined query
You don't even need a function. In Postgres 9.0+ you can execute dynamic commands in a DO
statement. And in Postgres 9.5+ the syntax can be even simpler:
DO
$do$
BEGIN
-- dangerous, test before you execute!
RAISE NOTICE '%', -- once confident, comment this line ...
-- EXECUTE -- ... and uncomment this one
(SELECT 'TRUNCATE TABLE ' || string_agg(oid::regclass::text, ', ') || ' CASCADE'
FROM pg_class
WHERE relkind = 'r' -- only tables
AND relnamespace = 'public'::regnamespace
);
END
$do$;
About the difference between pg_class
, pg_tables
and information_schema.tables
:
About regclass
and quoted table names:
For repeated use
Create a "template" database (let's name it my_template
) with your vanilla structure and all empty tables. Then go through a DROP
/ CREATE DATABASE
cycle:
DROP DATABASE mydb;
CREATE DATABASE mydb TEMPLATE my_template;
This is extremely fast, because Postgres copies the whole structure on the file level. No concurrency issues or other overhead slowing you down.
If concurrent connections keep you from dropping the DB, consider: