SQL Trigger Insert After Modify Value - triggers

I'm looking to make a trigger on new insert to modify a value. The scenario is as follows. When a new entry to the table is added, and if the ObjectTypeID is between 684 and 750, update the quality value to 100. From what I have read so far, I believe this should be done on insert after, but I am still new to SQL and learning. I have been unable to get this to work so far.
Here is my current attempt:
CREATE TRIGGER `items_after_insert` AFTER INSERT ON `items` FOR EACH ROW
BEGIN
SET items.Quality = '100'
FROM items
WHERE items.ObjectTypeID > '653' AND items.ObjectTypeID < '751'
END
Solved using the suggestion below:
CREATE TRIGGER `items_before_insert` BEFORE INSERT ON `items` FOR EACH ROW BEGIN
IF NEW.ObjectTypeID > '653' AND NEW.ObjectTypeID <'751' THEN
SET NEW.Quality = '100';
END IF;
END

You can use the keywords NEW and OLD on triggers to refer to inserted registers and updated registers.
Look at this example from MySQL documentation:
CREATE TRIGGER upd_check BEFORE UPDATE ON account
FOR EACH ROW
BEGIN
IF NEW.amount < 0 THEN
SET NEW.amount = 0;
ELSEIF NEW.amount > 100 THEN
SET NEW.amount = 100;
END IF;
END;
Now I think it´s easy to U solve your problem.

Related

How to use trigger with 3 different tables

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;

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

Execute deferred trigger only once per row in PostgreSQL

I have a deferred AFTER UPDATE trigger on a table, set to fire when a certain column is updated. It's an integer type I'm using as a counter.
I'm not 100% certain but it looks like if I increment that particular column 100 times during a transaction, the trigger is queued up and executed 100 times at the end of the transaction.
I would like the trigger to only be scheduled once per row no matter how many times I've incremented that column.
Can I do that somehow?
Alternatively if triggered triggers must queue up regardless if they are duplicates, can I clear this queue during the first run of the trigger?
Version of Postgres is 9.1. Here's what I got:
CREATE CONSTRAINT TRIGGER counter_change
AFTER UPDATE OF "Counter" ON "table"
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE counter_change();
CREATE OR REPLACE FUNCTION counter_change()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
PERFORM some_expensive_procedure(NEW."id");
RETURN NEW;
END;$$;
This is a tricky problem. But it can be done with per-column triggers and conditional trigger execution introduced in PostgreSQL 9.0.
You need an "updated" flag per row for this solution. Use a boolean column in the same table for simplicity. But it could be in another table or even a temporary table per transaction.
The expensive payload is executed once per row where the counter is updated (once or multiple time).
This should also perform well, because ...
... it avoids multiple calls of triggers at the root (scales well)
... does not change additional rows (minimize table bloat)
... does not need expensive exception handling.
Consider the following
Demo
Tested in PostgreSQL 9.1 with a separate schema x as test environment.
Tables and dummy rows
-- DROP SCHEMA x;
CREATE SCHEMA x;
CREATE TABLE x.tbl (
id int
,counter int
,trig_exec_count integer -- for monitoring payload execution.
,updated bool);
Insert two rows to demonstrate it works with multiple rows:
INSERT INTO x.tbl VALUES
(1, 0, 0, NULL)
,(2, 0, 0, NULL);
Trigger functions and Triggers
1.) Execute expensive payload
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_1()
RETURNS trigger AS
$BODY$
BEGIN
-- PERFORM some_expensive_procedure(NEW.id);
-- Update trig_exec_count to count execution of expensive payload.
-- Could be in another table, for simplicity, I use the same:
UPDATE x.tbl t
SET trig_exec_count = trig_exec_count + 1
WHERE t.id = NEW.id;
RETURN NULL; -- RETURN value of AFTER trigger is ignored anyway
END;
$BODY$ LANGUAGE plpgsql;
2.) Flag row as updated.
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_2()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE x.tbl
SET updated = TRUE
WHERE id = NEW.id;
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
3.) Reset "updated" flag.
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_3()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE x.tbl
SET updated = NULL
WHERE id = NEW.id;
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
Trigger names are relevant! Called for the same event they are executed in alphabetical order.
1.) Payload, only if not "updated" yet:
CREATE CONSTRAINT TRIGGER upaft_counter_change_1
AFTER UPDATE OF counter ON x.tbl
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
WHEN (NEW.updated IS NULL)
EXECUTE PROCEDURE x.trg_upaft_counter_change_1();
2.) Flag row as updated, only if not "updated" yet:
CREATE TRIGGER upaft_counter_change_2 -- not deferred!
AFTER UPDATE OF counter ON x.tbl
FOR EACH ROW
WHEN (NEW.updated IS NULL)
EXECUTE PROCEDURE x.trg_upaft_counter_change_2();
3.) Reset Flag. No endless loop because of trigger condition.
CREATE CONSTRAINT TRIGGER upaft_counter_change_3
AFTER UPDATE OF updated ON x.tbl
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
WHEN (NEW.updated) --
EXECUTE PROCEDURE x.trg_upaft_counter_change_3();
Test
Run UPDATE & SELECT separately to see the deferred effect. If executed together (in one transaction) the SELECT will show the new tbl.counter but the old tbl2.trig_exec_count.
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
Now, update the counter multiple times (in one transaction). The payload will only be executed once. Voilá!
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
I don't know of a way to collapse trigger execution to once per (updated) row per transaction, but you can emulate this with a TEMPORARY ON COMMIT DROP table which tracks those modified rows and performs your expensive operation only once per row per tx:
CREATE OR REPLACE FUNCTION counter_change() RETURNS TRIGGER
AS $$
BEGIN
-- If we're the first invocation of this trigger in this tx,
-- make our scratch table. Create unique index separately to
-- suppress avoid NOTICEs without fiddling with log_min_messages
BEGIN
CREATE LOCAL TEMPORARY TABLE tbl_counter_tx_once
("id" AS_APPROPRIATE NOT NULL)
ON COMMIT DROP;
CREATE UNIQUE INDEX ON tbl_counter_tx_once AS ("id");
EXCEPTION WHEN duplicate_table THEN
NULL;
END;
-- If we're the first invocation in this tx *for this row*,
-- then do our expensive operation.
BEGIN
INSERT INTO tbl_counter_tx_once ("id") VALUES (NEW."id");
PERFORM SOME_EXPENSIVE_OPERATION_HERE(NEW."id");
EXCEPTION WHEN unique_violation THEN
NULL;
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
There's of course a risk of name collision with that temporary table, so choose judiciously.

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.

In DB2, is it possible to have just a single trigger for both update and insert?

I have to create the trigger(s) which will keep the audit of my table. The trigger is supposed to execute on both insert and update.
currently i am having two triggers
One for Insert:
CREATE TRIGGER SCH.TRG_TBL1_AFT_I
AFTER INSERT ON SCH.TBL1
REFERENCING
NEW AS n
FOR EACH ROW
MODE DB2SQL
INSERT INTO SCH.TBL1_AUDIT
VALUES( .. ,, .. );
Another for update
CREATE TRIGGER SCH.TRG_TBL1_AFT_U
AFTER UPDATE ON SCH.TBL1
REFERENCING
NEW AS n
FOR EACH ROW
MODE DB2SQL
INSERT INTO SCH.TBL1_AUDIT
VALUES( .. ,, .. );
But the point is, if it possible to create a single trigger, in DB2, for doing the task? [ provided both, trigger are doing the same thing .]
Try this, the feature has been available since version 9.7.x.
CREATE or replace TRIGGER PASSENGER_TR01_BEFORE_IUD
BEFORE
DELETE
OR UPDATE OF FIRST_NAME
OR INSERT ON PASSENGER
REFERENCING
OLD AS oldRow
NEW AS newRow
FOR EACH ROW
WHEN (1=1)
Begin
Declare ACTION Char(1) Default '';
-- Use Case/When to inquire trigger-event
Case
When INSERTING Then
Set ACTION='I';
When UPDATING Then
Set ACTION='U';
When DELETING Then
Set ACTION='D';
Else
Set ACTION='N';
End Case;
-- Use If/Then/Else to inquire trigger-event
If INSERTING Then
Set ACTION='I';
ElseIf UPDATING Then
Set ACTION='U';
ElseIf DELETING Then
Set ACTION='D';
Else
Set ACTION='N';
End If;
End
Yes it is possible. See the documentation of create trigger. Here is a paste:
trigger-event
.-OR--------------------------------------.
V (4) |
|----+-INSERT--------------------------+-----+------------------|
+-DELETE--------------------------+
'-UPDATE--+---------------------+-'
| .-,-----------. |
| V | |
'-OF----column-name-+-'
That would enable you to say:
create trigger blah before insert on blah or update of blah.
You can find more info here
CREATE OR REPLACE TRIGGER SET_SALARY
NO CASCADE
BEFORE UPDATE OR INSERT ON employee
REFERENCING NEW AS n OLD AS o
FOR EACH ROW
WHEN( o.edlevel IS NULL OR n.edlevel > o.edlevel )
BEGIN
-- Give 10% raise to existing employees with new education level.
IF UPDATING THEN SET n.salary = n.salary * 1.1;
-- Give starting salary based on education level.
ELSEIF INSERTING THEN
SET n.salary = CASE n.edlevel WHEN 18 THEN 50000
WHEN 16 THEN 40000
ELSE 25000
END;
END IF;
END
Sorry, DB2 doesn't offer a way to combine update and insert triggers together.