Oracle trigger exclude conditions - sql

I am trying to create an Oracle trigger
CREATE OR REPLACE TRIGGER ACT_SESSION_AUDIT_TRIGGER
AFTER INSERT OR UPDATE
ON ACT_SESSION
FOR EACH ROW
DECLARE
BEGIN
if (:NEW.PROTOCOL='HTTP' or :NEW.PROTOCOL='SFTP') and :NEW.PRINCIPAL NOT IN ('AAA%', 'BBB%') then
INSERT INTO ACT_SESSION_AUDIT (PRINCIPAL,
CON_START_TIME,
DIS_END_TIME,
ADAPTER_NAME,
ADAPTER_TYPE,
PROTOCOL)
VALUES(:NEW.PRINCIPAL,
:NEW.CON_START_TIME,
:NEW.DIS_END_TIME,
:NEW.ADAPTER_NAME,
:NEW.ADAPTER_TYPE,
:NEW.PROTOCOL);
END if;
END;
However when the PRINCIPAL begins with AAA, it causes the trigger to be invoked. What is the correct way of using NOT IN with partial strings ?

You need LIKEfor the pattern matching in 'AAA%' and 'BBB%'.
IF :new.protocol IN ('HTTP', 'SFTP') AND
:new.principal NOT LIKE 'AAA%' AND
:new.principal NOT LIKE 'BBB%' THEN
...
You can move this to a WHEN cause by the way, to make this part of the trigger declaration part:
CREATE OR REPLACE TRIGGER ACT_SESSION_AUDIT_TRIGGER
AFTER INSERT OR UPDATE
ON act_session
FOR EACH ROW
WHEN (new.protocol IN ('HTTP', 'SFTP') AND
new.principal NOT LIKE 'AAA%' AND
new.principal NOT LIKE 'BBB%')
BEGIN
In theory this is faster, because the trigger won't get invoked, when the criteria is not met. I don't know, if this can really make a difference.

Related

Oracle trigger error ORA-04091

I get an error (ORA-04091: table DBPROJEKT_AKTIENDEPOT.AKTIE is mutating, trigger/function may not see it) when executing my trigger:
CREATE OR REPLACE TRIGGER Aktien_Bilanz_Berechnung
AFTER
INSERT OR UPDATE OF TAGESKURS
OR INSERT OR UPDATE OF WERT_BEIM_EINKAUF
ON AKTIE
FOR EACH ROW
DECLARE
bfr number;
Begin
bfr := :new.TAGESKURS - :new.WERT_BEIM_EINKAUF;
UPDATE AKTIE
SET BILANZ = TAGESKURS - WERT_BEIM_EINKAUF;
IF bfr < -50
THEN
DBMS_OUTPUT.PUT_LINE('ACHTUNG: The value (Nr: '||:new.AKTIEN_NR||') is very low!');
END IF;
END;
I want to check the value "BILANZ" after calculating it, wether it is under -50.
Do you have any idea why this error is thrown?
Thanks for any help!
There are several issues here:
Oracle does not allow you to perform a SELECT/INSERT/UPDATE/DELETE against a table within a row trigger defined on that table or any code called from such a trigger, which is why an error occurred at run time. There are ways to work around this - for example, you can read my answers to this question and this question - but in general you will have to avoid accessing the table on which a row trigger is defined from within the trigger.
The calculation which is being performed in this trigger is what is referred to as business logic and should not be performed in a trigger. Putting logic such as this in a trigger, no matter how convenient it may seem to be, will end up being very confusing to anyone who has to maintain this code because the value of BILANZ is changed where someone who is reading the application code's INSERT or UPDATE statement can't see it. This calculation should be performed in the INSERT or UPDATE statement, not in a trigger. It considered good practice to define a procedure to perform INSERT/UPDATE/DELETE operations on a table so that all such calculations can be captured in one place, instead of being spread out throughout your code base.
Within a BEFORE ROW trigger you can modify the values of the fields in the :NEW row variable to change values before they're written to the database. There are times that this is acceptable, such as when setting columns which track when and by whom a row was last changed, but in general it's considered a bad idea.
Best of luck.
You are modifying the table with the trigger. Use a before update trigger:
CREATE OR REPLACE TRIGGER Aktien_Bilanz_Berechnung
BEFORE INSERT OR UPDATE OF TAGESKURS OR INSERT OR UPDATE OF WERT_BEIM_EINKAUF
ON AKTIE
FOR EACH ROW
DECLARE
v_bfr number;
BEGIN
v_bfr := :new.TAGESKURS - :new.WERT_BEIM_EINKAUF;
:new.BILANZ := v_bfr;
IF v_bfr < -50 THEN
Raise_Application_Error(-20456,'ACHTUNG: The value (Nr: '|| :new.AKTIEN_NR || ') is very low!');
END IF;
END;

Generic Postgres 9.5 trigger to convert an UPDATE into modified INSERT

Is it possible to create a generic (not table-specific) trigger in Postgres 9.5 that would perform on instead of update that converts the update into an insert?
Basically what I want to do is (pseudocode):
sql
instead of UPDATE on TG_TABLE_NAME INSERT on TG_TABLE_NAME
I know I can create a very table-specific trigger that maps each value into an insert statement. What I'm trying to do is get away from creating this trigger on every single table.
It is a bit of an oddball idea (nothing personal), but how about this:
CREATE FUNCTION not_update_but_insert() RETURNS trigger AS $$
BEGIN
INSERT INTO TG_TABLE_NAME -- Do an INSERT...
SELECT NEW.*; -- ... using the values from the row to be updated
RETURN NULL; -- Fail the UPDATE
END;
$$ LANGUAGE plpgsql;
Obviously this would not work for any table that has a PRIMARY KEY or other UNIQUE constraints. You do have to CREATE TRIGGER x BEFORE UPDATE for every table this would apply to, so analyze your table structure before creating the trigger.
There is obviously a work-around - at least for the PKs based on a sequence - by examining the information_schema for "safe" columns in TG_TABLE_NAME and then assembling their names into strings to splice into the INSERT statement (column list of main statement and select list). Just leave the columns with sequences or appropriate default values out. This, however, does not address UNIQUE constraints that have no obvious replacement (like a user name or an email address).

postgresql triggers possible infinte loop?

I have a table with 3 fields
id, name , value
I want to add a 4th colum calcValue. The calculation of calcValue is based on the filed value in the whole table, changing it in one row can cause changes in all of the others.
I want to write a trigger that updates calcValue everytime there is insert, delete or update in the table.
What i'm worry about is that the trigger itself is going to have an Update command. Will it case another call to this trigger? Will I be stuck with infinte loop?
To describe it better:
CREATE TRIGGER x
AFTER INSERT OR UPDATE OR DELETE
ON a
FOR EACH ROW
EXECUTE PROCEDURE dosomething();
and:
CREATE OR REPLACE FUNCTION dosomething()
RETURNS trigger AS
$BODY$
begin
code + calculation of result...
for row in
QUERY
Loop
Update a set calValue=result where id=...
end loop;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
Will the update of a in dosomething() will cause another invoke of the trigger x? If it does is there a way to handle it so it won't stuck in infinte loop?
My goal is to do dosomething() once per update/insert/delete action of a . I don't want dosomething() to be called again because of the update a in the trigger.
Edit: Since i'm updating in the trigger a diffrent column I can do that:
CREATE TRIGGER x
BEFORE UPDATE OF value ON a
FOR EACH ROW
EXECUTE PROCEDURE dosomething();
this should solve my problem as the trigger updates calcValue and the tigger isn't set invoke on value column. However I would still like to know if there is an answer to my original question... suppose that the trigger would have update the same column.
Another alternative would be to add a when clause, along the lines of:
CREATE TRIGGER x
AFTER INSERT OR UPDATE OR DELETE
ON a
FOR EACH ROW
WHEN NEW IS NULL OR OLD IS NULL OR NEW.value <> OLD.value
EXECUTE PROCEDURE dosomething();
It will only execute the trigger when there's a change in the value field. Therefore when inside the trigger you update calcValue, the trigger is not called again, preventing an infinite "loop".

How to check in Trigger what has been given in the where clause?

I am writing an INSTEAD OF UPDATE trigger and I want to identify what columns has been given to the WHERE clause of the UPDATE statement that triggers the trigger.
For example,
Let's say that we have the table below
table_name
--COL1
--COL2
--COL3
--COL4
I want, when an update is performed
e.g.UPDATE table_name SET COL1=VAL1,COL2=VAL2 WHERE COL3=VAL3
to be able to say in my trigger
CREATE or replace TRIGGER DEVICES_VIEW_TR
INSTEAD OF UPDATE ON DEVICES_VW
BEGIN
IF (COL3 has been given in the where clause) THEN
variable=getValueOf(COL3);
ELSEIF (COL4 has been given in the where clause) THEN
variable=getValueOf(COL4);
END IF;
END;
/
Can this be done?
Thanks
You can use the UPDATING('column name') in your trigger:
-- in INSTEAD OF trigger body:
IF updating('COL1') THEN
-- some operation
END IF;
Check this for an example: Example of using UPDATING
You could use the NEW and OLD pseudorecords and run a comparison of the values
if :NEW.COL3 <> :OLD.COL3 THEN ...
Triggers don't know anything about the statement that invoked them, so you'll have to use some kind of out-of-band signalling, e.g. change your application to set some globals in a database package, or use an application context.

Oracle Trigger on Insert or Delete or Update

Trying to create an Oracle trigger that runs after a table is updated in any way. I've been googling this all morning and came up with this:
CREATE OR REPLACE TRIGGER gb_qty_change
AFTER UPDATE OR INSERT OR DELETE ON F_ITEM_STORE
FOR EACH ROW
DECLARE
v_qty V_AD_ON_HAND%rowtype;
v_isbn TD_ITEM_DESCRIPTION.TD_IDENTIFIER%type;
BEGIN
delete from gb_transaction where gb_tide = :new.ITST_ITEM_TIDE_CODE;
select TD_IDENTIFIER INTO v_isbn from TD_ITEM_DESCRIPTION where TD_TIDE = :new.ITST_ITEM_TIDE_CODE;
select * INTO v_qty from V_AD_ON_HAND where ITST_ITEM_TIDE_CODE = :new.ITST_ITEM_TIDE_CODE;
insert into gb_transaction(gb_tide, gb_isbn, gb_used_on_hand, gb_new_on_hand)
values(:new.ITST_ITEM_TIDE_CODE, v_isbn, v_qty.USED_ON_HAND, v_qty.NEW_ON_HAND);
END;
/
I'm trying to keep it to a single record per TIDE_CODE in the new table.
V_AD_ON_HAND is a view that pulls an inventory count.
gb_transaction is my new table where I'm logging these events
Comparing it to other peoples code it looks like it should run but I'm getting "Warning: Trigger created with compilation errors."
The problem, I believe is with the :new designations for a delete trigger. There is, after all, no NEW value as the record is expunged. You can only access the :OLD values on delete.
if you section the trigger by operation, you can do this.
CREATE OR REPLACE TRIGGER gb_qty_change
AFTER UPDATE OR INSERT OR DELETE ON F_ITEM_STORE
FOR EACH ROW
DECLARE
v_qty V_AD_ON_HAND%rowtype;
v_isbn TD_ITEM_DESCRIPTION.TD_IDENTIFIER%type;
BEGIN
IF INSERTING or UPDATING then
... insert your existing code
ELSE
... do something similar with the :old values for the deleting case
end if;
END;
/
Incidentally, it is generally helpfull if you tell us WHAT the error is, not just that you had one. If compiling via SQL*Plus script, after the forward slash call to compile the trigger after the "end;" statement, add a line that says:
SHOW ERRORS TRIGGER YOUR_TRIGGER_NAME;