How to use trigger with 3 different tables - postgresql

Dear Fellow StackOverFlow-members,
I have 3 tables. bdc(bdc_id, bdc_name, bdc_gc), stt(stt_id, stt_gc), bts(bts_id, bts_pmv).
I want if stt_gc = 'Checked' then set bdc_gc = 'Sent' and bts_pmv = 'To do'
I use Postgresql 11 and beginning with triggers/stored procedures
I tried to check with if condition stt_gc value and matching with the right bdc_gc bts_pmv according to their primary key.
create or replace function before_stt_gc() returns trigger
language plpgsql
as
$$
begin
if new.stt_gc='Checked' then
select bdc_gc from bdc
where new.stt_id = bdc_id;
doe_gc_bts= 'Sent';
select bts_pmv from bts
where new.stt_id = bts_id;
bts_pmv = 'To do'
end if;
end;
$$;
create trigger before_stt_gc_trigger before insert or update on stt
for each row
execute procedure before_stt_gc();
Obviously if I'm here it's because my code is totally wrong...
I want to learn from this, so if possible, explain me what I'm doing wrong here, or if my approach is lacking insight

I presume you are looking for updates within the IF statement
if new.stt_gc='Checked' then
update bdc set bdc_gc = 'Sent'
where new.stt_id = bdc_id;
UPDATE bts SET bts_pmv = 'To do'
where new.stt_id = bts_id;
end if;

Related

How to update same line than insert/update in plpgsql without reaching max_stack_depth

My problem is I reach the limit of the stack. And the message error says “You should increase max_stack_depth” and shows me the line that I use to update another column.
I encounter this error after an update request (code below).
I know my problem may look like others questions but none of them explain why I reach this error.
What I want to do is simple and I've done it many times, but here I'm missing something.
I want: if there is an update on the table support_fh pull a trigger. I expect this trigger to do:
if the new values of the update request are section= 'DISTRIBUTION' and modulo= '6' and fabricant = 'NEXANS' and capacite = 12 then set diametre = '12.5' (code below).
Of course it is the line of diametre from the same line than update request.
Futhermore I know I should use the character varying type instead of the integer type, but I was asked to so it like that.
My trigger function:
create or replace function maj_diam() returns trigger
as
$$
Declare fab_loc character varying;
Declare section_loc character varying;
Declare capa_loc character varying;
Declare modulo_loc character varying;
BEGIN
Select fabricant into fab_loc from support_fh where id = new.id;
Select section into section_loc from support_fh where id = new.id;
Select capcite into capa_loc from support_fh where id = new.id;
Select modulo into modulo_loc from support_fh where id = new.id;
if fab_loc = 'NEXANS' and section_loc = 'DISTRIBUTION'
and capa_loc = '12' and modulo_loc = '6' then
update support_fh set diametre = '12.2' where id = new.id;
endif;
return new;
end;
$$;
My trigger :
create trigger maj_diam
After update on support_fh
for each row
execute procedure maj_diam();
My update request to test my trigger :
update support_fh set fabricant = 'NEXANS', section = 'DISTRIBUTION', capacite = '12', modulo = '6'
where id = 11827;
I want to learn from this, so, if possible, explain to me what I'm doing wrong here, or if my approach is lacking insight.
You get that problem because the update in the trigger will launch the trigger again, causing an infinite loop. No value of max_stack_depth is big enough for that (and increasing that value too much is dangerous anyway).
Instead of what you are doing, you should create a BEFORE trigger and modify the NEW value that are about to be inserted:
IF NEW.fab_loc = 'NEXANS' AND NEW.section_loc = 'DISTRIBUTION'
AND NEW.capa_loc = '12' AND NEW.modulo_loc = '6'
THEN
NEW.diametre := '12.2';
END IF;
If you want to change columns in a row that is updated (or inserted), don't use UPDATE in the trigger function. Declare the trigger as BEFORE UPDATE, then simply assign the new values.
You also don't need four select statements to read four columns from the same table.
But as you are only accessing columns from the same row that was updated, you don't even need a SELECT at all.
So your trigger function can be simplified to:
create or replace function maj_diam() returns trigger
as
$$
BEGIN
if new.fabricant = 'NEXANS'
and new.section = 'DISTRIBUTION'
and new.capcite = '12'
and new.modulo = '6'
then
new.diametre := '12.2';
end if;
return new;
end;
$$;
Assuming that capcite, modulo and diametre are actually numbers, you shouldn't compare them with varchar values. So the above code should probably be: new.diametre := 12.2; or new.capcite = 12.
And the trigger definition needs to be changed to:
create trigger maj_diam
BEFORE update on support_fh
for each row
execute procedure maj_diam();

PostgreSQL trigger function update error

i cant seem to logically place this error being produced unlike most gives little to no information as to why (usually seems theirs very good pre-processors or you can dig into the logic, yet here i get no error.
also i have another function working well, that adds those keys, so its not that..
other then the transaction seems to fail and i get in the terminal when i enter
update guest_list set coatcheck = true where ticket_number = 3;
"PL/pgSQL function coatcheck_gen() line 8 at SQL statement
SQL statement "update guest_list set coatcheck_num = coat_num where ticket_number = old.ticket_number"
PL/pgSQL function coatcheck_gen() line 8 at SQL statement
SQL statement "update guest_list set coatcheck_num = coat_num where ticket_number = old.ticket_number"
this goes on for pages, then ends.
i've tried uses new. old. or just numbers to see. and nothing. same error.
all of the tables are fine. all updates work when just done on command.
it appears in examples elsewhere seemly correct...
the function is
create or replace function coatcheck_gen() returns trigger as $gencoatcheck$
declare
coat_num bigint;
begin
IF (TG_OP = 'UPDATE') then
if ( new.coatcheck = true ) then
coat_num := (old.frkey_id_event + old.frkey_id_guest);
update guest_list set coatcheck_num = coat_num where ticket_number = old.ticket_number;
return new;
END IF;
return new;
end if;
return new;
end;
$gencoatcheck$ LANGUAGE plpgsql;
trigger
create trigger trg_coatchek_gen after update on guest_list for each row when (new.coatcheck = true) execute Procedure coatcheck_gen();
You are making an infinite loop by updating the table inside the trigger.
You call it first and set the coatcheck = true, then the trigger update the table again but since coatcheck = true it will be again processed by the trigger (and this loop will never end).
You sould replace the entire line
update guest_list set coatcheck_num = coat_num where ticket_number = old.ticket_number;
by
new.coatcheck_num = coat_num;
and make the trigger before update

SELECT column WHERE (type = 'S' OR type = 'B') but perform different actions depending on whether type = 'S' or 'B'

))Hi all, currently Im stuck in an issue, hope some good PostgreSQL fellow programmer could give me a hand with it. This is my table...
I only want to select one "time" row, either WHERE "time_type" = 'Start' OR "time_type" = 'Break', but only one, the one that is at the bottom row (descending) (ORDER BY "fn_serial" DESC LIMIT 1).
Im successfully doing it by using this Trigger Function...
CREATE OR REPLACE FUNCTION timediff()
RETURNS trigger AS
$BODY$
DECLARE
prevtime character varying;
BEGIN
SELECT t.time FROM table_ebscb_spa_log04 t WHERE t.fn_name = NEW.fn_name AND (t.time_type = 'Start' OR time_type = 'Break') ORDER BY t.fn_serial DESC LIMIT 1 INTO prevtime;
IF NOT FOUND THEN
RAISE EXCEPTION USING MESSAGE = 'NOT FOUNDED';
ELSE
NEW.time_elapse := prevtime
END IF;
return NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION timediff()
OWNER TO postgres;
But in my script I would like to perform different actions depending on whether "fn_type" = 'Start' OR "fn_type = 'Break', I mean where "prevtime" variable came from, eg:
IF "prevtime" came from "fn_type" = 'Start' THEN
RAISE EXCEPTION USING MESSAGE = 'PREVTIME CAME FROM START';
ELSIF "prevtime" came from "fn_type" = 'BREAK' THEN
RAISE EXCEPTION USING MESSAGE = 'PREVTIME CAME FROM BREAK';
I can hardly imagine a way to do that, so I would like to ask for suggestions.
I guess one way to achieve this could be, create a sub IF, to check which one ('Start' OR 'Break') is at the bottom row (descending). How could I do that? or what could be a better approach?
Thanks Advanced.
Use more than one variable.
CREATE OR REPLACE FUNCTION timediff()
RETURNS trigger AS
$BODY$
DECLARE
prevtime character varying;
time_type character varying;
BEGIN
SELECT t.time, t.time_type FROM table_ebscb_spa_log04 t WHERE t.fn_name = NEW.fn_name AND (t.time_type = 'Start' OR time_type = 'Break') ORDER BY t.fn_serial DESC LIMIT 1 INTO prevtime,time_type;
IF NOT FOUND THEN
RAISE EXCEPTION USING MESSAGE = 'NOT FOUND';
ELSE
NEW.time_elapse := prevtime;
RAISE NOTICE "THE TIME CAME FROM %", time_type;
END IF;
return NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION timediff()
OWNER TO postgres;

Trigger to update old records in the same table using PL/pgsql

I'm trying to use a trigger function to update old records in the same table and mark them as redundant if a new record supersedes them.
I'm trying to use TG_TABLE_NAME as a generic way to update whichever table caused this trigger to fire. My code looks like this:
BEGIN
UPDATE TG_TABLE_NAME
SET "Redundant"=true
WHERE "DocumentID"=NEW."DocumentID"
AND "RecordID" = NEW."RecordID"
AND "TransactionID" < NEW."TransactionID"
AND "Redundant" = false ;
RETURN NEW;
END
But when the trigger fires, postgres complains that it can't find a table called "tg_table_name"
I'm guessing I'm doing something obviously wrong, but I'm new to pl/PGSQL. Does anyone have any advice for how to update old records (with matching RecordID and smaller TransactionID) ?
You cannot use variables for identifiers in plain SQL. You need to build SQL statements and use EXECUTE. Dynamic SQL.
Could look something like this:
CREATE FUNCTION foo() RETURNS trigger AS
$BODY$
BEGIN
EXECUTE '
UPDATE ' || quote_ident(TG_RELNAME) || '
SET "Redundant" = true
WHERE "DocumentID" = $1
AND "RecordID" = $2
AND "TransactionID" < $3
AND "Redundant" = FALSE'
USING
NEW."DocumentID"
,NEW."RecordID"
,NEW."TransactionID";
RETURN NEW;
END;
$BODY$ language plpgsql;
Note how I pass in variables with the USING clause. Simplifies the syntax.
You can find more information and links to the manual in this related answer.

count number of rows to be affected before update in trigger

I want to know number of rows that will be affected by UPDATE query in BEFORE per statement trigger . Is that possible?
The problem is that i want to allow only queries that will update up to 4 rows. If affected rows count is 5 or more i want to raise error.
I don't want to do this in code because i need this check on db level.
Is this at all possible?
Thanks in advance for any clues on that
Write a function that updates the rows for you or performs a rollback. Sorry for poor style formatting.
create function update_max(varchar, int)
RETURNS void AS
$BODY$
DECLARE
sql ALIAS FOR $1;
max ALIAS FOR $2;
rcount INT;
BEGIN
EXECUTE sql;
GET DIAGNOSTICS rcount = ROW_COUNT;
IF rcount > max THEN
--ROLLBACK;
RAISE EXCEPTION 'Too much rows affected (%).', rcount;
END IF;
--COMMIT;
END;
$BODY$ LANGUAGE plpgsql
Then call it like
select update_max('update t1 set id=id+10 where id < 4', 3);
where the first param ist your sql-Statement and the 2nd your max rows.
Simon had a good idea but his implementation is unnecessarily complicated. This is my proposition:
create or replace function trg_check_max_4()
returns trigger as $$
begin
perform true from pg_class
where relname='check_max_4' and relnamespace=pg_my_temp_schema();
if not FOUND then
create temporary table check_max_4
(value int check (value<=4))
on commit drop;
insert into check_max_4 values (0);
end if;
update check_max_4 set value=value+1;
return new;
end; $$ language plpgsql;
I've created something like this:
begin;
create table test (
id integer
);
insert into test(id) select generate_series(1,100);
create or replace function trg_check_max_4_updated_records()
returns trigger as $$
declare
counter_ integer := 0;
tablename_ text := 'temptable';
begin
raise notice 'trigger fired';
select count(42) into counter_
from pg_catalog.pg_tables where tablename = tablename_;
if counter_ = 0 then
raise notice 'Creating table %', tablename_;
execute 'create temporary table ' || tablename_ || ' (counter integer) on commit drop';
execute 'insert into ' || tablename_ || ' (counter) values(1)';
execute 'select counter from ' || tablename_ into counter_;
raise notice 'Actual value for counter= [%]', counter_;
else
execute 'select counter from ' || tablename_ into counter_;
execute 'update ' || tablename_ || ' set counter = counter + 1';
raise notice 'updating';
execute 'select counter from ' || tablename_ into counter_;
raise notice 'Actual value for counter= [%]', counter_;
if counter_ > 4 then
raise exception 'Cannot change more than 4 rows in one trancation';
end if;
end if;
return new;
end; $$ language plpgsql;
create trigger trg_bu_test before
update on test
for each row
execute procedure trg_check_max_4_updated_records();
update test set id = 10 where id <= 1;
update test set id = 10 where id <= 2;
update test set id = 10 where id <= 3;
update test set id = 10 where id <= 4;
update test set id = 10 where id <= 5;
rollback;
The main idea is to have a trigger on 'before update for each row' that creates (if necessary) a temporary table (that is dropped at the end of transaction). In this table there is just one row with one value, that is the number of updated rows in current transaction. For each update the value is incremented. If the value is bigger than 4, the transaction is stopped.
But I think that this is a wrong solution for your problem. What's a problem to run such wrong query that you've written about, twice, so you'll have 8 rows changed. What about deletion rows or truncating them?
PostgreSQL has two types of triggers: row and statement triggers. Row triggers only work within the context of a row so you can't use those. Unfortunately, "before" statement triggers don't see what kind of change is about to take place so I don't believe you can use those, either.
Based on that, I would say it's unlikely you'll be able to build that kind of protection into the database using triggers, not unless you don't mind using an "after" trigger and rolling back the transaction if the condition isn't satisfied. Wouldn't mind being proved wrong. :)
Have a look at using Serializable Isolation Level. I believe this will give you a consistent view of the database data within your transaction. Then you can use option #1 that MusiGenesis mentioned, without the timing vulnerability. Test it of course to validate.
I've never worked with postgresql, so my answer may not apply. In SQL Server, your trigger can call a stored procedure which would do one of two things:
Perform a SELECT COUNT(*) to determine the number of records that will be affected by the UPDATE, and then only execute the UPDATE if the count is 4 or less
Perform the UPDATE within a transaction, and only commit the transaction if the returned number of rows affected is 4 or less
No. 1 is timing vulnerable (the number of records affected by the UPDATE may change between the COUNT(*) check and the actual UPDATE. No. 2 is pretty inefficient, if there are many cases where the number of rows updated is greater than 4.