Issue with dynamic sql - postgresql

Hi every one I have multiple spatial tables That I want to control, so that I created a table where I will store the name of the operation applied on my layers tables(insert,update or delete), operation time and the team who did it, number of spatial tables created.
My script table
CREATE TABLE public.monitoring_table
(
operation character(1) COLLATE pg_catalog."default",
operat_ime timestamp without time zone,
userid text COLLATE pg_catalog."default",
dc_team text COLLATE pg_catalog."default",
number_pts_created integer,
id integer NOT NULL DEFAULT nextval('monitoring_table_id_seq'::regclass),
CONSTRAINT monitoring_table_pkey PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.monitoring_table
OWNER to postgres;
after that I stored all the teams that I have on my table :
insert into monitoring_table (dc_team) values ('abdoulhassan');
insert into monitoring_table (dc_team) values ('abdoulei');
insert into monitoring_table (dc_team) values ('danis');
insert into monitoring_table (dc_team) values ('david');
insert into monitoring_table (dc_team) values ('joseph');
To calculate the number of spatial tables created, I executed this function :
My counting function :
DROP FUNCTION get_dc_team_counting();
CREATE OR REPLACE FUNCTION get_dc_team_counting()
RETURNS bigint AS
$func$
DECLARE
dc_team text;
_tbl_pattern text;
_schema text = 'sige';
_tb_name information_schema.tables.table_name%TYPE;
_tc bigint;
BEGIN
FOR _tb_name IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = _schema
AND table_name ~ _tbl_pattern
LOOP
EXECUTE format('SELECT count(*) FROM %I.%I where id= 583', _schema, _tb_name)
INTO _tc;
return _tc;
END LOOP;
END
$func$ LANGUAGE plpgsql
To get the team I executed this function :
CREATE OR REPLACE FUNCTION get_team()
RETURNS text AS -- or whatever you want to return
$func$
DECLARE
dc_team text;
_tbl_pattern text;
_schema text = 'public';
_tb_name information_schema.tables.table_name%TYPE; -- currently varchar
_tc text;
BEGIN
FOR _tb_name IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = _schema
AND table_name ~ _tbl_pattern -- see below!
LOOP
EXECUTE format('SELECT dc_team FROM %I.%I where id = 26', _schema, _tb_name)
INTO _tc;
return _tc;
END LOOP;
END
$func$ LANGUAGE plpgsql;
In my where clause I want to get dynamically the value of the ID. I don't want to give it manually in the function. I don't see how to do it.
Now I created a trigger function to be able to update my table if a row was inserted, updated or deleted. I did it this way :
CREATE OR REPLACE FUNCTION process_monitoring() RETURNS TRIGGER AS $monitoring$
BEGIN
IF (TG_OP = 'DELETE') THEN
update monitoring_table set operation = 'D', operat_ime = now(), userid = user ,dc_team = OLD.dc_team, number_pts_created = get_dc_team_counting() where dc_team = get_team();
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
update monitoring_table set operation = 'U',operat_ime = now(),userid = user , dc_team = NEW.dc_team, number_pts_created = get_dc_team_counting() where dc_team = get_team();
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
update monitoring_table set operation = 'I', operat_ime = now(), userid = user, dc_team = NEW.dc_team, number_pts_created = get_dc_team_counting() where dc_team = get_team();
RETURN NEW;
END IF;
RETURN NULL;
END;
$monitoring$ LANGUAGE plpgsql;
CREATE TRIGGER monitoring
AFTER INSERT OR UPDATE OR DELETE ON sige.valve
FOR EACH ROW EXECUTE PROCEDURE process_monitoring();
In my previous functions I used known values of ids in the where clauses, but when I try to insert or update a value on the concerned table I get this error :
the control attempted it's end without return
CONTEXT: fonction PL/pgsql get_team()
instruction SQL « update monitoring_table set operation = 'U',operat_ime = now(),userid = user , dc_team = NEW.dc_team, number_pts_created = get_dc_team_counting() where dc_team = get_team() »
fonction PL/pgsql process_monitoring(), ligne 10 à instruction SQL
If you have any idea about the origin of the error and how to get dynamically a value of a field and put it in the where clause tell me please.
I'm a newbie and I'm struggling, any assistance would be warmly appreciated.
I'm using PostgreSQL.

Related

Function trigger with count in postgresql

I have created some schemas (dsfv, dsfn, etc.) and insert some tables inside each schema, including the following tables :
poste_hta_bt (parent table with attribute "code_pt" as unique key);
transfo_hta_bt (having also "code_pt" attribute as foreign key that referenced poste_hta_bt).
I have also created a function trigger that count the total number of entities from "transfo_hta_bt" and report it in "nb_transf" attribute of "poste_hta_bt".
My code is as follows :
SET SESSION AUTHORIZATION dsfv;
SET search_path TO dsfv, public;
CREATE TABLE IF NOT EXISTS poste_hta_bt
(
id_pt serial NOT NULL,
code_pt varchar(30) NULL UNIQUE,
etc.
);
CREATE TABLE IF NOT EXISTS transfo_hta_bt
(
id_tra serial NOT NULL,
code_tra varchar(30) NULL,
code_pt varchar(30) NULL,
etc.
);
ALTER TABLE transfo_hta_bt ADD CONSTRAINT "FK_transfo_hta_bt_poste_hta_bt"
FOREIGN KEY (code_pt) REFERENCES poste_hta_bt (code_pt) ON DELETE No Action ON UPDATE No Action;
CREATE OR REPLACE FUNCTION recap_transf() RETURNS TRIGGER
language plpgsql AS
$$
DECLARE
som_transf smallint;
som_transf1 smallint;
BEGIN
IF (TG_OP = 'INSERT') THEN
SELECT COUNT(*) INTO som_transf FROM dsfv.transfo_hta_bt WHERE code_pt = NEW.code_pt;
UPDATE dsfv.poste_hta_bt SET nb_transf = som_transf WHERE dsfv.poste_hta_bt.code_pt = NEW.code_pt;
RETURN NULL;
ELSIF (TG_OP = 'DELETE') THEN
SELECT COUNT(*) INTO som_transf FROM dsfv.transfo_hta_bt WHERE code_pt = OLD.code_pt;
UPDATE dsfv.poste_hta_bt SET nb_transf = som_transf WHERE dsfv.poste_hta_bt.code_pt = OLD.code_pt;
RETURN NULL;
ELSIF (TG_OP = 'UPDATE') THEN
SELECT COUNT(*) INTO som_transf FROM dsfv.transfo_hta_bt WHERE code_pt = NEW.code_pt;
SELECT COUNT(*) INTO som_transf1 FROM dsfv.transfo_hta_bt WHERE code_pt = OLD.code_pt;
UPDATE dsfv.poste_hta_bt SET nb_transf = som_transf WHERE dsfv.poste_hta_bt.code_pt = NEW.code_pt;
UPDATE dsfv.poste_hta_bt SET nb_transf = som_transf1 WHERE dsfv.poste_hta_bt.code_pt = OLD.code_pt;
RETURN NULL;
ELSE
RAISE WARNING 'Other action occurred: %, at %', TG_OP, now();
RETURN NULL;
END IF;
END;
$$
;
DROP TRIGGER IF EXISTS recap_tr ON dsfv.transfo_hta_bt;
CREATE TRIGGER recap_tr AFTER INSERT OR UPDATE OR DELETE ON dsfv.transfo_hta_bt FOR EACH ROW EXECUTE PROCEDURE recap_transf();
This code runs correctly but I didn't understand why the following:
In the function trigger, I noticed that I have to specify the schema of each table, despite that I adjusted search_path to dsfv from the beginning.
Also when I replace dsfv.transfo_hta_bt with TG_TABLE_NAME in the function trigger, the lattest variable is unrecognized.
Thank you in advance for your help.
You need to repeat SET search_path TO dsfv, public; at the beginning of the function body for it to apply to its inner context.
TG_TABLE_NAME is a text variable not unlike TG_OP that you are already using, so you can't directly plug it in a query as a table name. You'd have to construct your queries as text and use dynamic SQL EXECUTE to run them.
CREATE OR REPLACE FUNCTION recap_transf() RETURNS TRIGGER
language plpgsql AS
$$
DECLARE
som_transf smallint;
som_transf1 smallint;
BEGIN
--search path update is rendered somewhat useless by dynamic SQL used later
execute 'SET search_path TO '||TG_TABLE_SCHEMA||', public;';
IF (TG_OP = 'INSERT') THEN
execute format('SELECT COUNT(*) FROM %1$I.%2$I WHERE code_pt = $1',
TG_TABLE_SCHEMA,
TG_TABLE_NAME)
into som_transf
using NEW.code_pt;
execute format('UPDATE %1$I.poste_hta_bt SET nb_transf = $1 WHERE %1$I.poste_hta_bt.code_pt = $2',
TG_TABLE_SCHEMA)
using som_transf,
NEW.code_pt;
RETURN NULL;
ELSIF (TG_OP = 'DELETE') THEN
execute format('SELECT COUNT(*) FROM %1$I.%2$I WHERE code_pt = $1',
TG_TABLE_SCHEMA,
TG_TABLE_NAME)
into som_transf
using OLD.code_pt;
execute format('UPDATE %1$I.poste_hta_bt SET nb_transf = $1 WHERE %1$I.poste_hta_bt.code_pt = $2',
TG_TABLE_SCHEMA)
using som_transf,
OLD.code_pt;
RETURN NULL;
ELSIF (TG_OP = 'UPDATE') THEN
execute format('SELECT COUNT(*) FROM %1$I.%2$I WHERE code_pt = $1',
TG_TABLE_SCHEMA,
TG_TABLE_NAME)
into som_transf
using NEW.code_pt;
execute format('SELECT COUNT(*) FROM %1$I.%2$I WHERE code_pt = $1',
TG_TABLE_SCHEMA,
TG_TABLE_NAME)
into som_transf1
using OLD.code_pt;
execute format('UPDATE %1$I.poste_hta_bt SET nb_transf = $1 WHERE %1$I.poste_hta_bt.code_pt = $2',
TG_TABLE_SCHEMA)
using som_transf,
NEW.code_pt;
execute format('UPDATE %1$I.poste_hta_bt SET nb_transf = $1 WHERE %1$I.poste_hta_bt.code_pt = $2',
TG_TABLE_SCHEMA)
using som_transf1,
OLD.code_pt;
RETURN NULL;
ELSE
RAISE WARNING 'Other action occurred: %, at %', TG_OP, now();
RETURN NULL;
END IF;
END;
$$
;
PostgreSQL stores a function as string, which is interpreted when the function is executed. The search_path that applies is the one in effect when the function is called, not the one in effect when it is created. (SQL functions created with the new syntax from v14 are safe from that, since they are parsed when the function is created.)
To avoid these problems, you should fix the search_path for all functions:
ALTER FUNCTION recap_transf() SET search_path = dsfv;
Note that it is not safe to add a schema where untrusted users can create objects, so only add public if you revoked the CREATE privilege for PUBLIC that it has in versions before v15.

How insert count of a table in another table attribute by trigger postgresql

I have created two tables "post" and "node" and I want to assign the sum of the entities of the "node" table in the attribute "nb_noeud" of the "post" table by trigger. But, the code below does not work and I think I missed something.
My code is as follows:
CREATE TABLE noeud
(
id_noeud serial NOT NULL,
code_noeud varchar(10) NULL,
type_noeud t_noeud NULL,
phase t_phase NULL
x_32632 bigint NULL,
y_32632 bigint NULL,
geom_noeud geometry(point) NULL,
obs text NULL
)
;
CREATE TABLE poste
(
id_pt serial NOT NULL,
code_pt varchar(8) NULL,
nom_pt varchar(50) NULL,
nb_noeud smallint NULL,
geom_pt geometry(polygon) NULL,
surf_pt numeric(15,2) NULL,
obs text NULL
)
;
CREATE OR REPLACE FUNCTION recap_noeud() RETURNS TRIGGER
language plpgsql AS
$$
DECLARE
som_noeud smallint;
BEGIN
IF (TG_OP = 'INSERT') THEN
SELECT COUNT(*) INTO som_noeud FROM noeud;
UPDATE poste set NEW.nb_noeud = som_noeud;
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
SELECT COUNT(*) INTO som_noeud FROM noeud;
UPDATE poste set NEW.nb_noeud = som_noeud;
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
RETURN NULL;
ELSE
RAISE WARNING 'Other action occurred: %, at %', TG_OP, now();
RETURN NULL;
END IF;
END;
$$
;
DROP TRIGGER IF EXISTS trig_recap_noeud ON noeud;
CREATE TRIGGER trig_recap_noeud AFTER INSERT OR UPDATE OR DELETE ON noeud FOR EACH ROW EXECUTE PROCEDURE recap_noeud();
Replace DELETE and INSERT clauses with
IF TG_OP = 'INSERT' OR TG_OP = 'DELETE' THEN
SELECT COUNT(*) INTO som_noeud FROM noeud;
UPDATE poste set nb_noeud = som_noeud;
RETURN NULL;
Best regards, Bjarni

trigger in postgresql old value and new value

how to write trigger in postgresql which maintain old value, new value and table name.
I have 5 tables and each tables is different data structure i want to maintain audit details in single table with old value new value and table name.old value and new value contain multiple columns in json format with column name and value.
example
audit_details
----------------------------------------------------------------
date_time|table_name|old_data|new_data|user|primary_key_of_table
----------------------------------------------------------------
I wrote sample trigger function for you. You can make additional changes yourself.
CREATE TABLE test.log_table_data (
id serial not null,
schema_name varchar(100) NOT NULL,
table_name varchar(100) NOT NULL,
action_date timestamp NOT NULL DEFAULT now(),
action_type varchar(10) NOT NULL,
table_id int4 NOT NULL,
old_data jsonb NULL,
new_data jsonb NULL,
CONSTRAINT log_table_data_pk PRIMARY KEY (id)
);
create or replace function test.register_as_log()
returns trigger
language plpgsql
security definer
as $function$
declare
v_old_data json;
v_new_data json;
shemaname varchar;
tablename varchar;
begin
shemaname = tg_table_schema;
tablename = tg_table_name;
/* json_strip_nulls - removes null values */
if (tg_op = 'UPDATE') then
v_old_data = (select json_strip_nulls(row_to_json(old.*)));
v_new_data = (select json_strip_nulls(row_to_json(new.*)));
insert into test.log_table_data (schema_name, table_name, action_type, table_id, old_data, new_data)
values (shemaname, tablename, 'update', old.id, v_old_data, v_new_data);
return new;
end if;
if (tg_op = 'DELETE') then
v_old_data = (select json_strip_nulls(row_to_json(old.*)));
insert into test.log_table_data (schema_name, table_name, action_type, table_id, old_data, new_data)
values (shemaname, tablename, 'delete', old.id, v_old_data, null);
return old;
end if;
end;
$function$
;

Postgres update trigger

I've problem with a trigger function in postgresql.
Here my simple code.
CREATE TABLE specie
(specie_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_comune TEXT UNIQUE,
nome_scientifico TEXT UNIQUE);
CREATE TABLE rilevatore
(rilevatore_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_cognome TEXT);
CREATE TABLE evento_investimento
(evento_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data DATE,
ora TIME WITHOUT TIME ZONE,
rilevatore_id INT REFERENCES rilevatore (rilevatore_id),
specie_id INT REFERENCES specie(specie_id));
CREATE VIEW inserimento_dati_vista AS
SELECT row_number() OVER ()::integer AS gid,
evento_investimento.ora,
evento_investimento.data,
rilevatore.nome_cognome,
specie.nome_comune,
specie.nome_scientifico
FROM evento_investimento
JOIN specie ON evento_investimento.specie_id = specie.specie_id
JOIN rilevatore ON evento_investimento.rilevatore_id = rilevatore.rilevatore_id;
CREATE OR REPLACE FUNCTION inserimento_dati_fun_2() RETURNS trigger AS $$
BEGIN
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome)
VALUES (NEW.nome_cognome);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
INSERT INTO evento_investimento (data, ora, rilevatore_id, specie_id)
VALUES (NEW.data,NEW.ora,
(SELECT rilevatore_id FROM rilevatore WHERE rilevatore.nome_cognome = NEW.nome_cognome),
(SELECT specie_id FROM specie WHERE specie.nome_comune = NEW.nome_comune));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger inserimento_dati_fun_trg
instead of insert on inserimento_dati_vista for each row EXECUTE procedure inserimento_dati_fun_2();
Now, I want to add a function that allow to update all the tables by using the view inserimento_dati_vista.
I've tried with a simple code to update only the data column
CREATE OR REPLACE FUNCTION update_dati_fun_2() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF old.data is distinct from new.data then
UPDATE evento_investimento
SET data = new.data;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger update_dati_fun_2_trg
instead of update on inserimento_dati_vista for each row EXECUTE procedure update_dati_fun_2();
However when I perfomr the query in order to update only a row, the trigger update all the rows in the table. Here some code to fill data.
INSERT INTO inserimento_dati_vista
(data, ora, nome_cognome, nome_comune, nome_scientifico)
VALUES
('2020-01-01', '16:54:00','mario', 'lupo', 'Canis lupus'),
('2020-01-02', '13:54:00','luca', 'lontra', 'Lutra lutra');
UPDATE inserimento_dati_vista
SET data = '2021-01-02' where nome_cognome = 'luca'
Update function is:
CREATE OR REPLACE FUNCTION update_dati_fun_2() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF old.data is distinct from new.data then
UPDATE evento_investimento e
SET data = new.data
FROM rilevatore r
WHERE nome_cognome = new.nome_cognome AND r.rilevatore_id = e.rilevatore_id;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

PL/pgSQL: How to use IF NEW.<variable_column_name> <> OLD.<variable_column_name>

I am pretty new to PL/pgSQL programming. I have a requirement of audit logging updated columns in my table
Table
create table sample_table(name varchar(15),city varchar(15),age int,mail varchar(20) primary key);
Audit table
create table sample_table__audits_dynamicols(mail varchar(20), columnchanged varchar(10), oldvalue varchar(10), changed_on timestamp(6) NOT NULL)
Trigger Function
CREATE FUNCTION public.log_sample_table_allchanges() RETURNS trigger AS $BODY$DECLARE
_colname text;
_tablename varchar(15) := 'sample_table';
_schema varchar(15) := 'public';
_changed_on time := now();
BEGIN
FOR _colname IN SELECT column_name FROM information_schema.Columns WHERE table_schema = _schema AND table_name = _tablename LOOP
IF NEW._colname <> OLD._colname THEN
INSERT INTO sample_table__audits_dynamicols(mail,columnchanged, oldvalue ,changed_on)
VALUES(OLD.mail,_colname,OLD.:_colname,_changed_on);
END IF;
END LOOP;
RETURN NEW;
END$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
Trigger
create TRIGGER log_sample_table_allchanges
BEFORE UPDATE
ON SAMPLE_TABLE
FOR EACH ROW
EXECUTE PROCEDURE log_sample_table_allchanges();
Requirement: Whenever a column value is changed i want to log it as
(mail, columnname, columnvalue, date)
E.g:
insert into sample_table (name, mail, city, age) values('kanta','mk#foo.com','hyd',23);
insert into sample_table (name, mail, city, age) values('kmk','mk#gmail.com','hyd',23);
So when i update like the following
update sample_table set age=24 where mail='mk#foo.com';
update sample_table set city='bza' where mail='mk#gmail.com'
I want audit table to record like
(mk#foo.com,age,23, timestamp)
(mk#gmail.com, city, hyd, timestamp)
Right now I am facing issue with column comparison in my Trigger function. Please help me rectifying my Trigger function to meet my requirement.
You may use EXECUTE to get the values of columns dynamically and do the comparison.
CREATE OR REPLACE FUNCTION public.log_sample_table_allchanges() RETURNS trigger AS
$BODY$
DECLARE
_colname text;
_tablename varchar(15) := 'sample_table';
_schema varchar(15) := 'public';
_changed_on timestamp := now();
_old_val text;
_new_val text;
BEGIN
FOR _colname IN SELECT column_name FROM information_schema.Columns WHERE table_schema = _schema AND table_name = _tablename
LOOP
EXECUTE 'SELECT $1.' || _colname || ', $2.' || _colname
USING OLD,NEW
INTO _old_val, _new_val; --get the old and new values for the column.
IF _new_val <> _old_val THEN
INSERT INTO sample_table__audits_dynamicols(mail,columnchanged, oldvalue ,changed_on)
VALUES(OLD.mail,_colname,_old_val,_changed_on);
END IF;
END LOOP;
RETURN NEW;
END$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
I'm not sure why you have defined mail as a PRIMARY KEY in the audits table, it will cause unique constraint violation if the same mail gets updated twice.