2

I am trying to dynamically create audit tables for (almost) all tables in the database. I can generate the appropriate SQL dynamically, like so:

SELECT                          
    'CREATE TABLE IF NOT EXISTS '
    || tab_name || '_audit(timestamp TIMESTAMPTZ NOT NULL, entity JSONB NOT NULL);'
FROM (
    SELECT                                                                     
        quote_ident(table_schema) || '.' || quote_ident(table_name) as tab_name
    FROM                         
        information_schema.tables
    WHERE                                                       
        table_schema NOT IN ('pg_catalog', 'information_schema')
        AND table_schema NOT LIKE 'pg_toast%'
) tablist;

This gives me a series of rows of the form:

CREATE TABLE IF NOT EXISTS public.table1_audit(timestamp TIMESTAMPTZ NOT NULL, entity JSONB NOT NULL);
CREATE TABLE IF NOT EXISTS public.table2_audit(timestamp TIMESTAMPTZ NOT NULL, entity JSONB NOT NULL);

Etc. What I am struggling with is actually executing those dynamically generated queries. From searching EXECUTE seemed to be the required function, but I could not get it to work without either producing a syntax error, or just doing nothing. I would appreciate a point in the right direction.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Alex
  • 2,270
  • 3
  • 33
  • 65

1 Answers1

3

You can use dynamic SQL with EXECUTE in a loop in a DO statement:

DO
$$
DECLARE
   _sql text;
BEGIN
   FOR _sql IN
      SELECT format('CREATE TABLE IF NOT EXISTS %I.%I(timestamp timestamptz NOT NULL, entity jsonb NOT NULL)'
                  , schemaname
                  , tablename || '_audit')
      FROM   pg_catalog.pg_tables  -- only tables and partitioned tables, no toast tables
      WHERE  schemaname NOT IN ('pg_catalog', 'information_schema')
   LOOP
      RAISE NOTICE '%', _sql;
      -- EXECUTE _sql;
   END LOOP;
END
$$;

I threw in a RAISE NOTICE to inspect the payload first.
Uncomment the EXECUTE line to actually execute.

You had quote_ident(table_name) before appending '_audit'. That would fail for all table names that actually require double-quoting. You'd have to do quote_ident(table_name || 'audit'). But I use format() instead. More convenient. See:

I also use pg_catalog.pg_tables instead of information_schema.tables. Faster, and exactly what you need. See:

And I scrapped the subquery - not needed.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228