How to log DML statements executed by a pl/pgsql function?

Question asked

There is a built-in way to log all statements inside plpgsql functions: auto-explain

LOAD 'auto_explain';
SET auto_explain.log_min_duration = 1; -- exclude very fast trivial queries
SET auto_explain.log_nested_statements = ON; -- statements inside functions

Details under this closely related question:
Postgres query plan of a UDF invocation written in pgpsql

Potentially generates a lot of log output. I would only use it for debugging, not in production.
If you just need the one statement logged, go with @dezso's advice.

Code audit

Consider this rewritten function:

CREATE OR REPLACE FUNCTION fnct_clear_temp_fields()
  RETURNS void AS
$func$
DECLARE
   rec record;
   qry text;
BEGIN
   FOR rec IN
      SELECT quote_ident(c.relname) AS tbl, quote_ident(a.attname) AS col
      FROM   pg_namespace n
      JOIN   pg_class     c ON c.relnamespace = n.oid
      JOIN   pg_attribute a ON a.attrelid = c.oid
      WHERE  n.nspname = 'public'
      AND    c.relkind = 'r'
      AND    a.attname LIKE 'temp_%'  -- LIKE is faster than ~
      AND    a.attnum > 0
      AND    NOT a.attisdropped
      ORDER  BY 1,2
   LOOP
      RAISE NOTICE 'Table: %, Column: %', rec.tbl, rec.col;
      qry := format('UPDATE %1$s SET %2$s = NULL WHERE %2$s IS NOT NULL', rec.tbl, rec.col);
      RAISE LOG 'Query: %', qry;
      EXECUTE qry;
   END LOOP;
END
$func$  LANGUAGE plpgsql;

Major points

  • You must sanitize all identifiers that you build into dynamic SQL, else it can fail with non-standard names that require double-quoting. Worse, you are open to SQL injection.
    Demonstrating quote_ident(), since you are using the sanitized identifiers multiple times. There are more options with regclass or format():
    Table name as a PostgreSQL function parameter

  • I prefer to base such queries on the system catalog instead of the painfully slow views of the information schema. That's a matter of requirements and taste, though. Demonstrating the equivalent, which is ~ 10 times faster (not regarding UPDATE commands). More:
    How to check if a table exists in a given schema

  • LIKE is generally faster than the more powerful regular expression matching (~). If LIKE can do the job, use it.

  • Some other minor simplifications.

  • Related answer with more details:
    Update record of a cursor where the table name is a parameter


So, my suggestion as an actual answer:

If you need it only in this function, you can do a RAISE LOG '%', your_statement;, or in your actual code:

...
DECLARE
    exec_str text;
...
    --Set to NULL the contents of the current 'temp_' column
    exec_str := 'UPDATE '||dataset_1_row.table_name||
                'SET '||dataset_1_row.column_name||'=NULL 
                 WHERE '||dataset_1_row.column_name||' IS NOT NULL';
    RAISE LOG 'Query executed: %', exec_str;
    EXECUTE exec_str;
...

Also, I find the

FOR dataset_1_row IN SELECT ... 
LOOP 
END LOOP;

construct much smoother.