问题
I have a field of type character varying but I get an error when trying to save a data that contains decimal. I want to save that data without problem.
This is my trigger:
CREATE TRIGGER "public.usuarios_trigger_process_audit"
BEFORE INSERT OR UPDATE OR DELETE
ON usuarios
FOR EACH ROW
EXECUTE PROCEDURE process_audit();
This the PROCEDURE:
DECLARE
newtable text;
col information_schema.columns %ROWTYPE;
txtquery text;
line_old TEXT;
tmpquery text;
i int;
columns_old text[];
BEGIN
IF ( TG_TABLE_SCHEMA = 'public' ) THEN
SELECT TG_TABLE_NAME || '_actividad' INTO newtable; /* select TG_RELNAME || '_actividad' into newtable; */
ELSE
SELECT TG_TABLE_SCHEMA || '_' || TG_TABLE_NAME || '_actividad' INTO newtable; /* select TG_RELNAME || '_actividad' into newtable; */
END IF;
PERFORM creartablaactividad( TG_TABLE_SCHEMA, TG_TABLE_NAME );
IF ( TG_OP = 'DELETE' ) THEN
line_old := TRIM( substr(OLD::text,2,(select length(OLD::text)-2)) );
columns_old := STRING_TO_ARRAY( line_old, ',' );
i := 0;
tmpquery := '''' || array_to_string(columns_old, ''',''') || '''';
tmpquery := replace(tmpquery,','''',',',NULL,');
/* SELECT 'INSERT INTO actividad.' || newtable ||' SELECT user, inet_client_addr(), now (), ''D'',' || replace(tmpquery, ',''''',',NULL') into txtquery; */
SELECT 'INSERT INTO actividad.' || newtable ||' SELECT user, now (), ''D'',' || replace(tmpquery, ',''''',',NULL') into txtquery;
EXECUTE txtquery;
RETURN OLD;
ELSIF ( TG_OP = 'UPDATE' ) THEN
line_old := TRIM( substr(OLD::text,2,(select length(OLD::text)-2)) );
columns_old := STRING_TO_ARRAY( line_old, ',' );
i := 0;
tmpquery := '''' || array_to_string(columns_old, ''',''') || '''';
tmpquery := replace(tmpquery,','''',',',NULL,');
tmpquery := replace(tmpquery,','''',',',NULL,');
/* SELECT 'INSERT INTO actividad.' || newtable ||' SELECT user, inet_client_addr(), now (), ''ANT'',' || replace(tmpquery, ',''''',',NULL') into txtquery; */
SELECT 'INSERT INTO actividad.' || newtable ||' SELECT user, now (), ''ANT'',' || replace(tmpquery, ',''''',',NULL') into txtquery;
EXECUTE txtquery;
line_old := TRIM( substr(NEW::text,2,(select length(NEW::text)-2)) );
columns_old := STRING_TO_ARRAY( line_old, ',' );
i := 0;
tmpquery := '''' || array_to_string(columns_old, ''',''') || '''';
tmpquery := replace(tmpquery,','''',',',NULL,');
/* SELECT 'INSERT INTO actividad.' || newtable ||' SELECT user, inet_client_addr(), now (), ''U'',' || replace(tmpquery, ',''''',',NULL') into txtquery; */
SELECT 'INSERT INTO actividad.' || newtable ||' SELECT user, now (), ''U'',' || replace(tmpquery, ',''''',',NULL') into txtquery;
EXECUTE txtquery;
RETURN NEW;
ELSIF ( TG_OP = 'INSERT' ) THEN
line_old := TRIM( substr(NEW::text,2,(select length(NEW::text)-2)) );
columns_old := STRING_TO_ARRAY( line_old, ',' );
i := 0;
tmpquery := '''' || array_to_string(columns_old, ''',''') || '''';
tmpquery := replace(tmpquery,','''',',',NULL,');
/* SELECT 'INSERT INTO actividad.' || newtable ||' SELECT user, inet_client_addr(), now (), ''I'',' || replace(tmpquery, ',''''',',NULL') into txtquery; */
SELECT 'INSERT INTO actividad.' || newtable ||' SELECT user, now (), ''I'',' || replace(tmpquery, ',''''',',NULL') into txtquery;
EXECUTE txtquery;
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
My table usuarios:

The error:

回答1:
You can use format()
to make creating a dynamic SQL query much easier as it will automatically deal with identifiers and literals correctly. One thing that people usually overlook is that you can expand a single record expression to all its columns using (...).*
- this also works for NEW
and OLD
record variables in a trigger, e.g. select (new).*
You can also pass variables to a dynamic SQL with the using keyword of the execute
statement. There is no need to convert the record back and forth between a record and a text representation.
Using that possibility your trigger function can be simplified to:
DECLARE
l_sql text;
BEGIN
IF TG_TABLE_SCHEMA = 'public' THEN
newtable := TG_TABLE_NAME || '_actividad';
ELSE
newtable := TG_TABLE_SCHEMA || '_' || TG_TABLE_NAME || '_actividad';
END IF;
PERFORM creartablaactividad(TG_TABLE_SCHEMA, TG_TABLE_NAME);
l_sql := 'INSERT INTO actividad.%I SELECT current_user, current_timestamp, %L, ($1).*';
IF TG_OP = 'DELETE' THEN
execute format(l_sql, newtable, 'D') using OLD;
RETURN OLD;
ELSE
-- covers UPDATE and INSERT
execute format(l_sql, newtable, 'U') using NEW;
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
Using placeholders like %I
and %L
also makes it possible to define the actual SQL only once and re-use it. Those "parameters" are replaced by the format()
function (which preserves the $1
)
Note the use of ($1).*
inside the SQL string. That will make the execute
statement expand the record parameter $1
to all its columns. The record itself is passed "natively" with the USING
keyword.
The use of INSERT
without a target column list (insert into some_table ...
instead of insert into some_table (col1, col2, ...) ...
) is a pretty fragile thing to do. If the source and the target don't match the insert can fail quite easily. .
If you don't run massive reporting on the audit tables (where having explicit column names would be much more efficient) you might want to think of a more generic audit trigger using a JSON
or HSTORE
column to store the whole record. There are several ready-made audit triggers available:
- http://okbob.blogspot.de/2015/01/most-simply-implementation-of-history.html
- https://github.com/wingspan/wingspan-auditing
- https://www.cybertec-postgresql.com/en/tracking-changes-in-postgresql/
- https://wiki.postgresql.org/wiki/Audit_trigger_91plus
来源:https://stackoverflow.com/questions/50322065/how-to-save-a-data-with-comma-in-character-varying-that-passes-through-a-trigger