You cannot parameterize identifiers in plain SQL. You would need dynamic SQL for that, i.e.: build a query string dynamically, then execute it, which can be cumbersome and error-prone. There is a simpler alternative for your case, especially since you need to cast all columns to a common data type anyway, the natural choice being text
.
It's also not possible to loop through fields of a record or row type. That's why you are currently reading metadata from the information schema to walk through columns, but you can have that simpler, too.
Use the extension hstore
and its tools. Install it once per database:
Convert a row to hstore
with the obvious function hstore()
:
hstore(OLD)
It returns a type hstore
, which is a key/value store with all values converted to text. Now you have various options. Probably simplest: unnest with
each()` and join:
CREATE FUNCTION my_function()
RETURNS TRIGGER AS
$func$
DECLARE
_col text;
_old_val text;
_new_val text;
BEGIN
FOR _col, _old_val, _new_val
SELECT key , o.value , n.value
FROM each(hstore(OLD)) o -- (key, value)
JOIN each(hstore(NEW)) n USING (key)
WHERE o.value IS DISTINCT FROM n.value -- only changed columns
LOOP
-- do something
END LOOP;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
If all you want is to insert changed columns into an audit table, make it a single SQL command and no looping at all:
CREATE FUNCTION my_function()
RETURNS TRIGGER AS
$func$
BEGIN
INSERT INTO audit_table
(tbl_schema , tbl , col , oldval , newval)
SELECT TG_TABLE_SCHEMA, TG_TABLE_NAME, key , o.value, n.value
FROM each(hstore(OLD)) o -- (key, value)
JOIN each(hstore(NEW)) n USING (key)
WHERE o.value IS DISTINCT FROM n.value;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Or you can convert hstore
to a 2-dimensional key/value array with %# hstore(OLD)
and loop through it like instructed here:
For more sophisticated needs there are audit solutions, like a_horse commented.