This sends a JSON array of columns that actually changed, with their new value:
CREATE OR REPLACE FUNCTION notify_col()
RETURNS trigger AS
, (SELECT json_agg(n)::text
FROM json_each_text(to_json(OLD)) o
JOIN json_each_text(to_json(NEW)) n USING (key)
WHERE n.value IS DISTINCT FROM o.value));
$func$ LANGUAGE plpgsql;
CREATE TRIGGER tbl_upd_aft
BEFRE UPDATE ON tbl
FOR EACH ROW
WHEN (OLD IS DISTINCT FROM NEW) -- optional
EXECUTE procedure notify_col();
If works by converting
NEW to JSON and expanding each to a set of key/value pairs. Then join on the key and only keep columns (now rows) that actually changed. Finally aggregate into a JSON array.
WHEN condition to the trigger. Updates that do not actually change anything don't fire the trigger. (So the trigger is guaranteed to process actual changes.)
If there can be more triggers consider an
AFTER trigger to only report what's actually changed. And reconsider the
WHEN condition if triggers might add changes - which would circumvent this condition.
Aside: consider this note in the manual:
row_to_json have the same behavior as
to_json except for offering a pretty-printing option.