A trigger that triggers a change in a specific field Firebird 3.0 - sql

SET TERM ^ ;
CREATE TRIGGER ADDPWDHASHHISTORY FOR USERS ACTIVE
AFTER INSERT OR UPDATE POSITION 3
AS
BEGIN
INSERT INTO USERSPWDHASHHISTORY
(id,
USERID,
FIO,
PWDHASH,
ATTIME)
values (gen_id(GEN_PWDHASHHIS_ID,1) , new.id, new.fio, new.pwdhash, CURRENT_TIMESTAMP);
END^
SET TERM ; ^
This trigger is triggered to change any field in the USERS table. I need to change it, to trigger only on changing the PWDNASH field. Please help

A trigger on insert or update will fire for any insert or update on the table. If you want to have conditional behaviour on specific fields, you will need to do that in the trigger body.
In this case, you will need to do something like:
SET TERM ^ ;
CREATE TRIGGER ADDPWDHASHHISTORY FOR USERS ACTIVE
AFTER INSERT OR UPDATE POSITION 3
AS
BEGIN
if (inserting or new.PWDHASH is distinct from old.PWDHASH) then
INSERT INTO USERSPWDHASHHISTORY
(id,
USERID,
FIO,
PWDHASH,
ATTIME)
values (gen_id(GEN_PWDHASHHIS_ID,1) , new.id, new.fio, new.pwdhash, CURRENT_TIMESTAMP);
END^
SET TERM ; ^
That is, the inserting condition will be true when the trigger was fired for an insert, while the new.PWDHASH is distinct from old.PWDHASH checks if the value has changed in an update. You could also use the condition inserting or updating and new.PWDHASH is distinct from old.PWDHASH, but that is not really necessary, so I opted for the shorter version.
See also:
INSERTING
IS [NOT] DISTINCT FROM
OLD and NEW Context Variables

Related

trigger to update specific column when insert/update happened in same table

I trying to write a trigger that will update a column when user insert or updates a row, within the same table.
Example:
insert into user(ID, F_NM, L_NM, EMAIL) values ('1', 'John','Doe','john.doe#market.org.com');
after the insert, i want to call: update user set ORG = 'market' where ID = '1'.
create or replace trigger user_change
after insert or update of EMAIL on USER
for each row
declare
NEW_ORG VARCHAR(10);
BEGIN
CASE
when :NEW.EMAIL like '$#market.org.com' then
NEW_ORG := 'market';
........
END CASE;
UPDATE USER set ORG = NEW_ORG where ID = :NEW.ID
END;
Calculating the new ORG work, but I can't get the update statement to work.
I get 'ORA-04091 table USER is mutating, trigger/funtion may not see it', figure its due to me inserting/updating the same record at same time. Tried adding 'pragma autonomous_transaction' and 'commit' to the trigger, the insert/update of fields works but the ORG does not get updated.
Also tried changing to INSTEAD OF INSERT OR UPDATE OF EMAIL but I keep getting 'ORA-04073 column list not valid for this trigger type'
create or replace trigger user_change
instead of insert or update of EMAIL on USER
while i get 'ORA-25002 cannot create instead of triggers on tables'
create or replace trigger user_change
instead of insert on USER
Why not simply turn the trigger to a before trigger, when you can set the value before it is written? This way, you don't need to run a new DML statement on the table, which avoid the "mutating" error.
create or replace trigger user_change
after insert or update of email on user
for each row
begin
if :new.email like '%#market.org.com' then
:new.org := 'market';
end if;
end;
Looks like your org column can be calculated virtual column. In this case it would be better to create user-defined deterministic pl/sql function that returns correct calculated value and add it to your table, for example:
Alter table t add org varchar2(30) generated always as (f_get_org(email))

PostgreSQL, check for duplicate before insert

I'm running PostgreSQL 10 and have several BEFORE INSERT OR UPDATE and AFTER INSERT OR UPDATE on my table tests.
I want to have another trigger BEFORE INSERT OR UPDATE which should check for potential duplicate row.
I've made this:
CREATE OR REPLACE FUNCTION set_check_dublicate_on_test() RETURNS trigger AS $$
BEGIN
IF EXISTS (SELECT 1 FROM tests WHERE test_id = NEW.test_id) THEN
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS set_check_dublicate_on_test ON tests;
CREATE TRIGGER set_check_dublicate_on_test BEFORE INSERT OR UPDATE ON tests
FOR EACH ROW EXECUTE PROCEDURE set_check_dublicate_on_test();
But I'm not sure if it will conflict with other triggers or it will fullfill the goal, and the triggers simply will be ignored if this returns NULL ?
Firstly - I believe that if you want to have a unique field in your table - then it is the easiest to mark it as such:
https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-UNIQUE-CONSTRAINTS
If an attempted insert on such a field should not raise an error, then there is a the ON CONFLICT keyword:
https://www.postgresql.org/docs/current/sql-insert.html
Especially: ON CONFLICT DO NOTHING
Let the database manage uniqueness! This ensures data integrity for both inserts and updates.
This is quite simple:
alter table test add constraint unq_test_test_id unique (test_id);
If you insert rows one at a time, then this is fine. The insert (or update) will fail.
If you want to insert multiple rows and allow non-duplicate inserts to go in, then us on conflict:
insert into test ( . . . )
select . ..
from . . .
on conflict on constraint unq_test_test_id do nothing;

UPSERT inserts duplicate null entry into table (ORACLE)

I am trying to make an upsert trigger on ORACLE via PL/SQL by checking some examples, i am doing fine, i think it is the last step i should only configure. My requirement is that :
A system that will insert to that field will remain one column always null, so i will read column value from another table, then upsert it with inclusion of that value.
d2c_region_locale_config holds d2c_is_active value, so i firstly read that value regarding to locale condition then trigger inserts or updates to table with addition of this value on active_for_d2c column.(for update i am using locale and country columns as it is shown on where clause, they are not PK but has not null condition)
So i've created this trigger:
CREATE OR REPLACE TRIGGER BL_PIM_LOCALE_COUNTRY
BEFORE INSERT OR UPDATE ON PIM_LOCALE_COUNTRY REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
l_active_for_d2c INTEGER;
BEGIN
if :NEW.active_for_d2c is null then
DELETE from pim_locale_country where active_for_d2c is null;
select distinct(d2c_isactive) into l_active_for_d2c from d2c_region_locale_config where d2c_locale= :NEW.locale;
UPDATE pim_locale_country
SET locale = :NEW.locale, locale_name = :NEW.locale_name,
country = :NEW.country, country_name = :NEW.country_name, isdummy = :NEW.isdummy,
active_for_d2c = l_active_for_d2c, itextpos = :NEW.itextpos, locale_charset = :NEW.locale_charset,
fallback_locale = :NEW.fallback_locale, default_for_lang = :NEW.default_for_lang, opeclang = :NEW.opeclang
where locale = :NEW.locale and country = :NEW.country;
IF ( sql%notfound ) THEN
INSERT INTO PIM_LOCALE_COUNTRY (locale,locale_name,country,country_name,isdummy,active_for_d2c,itextpos,locale_charset,fallback_locale,default_for_lang,opeclang)
VALUES (:NEW.locale, :NEW.locale_name,:NEW.country,:NEW.country_name,:NEW.isdummy,l_active_for_d2c,:NEW.itextpos,:NEW.locale_charset,:NEW.fallback_locale,:NEW.default_for_lang,:NEW.opeclang);
END IF;
end if;
END;
It currently does the job, reads value and inserts or updates the existing locale-country couple for other values. But critical thing is that, table always has one "null" value(Please check screenshot), even that i run delete statement at the beginning on my trigger. So my question would be how to delete, or how to make this approach on trigger side ?
Many thanks for answers!
Trigger before insert doesn't block insert itself, so you insert that record twice. That is, once your trigger done its work (inserted or updated record), oracle will proceed with insert (or update) using values that stand in NEW record of your trigger. If trigger modifies NEW., it will be stored as you changed it, but if trigger inserts something itself, you can get more records.
You can use instead of insert or update triggers, and then oracle will not run its own inserts/updates after trigger finishes.
But more common way for 1-record triggers is to modify fields in NEW, for this case field NEW.d2c_is_active.
It looks like this (possible typos, please check)
CREATE OR REPLACE TRIGGER BL_PIM_LOCALE_COUNTRY
BEFORE INSERT OR UPDATE ON PIM_LOCALE_COUNTRY REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
BEGIN
if :NEW.active_for_d2c is null then
select d2c_isactive
into :NEW.active_for_d2c
from d2c_region_locale_config
where d2c_locale= :NEW.locale and rownum<=1;
end if;
END;

Oracle trigger to prevent inserting the new row upon a condition

I've found few questions addressing the same question but without a better solution.
I need to create an Oracle trigger which will prevent new inserts upon a condition, but silently (without raising an error).
Ex : I need to stop inserting rows with bar='FOO' only. (I can't edit the constraints of the table, can't access the procedure which really does the insertion etc so the trigger is the only option)
Solutions so far confirms that it isn't possible. One promising suggestion was to create an intermediate table, insert key values to that when bar='FOO' and then delete those records from original table once insertion is done, which is not correct I guess.
Any answer will be highly appreciated.
Apparently, it is not possible to use a trigger to stop inserts without raising an exception.
However, if you have access to the schema (and asking about a trigger this is probably ok), you could think about replacing the table with a view and an instead of trigger.
As a minimal mock up for your current table. myrole is just a stand in for the privileges granted on the table:
CREATE ROLE myrole;
CREATE TABLE mytable (
bar VARCHAR2(30)
);
GRANT ALL ON mytable TO myrole;
Now you rename the table and make sure nobody can directly access it anymore, and replace it with a view. This view can be protected by a instead of trigger:
REVOKE ALL ON mytable FROM myrole;
RENAME mytable TO myrealtable;
CREATE OR REPLACE VIEW mytable AS SELECT * FROM myrealtable;
GRANT ALL ON mytable TO myrole;
CREATE OR REPLACE TRIGGER myioftrigger
INSTEAD OF INSERT ON mytable
FOR EACH ROW
BEGIN
IF :new.bar = 'FOO' THEN
NULL;
ELSE
INSERT INTO myrealtable(bar) VALUES (:new.bar);
END IF;
END;
/
So, if somebody is inserting a normal row into the fake view, the data gets inserted into your real table:
INSERT INTO mytable(bar) VALUES('OK');
1 row inserted.
SELECT * FROM mytable;
OK
But if somebody is inserting the magic value 'FOO', the trigger silently swallows it and nothing gets changed in the real table:
INSERT INTO mytable(bar) VALUES('FOO');
1 row inserted.
SELECT * FROM mytable;
OK
Caution: If you want to protect your table from UPDATEs as well, you'd have to add a second trigger for the updates.
One way would be to hide the row. From 12c this is reasonably easy:
create table demo
( id integer primary key
, bar varchar2(10) );
-- This adds a hidden column and registers the table for in-database archiving:
alter table demo row archival;
-- Set the hidden column to '1' when BAR='FOO', else '0':
create or replace trigger demo_hide_foo_trg
before insert or update on demo
for each row
begin
if :new.bar = 'FOO' then
:new.ora_archive_state := '1';
else
:new.ora_archive_state := '0';
end if;
end demo_hide_foo_trg;
/
-- Enable in-database archiving for the session
-- (probably you could set this in a log-on trigger):
alter session set row archival visibility = active;
insert into demo (id, bar) values (1, 'ABC');
insert into demo (id, bar) values (2, 'FOO');
insert into demo (id, bar) values (3, 'XYZ');
commit;
select * from demo;
ID BAR
-------- --------
1 ABC
3 XYZ
-- If you want to see all rows (e.g. to delete hidden rows):
alter session set row archival visibility = all;
In earlier versions of Oracle, you could achieve the same thing using a security policy.
Another way might be to add a 'required' flag which defaults to 'Y' and set it to to 'N' in a trigger when bar = 'FOO', and (assuming you can't change the application to use a view etc) have a second trigger delete all such rows (or perhaps better, move them to an archive table).
create table demo
( id integer primary key
, bar varchar2(10) );
alter table demo add required_yn varchar2(1) default on null 'Y';
create or replace trigger demo_set_not_required_trg
before insert or update on demo
for each row
begin
if :new.bar = 'FOO' then
:new.required_yn := 'N';
end if;
end demo_hide_foo_trg;
/
create or replace trigger demo_delete_not_required_trg
after insert or update on demo
begin
delete demo where required_yn = 'N';
end demo_delete_not_required_trg;
/

Disable Trigger for a particular DELETE Query

I have a ruby app. The app is doing the insert,update and delete on a particular table.
It does 2 kinds of INSERT, one insert should insert a record in the table and also into trigger_logs table. Another insert is just to insert the record into the table and do nothing. Another way to put it is, one kind of insert should log that the 'insert' happened into another table and another kind of insert should just be a normal insert. Similarly, there are 2 kinds of UPDATE and DELETE also.
I have achieved the 2 types of INSERT and UPDATE using a trigger_disable. Please refer to the trigger code below.
So, when I do a INSERT, I will set the trigger_disable boolean to true if I don't want to log the trigger. Similarly I am doing for an UPDATE too.
But I am not able to differentiate between the 2 kinds of DELETE as I do for an INSERT or UPDATE. The DELETE action is logged for both kinds of DELETE.
NOTE: I am logging all the changes that are made under a certain condition, which will be determined by the ruby app. If the condition is not satisfied, I just need to do a normal INSERT, UPDATE or DELETE accordingly.
CREATE OR REPLACE FUNCTION notify_#{#table_name}()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
changed_row_id varchar(100);
BEGIN
IF TG_OP = 'DELETE' THEN
-- When the trigger is due to a delete
IF (OLD.trigger_disable IS NULL)
OR (OLD.trigger_disable = false) THEN
-- Prevent the trigger if trigger_disable is 'true'
-- The Problem is here: This insertion into the
-- trigger_logs table happens
-- for all the delete statements.
-- But during certain deletes I should not
-- insert into trigger_logs
INSERT INTO trigger_logs (table_name, action, row_id, dirty)
VALUES (
'#{#table_name}',
CAST(TG_OP AS Text),
OLD.id,
true
) RETURNING id into changed_row_id;
END IF;
RETURN OLD;
ELSE
-- The trigger is due to a Insert or Update
IF (NEW.trigger_disable IS NULL)
OR (NEW.trigger_disable = false) THEN
-- Prevent the trigger if trigger_disable is 'true'
INSERT INTO trigger_logs (table_name, action, row_id, dirty)
VALUES (
'#{#table_name}',
CAST(TG_OP AS Text),
NEW.id,
true
) RETURNING id into changed_row_id;
ELSE
NEW.trigger_disable := false;
END IF;
RETURN NEW;
END IF;
END
I'm going to take a stab in the dark here and guess that you're trying to contextually control whether triggers get fired.
If so, perhaps you can use a session variable?
BEGIN;
SET LOCAL myapp.fire_trigger = 'false';
DELETE FROM ...;
COMMIT;
and in your trigger, test it:
IF current_setting('myapp.fire_trigger') = 'true' THEN
Note, however, that if the setting is missing from a session you won't get NULL, you'll get an error:
regress=> SELECT current_setting('myapp.xx');
ERROR: unrecognized configuration parameter "myapp.xx"
so you'll want to:
ALTER DATABASE mydb SET myapp.fire_trigger = 'true';
Also note that the parameter is text not boolean.
Finally, there's no security on session variables. So it's not useful for security audit, since anybody can come along and just SET myapp.fire_trigger = 'false'.
(If this doesn't meet your needs, you might want to re-think whether you should be doing this with triggers at all, rather than at the application level).