I want to create a deleted logs and insert data from the OLD row column. The problem is the column is not same for each table, some tables only has transaction_date
and other table only has created_at
. So I want to check if transaction_date
just use it, otherwise use created_at
column. I tried using coalesce
function but still return:
ERROR: record "old" has no field "transaction_date" CONTEXT: SQL statement "INSERT INTO "public"."delete_logs" ("table", "date") VALUES (TG_TABLE_NAME, coalesce(OLD.transaction_date, coalesce(OLD.created_at, now())))" PL/pgSQL function delete_table() line 2 at SQL statement
here is my function:
CREATE OR REPLACE FUNCTION delete_table() RETURNS trigger AS
$$BEGIN
INSERT INTO "public"."deleted_logs" ("table", "created_at") VALUES (TG_TABLE_NAME, coalesce(OLD.transaction_date, coalesce(OLD.created_at, now())));
RETURN OLD;
END;$$ LANGUAGE plpgsql;
CREATE TRIGGER "testDelete" AFTER DELETE ON "exampletable" FOR EACH ROW EXECUTE PROCEDURE "delete_table"();
Actually, I wanted to create a function for each table, but I think it will be difficult to update the function in the future, so I need to create a single function for all tables.
CodePudding user response:
If you only have two alternative columns transaction_date
and created_at
, then you can try to manage the ERROR with an exception handling :
CREATE OR REPLACE FUNCTION delete_table() RETURNS trigger AS
$$BEGIN
INSERT INTO "public"."deleted_logs" ("table", "created_at") VALUES (TG_TABLE_NAME, OLD.transaction_date);
RETURN OLD;
EXCEPTION WHEN OTHERS THEN
INSERT INTO "public"."deleted_logs" ("table", "created_at") VALUES (TG_TABLE_NAME, OLD.created_at);
RETURN OLD ;
END;$$ LANGUAGE plpgsql;
CodePudding user response:
So I want to check if
transaction_date
just use it, otherwise usecreated_at
column.
You can avoid the exception you saw by converting the row to json:
CREATE OR REPLACE FUNCTION log_ts_after_delete()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
INSERT INTO public.deleted_logs
(table_name , created_at) -- "table" is a reserved word
VALUES (TG_TABLE_NAME, COALESCE(to_json(OLD)->>'transaction_date', to_json(OLD)->>'created_at')::timestamptz);
RETURN NULL; -- not used in AFTER trugger
END
$func$;
My answer assumes that transaction_date
is defined NOT NULL
. Else, the expression defaults to created_at
. Probably not what you want.
JSON is not as strict as SQL. A reference to a non-existing JSON key results in NULL instead of the exception for the reference to a non-existing table column. So COALESCE
just works.
Related:
If the row is wide, it might be cheaper to convert to JSON only once and save it to a variable, or do it in a subquery or CTE.
Related:
If tables never switch the columns in question, passing a parameter in the trigger definition would be much cheaper. You find out (at trigger creation time) once with:
SELECT attname
FROM pg_attribute
WHERE attrelid = 'public.exampletable'::regclass
AND attname IN ('transaction_date', 'created_at')
AND NOT attisdropped
ORDER BY attname DESC
This returns 'transaction_date' if such a column exists in the table, else 'created_at', else NULL (no row). Related:
It's still cheapest to have a separate trigger function for each type of trigger. Just two functions instead of one. If the trigger is fired often I would do that.
Avoid exception handling if you can. The manual:
Tip
A block containing an
EXCEPTION
clause is significantly more expensive to enter and exit than a block without one. Therefore, don't useEXCEPTION
without need.