Postgresql insert trigger to concatenate - sql

I want to create a trigger to concatenate my column with event "before insert", but the query didn't work and i got an error:
SQL error:
ERROR: syntax error at or near "SET" LINE 4: SET new.fullname =
CONCAT(new.first_name, '', new.mid_name, ...
In statement:
CREATE TRIGGER insert_trigger
BEFORE INSERT ON t_employees
FOR EACH ROW
SET new.fullname = CONCAT(new.first_name, '', new.mid_name, '', new.last_name);

Here's a working solution:
CREATE TEMPORARY TABLE t_employees (
first_name TEXT,
mid_name TEXT,
last_name TEXT,
fullname TEXT
);
CREATE OR REPLACE FUNCTION set_fullname()
RETURNS TRIGGER AS $$
BEGIN
NEW.fullname = NEW.first_name || ' ' || NEW.mid_name || ' ' || NEW.last_name;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER set_fullname_trigger
BEFORE INSERT OR UPDATE ON t_employees
FOR EACH ROW
EXECUTE PROCEDURE set_fullname();
SET client_min_messages TO 'debug';
INSERT INTO t_employees VALUES ('fname1', 'mname1', 'lname1');
SELECT * FROM t_employees;
UPDATE t_employees SET first_name = 'updated-first-name';
SELECT * FROM t_employees;

Well, i finally did it. According your suggestions and postgresql docs i've made the trigger like what i want. Here is the syntax:
create the function:
CREATE FUNCTION insert_funct() RETURN TRIGGER AS
$$
BEGIN
SELECT new.fullname := CONCAT(new.first_name, '', new.mid_name, '', new.last_name);
END;
$$
LANGUAGE plpgsql;
then create the trigger:
CREATE TRIGGER insert_trigger
BEFORE INSERT ON t_employees
FOR EACH ROW
EXECUTE PROCEDURE insert_funct();

Related

Error: Relation "abc.emp_audit" does not exist. when i add schema name in all objects. it was working fine without schema

CREATE TABLE abc.emp
(
empname TEXT NOT NULL,
salary INTEGER
);
CREATE TABLE abc.emp_audit
(
operation CHAR(1) NOT NULL,
stamp TIMESTAMP NOT NULL,
userid TEXT NOT NULL,
empname TEXT NOT NULL,
salary INTEGER
);
CREATE FUNCTION abc.audit()
returns TRIGGER AS $$ ...
EXECUTE format
( 'INSERT INTO %I SELECT ''I'', current_timestamp, %L, ($1::%I.%I).*',
tg_argv[0], CURRENT_USER, tg_table_schema, tg_table_name ) using new;
... $$;
CREATE TRIGGER emp_audit_trig after
INSERT
OR
UPDATE
OR
DELETE
ON abc.emp FOR each row
EXECUTE FUNCTION
abc.audit('abc.emp_audit');
I got error : Relation "abc.emp_audit" does not exist . How to correct this issue when schema added in tables ad audit tables, functions, and triggers arguments.
You'd have to use two parameters, one for the schema and one for the table. In the trigger function, use
EXECUTE
format(
'INSERT INTO %I.%I SELECT ''I'', current_timestamp, %L, ($1::%I.%I).*',
TG_ARGV[0], TG_ARGV[1],
current_user,
TG_TABLE SCHEMA, TG_TABLE NAME
) USING NEW;
Then define the trigger as
CREATE TRIGGER ... EXECUTE FUNCTION abc.audit('abc', 'emp_audit');
An alternative to the one proposed by #Laurenz Albe is to stick to a single parameter and then split it before creating the INSERT statement, e.g
CREATE FUNCTION abc.audit() RETURNS TRIGGER AS $$
DECLARE
tbname TEXT := (string_to_array(tg_argv[0],'.'))[1];
schname TEXT := (string_to_array(tg_argv[0],'.'))[2];
BEGIN
-- rest of your code ...
EXECUTE format('INSERT INTO %I.%I SELECT ''I'', current_timestamp, %L, ($1::%I.%I).*',
tbname,schname,CURRENT_USER,tg_table_schema,tg_table_name ) USING NEW;
-- rest of your code ...
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit_trig AFTER INSERT OR UPDATE OR DELETE
ON abc.emp FOR EACH ROW EXECUTE FUNCTION abc.audit('abc.emp_audit');
Demo: db<>fiddle

How can a Postgres trigger `AFTER UPDATE FOR EACH STATEMENT` correlate its `OLD TABLE` and `NEW TABLE`?

After upgrading from Postgres 9.5 to 11, I am trying to replace the following FOR EACH ROW trigger with a FOR EACH STATEMENT trigger which I hope would be more efficient for my specific use-case:
CREATE OR REPLACE FUNCTION audit_update_operations()
RETURNS TRIGGER
AS
$$
DECLARE
audit_user UUID;
data_before TEXT;
data_after TEXT;
BEGIN
audit_user := coalesce(current_setting('audit.AUDIT_USER', TRUE), '77777777-0000-7777-0000-777777777777')::UUID;
data_before := ROW (old.*);
data_after := ROW (new.*);
INSERT INTO dml_audit_log
(changed_at,
user_id,
operation,
table_name,
data_after,
data_before)
VALUES (now(),
audit_user,
'U',
tg_table_name::TEXT,
data_after,
data_before);
RETURN new;
END ;
$$
LANGUAGE plpgsql;
I thought this was pretty easy:
CREATE OR REPLACE FUNCTION audit_update_operations()
RETURNS TRIGGER
AS
$$
DECLARE
user_id UUID;
BEGIN
user_id := coalesce(current_setting('audit.AUDIT_USER', TRUE), '77777777-0000-7777-0000-777777777777')::UUID;
INSERT INTO dml_audit_log
SELECT now() AS changed_at,
user_id,
'U' AS operation,
tg_table_name::TEXT AS table_name,
ROW (new_table.*) AS data_after,
ROW (old_table.*) AS data_before
FROM new_table;
RETURN NULL;
END ;
$$
LANGUAGE plpgsql;
which is called generically for every table via the following trigger:
EXECUTE ('
CREATE TRIGGER trigger_audit_update
AFTER UPDATE ON ' || tablename || '
REFERENCING
OLD TABLE AS old_table
NEW TABLE AS new_table
FOR EACH STATEMENT
EXECUTE PROCEDURE audit_update_operations()');
but an UPDATE statement triggering this function results in the following error:
[42P01] ERROR: missing FROM-clause entry for table "old_table" Where: PL/pgSQL function shared.audit_update_operations() line 7 at SQL statement
The question I am struggling with is:
How can I correlate the old and new rows without making any assumption about the table being changed?
The tables I am triggering on may or may not have a primary key. Even if they do, I would not know its name or column(s) in the trigger function.
Are the rows in OLD TABLE and NEW TABLE guaranteed to be in the same order? I cannot rely on undocumented implementation details that may change.

Accessing current row values in a trigger

I created a function will insert a value into a json for each row inserted in a table.
But when I execute the insert, it says me that the column doesn't exist.
Here is the function :
CREATE OR REPLACE FUNCTION insert_id_function()
RETURNS trigger AS'
BEGIN
NEW.previewcontent = previewcontent || ''{"id":1}'';
RETURN NEW;
END;'
LANGUAGE plpgsql VOLATILE;
Here is the trigger :
CREATE TRIGGER insert_id_trigger
BEFORE INSERT
ON "Telnet"
FOR EACH ROW
EXECUTE PROCEDURE insert_id_function();
Here is the error :
`ERROR: column "previewcontent" does not exist LINE 1: SELECT previewcontent || '{"id":1}'`
Here is my table definition :
This is a column of the NEW record:
NEW.previewcontent = NEW.previewcontent || ''{"id":1}'';
If the type of previewcontent is jsonb use jsonb_build_object() to add the current value of id to the jsonb column:
CREATE OR REPLACE FUNCTION insert_id_function()
RETURNS trigger AS $$
BEGIN
NEW.previewcontent = NEW.previewcontent || jsonb_build_object('id', NEW.id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql VOLATILE;

Can we restrict a trigger on table to only perform inserts based on function that is being called by it? (PostgreSQL)

Is there any way in PostgreSQL where we can restrict the trigger to only do the insert's based on the function that is being called? Say, I created a trigger on fact_activity but whenever I perform any insert's the trigger should execute the function to perform inserts on the table defined in the function only.
--Here's my trigger that executes the function.
CREATE TRIGGER test_insert
BEFORE INSERT ON fact_activity
FOR EACH ROW
EXECUTE PROCEDURE insert_function();
--Insert function
CREATE OR REPLACE FUNCTION insert_function()
RETURNS trigger AS
$BODY$
DECLARE
l_part_date TEXT;
l_table_name TEXT;
BEGIN
l_part_date := to_char(TO_date(NEW.activity_date_key ::text,'YYYYMMDD'),'YYYY_MM');
l_table_name := TG_TABLE_NAME ||'_'|| l_part_date;
EXECUTE 'INSERT INTO ' || l_table_name || ' SELECT $1.*' USING NEW;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql;
Note: activity_date_key is of integer format (20150512). Hence the conversion.
The above script results in a record being inserted in both fact_activity & fact_activity_2015_05. But I only need to insert a record in fact_activity_2015_05.
--DDL for the tables:
CREATE TABLE fact_activity
(
gc_activity_key bigint NOT NULL DEFAULT nextval('fact_gc_activity_key_seq'::regclass),
user_key bigint,
user_category_key integer,
user_geographic_region_key integer,
activity_date_key integer
);
CREATE TABLE fact_activity_2015_05 ( )
INHERITS (fact_activity);
-- DML:
INSERT INTO fact_gc_activity(user_key, user_category_key, user_geographic_region_key,activity_date_key)
VALUES (6, 1, 1,20150515);
Thanks in advance.
The trigger should return NULL
CREATE OR REPLACE FUNCTION insert_function()
RETURNS trigger AS
$BODY$
DECLARE
l_part_date TEXT;
l_table_name TEXT;
BEGIN
l_part_date := to_char(TO_date(NEW.activity_date_key ::text,'YYYYMMDD'),'YYYY_MM');
l_table_name := TG_TABLE_NAME ||'_'|| l_part_date;
EXECUTE 'INSERT INTO ' || l_table_name || ' SELECT $1.*' USING NEW;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql;

Trigger on every update or insert

I want to create trigger that fires every time any column is changed - whether it is freshly updated or new insert. I created something like this:
CREATE TRIGGER textsearch
BEFORE INSERT OR UPDATE
ON table
FOR EACH ROW
EXECUTE PROCEDURE trigger();
and body of trigger() function is:
BEGIN
NEW.ts := (
SELECT COALESCE(a::text,'') || ' ' ||
COALESCE(b::int,'') || ' ' ||
COALESCE(c::text,'') || ' ' ||
COALESCE(d::int, '') || ' ' ||
COALESCE(e::text,'')
FROM table
WHERE table.id = new.id);
RETURN NEW;
END
I hope it is clear what I want to do.
My problem is that trigger fires only on update, not on insert. I guess that this isn't working because I have BEFORE INSERT OR UPDATE, but if I change it to AFTER INSERT OR UPDATE then it doesn't work neither for INSERT nor UPDATE.
you need to use the NEW record directly:
BEGIN
NEW.ts := concat_ws(' ', NEW.a::text, NEW.b::TEXT, NEW.c::TEXT);
RETURN NEW;
END;
The advantage of concat_ws over || is that concat_ws will treat NULL values differently. The result of 'foo'||NULL will yield NULL which is most probably not what you want. concat_ws will use an empty string NULL values.
It doesn't work because you're calling SELECT inside the function.
When it runs BEFORE INSERT then there isn't a row to select, is there?
Actually, BEFORE UPDATE you'll see the "old" version of the row anyway, won't it?
Just directly use the fields: NEW.a etc rather than selecting.
As an edit - here is an example showing what the trigger function can see. It's exaclty as you'd expect in a BEFORE trigger.
BEGIN;
CREATE TABLE tt (i int, t text, PRIMARY KEY (i));
CREATE FUNCTION trigfn() RETURNS TRIGGER AS $$
DECLARE
sv text;
BEGIN
SELECT t INTO sv FROM tt WHERE i = NEW.i;
RAISE NOTICE 'new value = %, selected value = %', NEW.t, sv;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigtest BEFORE INSERT OR UPDATE ON tt FOR EACH ROW EXECUTE PROCEDURE trigfn();
INSERT INTO tt VALUES (1,'a1');
UPDATE tt SET t = 'a2' WHERE i = 1;
ROLLBACK;
in your COALESCE statements when you cast b::int i had to change your coalesce to use integer place holder instead. As mentioned by a_horse_with_no_name this can end up with null values but you can see how to make your specific code example run. I included the "RAISE NOTICE" lines for debug purposes only.
Based on your provided information the following works for me:
CREATE TABLE my_table (id SERIAL NOT NULL,a TEXT,b INTEGER,c TEXT,d INTEGER,e TEXT);
CREATE OR REPLACE FUNCTION my_triggered_procedure() RETURNS trigger AS $$
BEGIN
if(TG_OP = 'UPDATE' OR TG_OP = 'INSERT') THEN
NEW.ts := (SELECT COALESCE(a::text,'') || ' ' ||
COALESCE(b::int,0) || ' ' ||
COALESCE(c::text,'') || ' ' ||
COALESCE(d::int, 0) || ' ' ||
COALESCE(e::text,'')
FROM my_table
WHERE id=NEW.id);
RAISE NOTICE 'INSERT OR UPDATE with new ts = %',NEW.ts;
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
OLD.ts := ' ';
RAISE NOTICE 'DELETED old id: %',OLD.id;
RETURN OLD;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER text_search
AFTER INSERT OR UPDATE OR DELETE
ON my_table
FOR EACH ROW
EXECUTE PROCEDURE my_triggered_procedure();
INSERT INTO my_table (a,b,c,d,e) VALUES('text11',12,'text21',3,'text4');
>NOTICE: INSERT OR UPDATE with new ts = text11 12 text21 3 text4
>INSERT 0 1
DELETE FROM my_table WHERE id=24;
>NOTICE: DELETED ID = 24
>DELETE 1
PostgreSQL::Trigger Procedures