I have tables that have almost the same columns. The first table has the current values and the other table has the old values and an additional column to store the date data is inserted. The second table is named as the first table with the prefix _bit. Example:
CREATE TABLE x (
id serial PRIMARY KEY,
f1 integer NOT NULL,
f2 character varying(10) NOT NULL,
cuando timestamp without time zone NOT NULL DEFAULT now()
);
CREATE TABLE _bitx (
id integer,
f1 integer,
f2 character varying(10),
cuando timestamp without time zone,
cuandobit timestamp without time zone DEFAULT now()
);
I have a trigger function that copies the OLD
values of a table to another:
CREATE FUNCTION bitacoratabla() RETURNS trigger AS $$
BEGIN
INSERT INTO _bitx VALUES((OLD).*);
RETURN NEW;
END;
$$ LANGUAGE plpgsql
The trigger:
CREATE TRIGGER trigbitx
BEFORE UPDATE
ON public.x
FOR EACH ROW
EXECUTE PROCEDURE public.bitacoratabla();
All this works fine but I must do this for multiple tables that follow the same rules.
I modified the function in order to create the name of the table where I will insert data but I am having problems with that and with (OLD).*
CREATE FUNCTION bitacoratabla() RETURNS trigger AS $$
DECLARE
tabla text := '_bit' || quote_ident(TG_TABLE_NAME);
BEGIN
EXECUTE 'INSERT INTO ' || tabla || ' VALUES(' || (OLD).* || ')';
RETURN NEW;
END;
$$ LANGUAGE plpgsql
I am new to PL/pgSQL and thought I did not have to do more. Any ideas?
UPDATE
As all you suggest it is better to use format()
and pass values with USING
keyword. Thank you. This is what I did:
CREATE OR REPLACE FUNCTION bitacoratabla() RETURNS TRIGGER
AS $$
BEGIN
EXECUTE format('INSERT INTO %I SELECT $1.*', '_bit' || TG_TABLE_NAME) USING OLD;
RETURN NEW;
END
$$ LANGUAGE PLPGSQL;
Best Answer
Answers so far still suffer from one or the other shortcoming.
Use the
USING
clause ofEXECUTE
to pass values. Do not concatenate the text representation of values into the SQL string. Expensive and error-prone.Properly escape all identifiers when used in dynamic SQL to defend against sneaky errors with double quoted identifiers and possible SQL injection. Even if you think you do not need it. Do it on principal.
But this would be an error waiting to happen:
'_bit' || quote_ident(TG_TABLE_NAME)
For a table name
"MyTable"
this would produce_bit"MyTable"
- syntactical nonsense. It must be:Or use
format('... %I ...', '_bit' || TG_TABLE_NAME)
The only correct solution is what you added to your question yourself:
Detailed explanation:
But why does it work with an additional column?
This happens to work as long as the additional column in the target table comes last because, quoting the manual on
INSERT
:So you better be sure the additional column(s) come last in the table definition of the target table, and all other columns are listed in matching order, now and in all future - or you must add an explicit target column list (which would prevent a generic function to begin with).