I have a command to execute inside oracle triggers after a modification of a table.
I need this command to run once (even if there is 100 rows updated), and only when there is rows updated.
FOR EACH ROW allow to be sure to send the command only when there is rows updated, so how could I stop its execution after the first loop ?
It looks you're going to use compound trigger. In for each row section you need to collect rowids to update and in after statement run whole update
Use global package variable.
1) Reset it to null in before update statement trigger.
2) Save values/inc counter in for each row trigger
3) do final check in after update statement trigger and fire your logic inly once regardless number of affected rows
Related
CREATE DEFINER=`belito`#`%` TRIGGER `sumupdate` AFTER INSERT ON `stavkaotpremnice` FOR EACH ROW UPDATE otpremnica a
SET a.ukupno =
(SELECT SUM(ukupno)
FROM stavkaotpremnice
WHERE brojotpremnice = a.brojotpremnice)
WHERE a.brojotpremnice = NEW.brojotpremnice
Im struggling doing it, made some searches but didnt go nowhere.
Is this working for you in MySql? If you tried doing this in Oracle using an "AFTER INSERT .... FOR EACH ROW" trigger, you would run into a "Table is mutating error" during the actual insert into stavkaotpremnice. This is because you cannot query the table being inserted into inside of a row-level trigger on that same table. You can do so in a statement-level trigger, but then you lose access to the :new values. A sort of workaround is to use a compound trigger where you can set a variable to the :new value in the "AFTER EACH ROW" section, and then use that variable in your update in the "AFTER STATEMENT" section, but I would highly discourage doing so because that will only work for single row inserts, and you'll get unexpected results if there's ever an insert statement that inserts multiple rows with different values for stavkaotpremnice.brojotpremnice.
I think the best approach here is not to do this in a trigger. Instead, either issue the otpremnica update in your application code and/or via a batch update some time later.
So I am trying to use triggers to basically set some rules.. If anyone has an ID number lower than 3, he will have to pay only 100 dollars, but if someone has an ID above that, he will have to pay more. I did some research and have been told to use triggers and that triggers are very useful when fetching multiple rows. So I tried doing that but it didn't work. Basically the trigger gets created but then when i try to add values, I get the following error:-
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "S.PRICTICKET", line 6
ORA-04088: error during execution of trigger 'S.PRICTICKET'
here is what i did to make the trigger:-
CREATE OR REPLACE TRIGGER PRICTICKET BEFORE INSERT OR UPDATE OR DELETE ON PAYS FOR EACH ROW ENABLE
DECLARE
V_PRICE PAYS.PRICE%TYPE;
V_ID PAYS.ID%TYPE;
V_NAME PAYS.NAME%TYPE;
BEGIN
SELECT ID,NAME INTO V_ID,V_NAME FROM PAYS;
IF INSERTING AND V_ID<3 THEN
V_PRICE:=100;
INSERT INTO PAYS(ID,NAME,PRICE) VALUES (V_ID,V_NAME,V_PRICE);
ELSIF INSERTING AND V_ID>=3 THEN
V_PRICE:=130;
INSERT INTO PAYS(ID,NAME,PRICE) VALUES (V_ID,V_NAME,V_PRICE);
END IF;
END;
and the thing is, when i execute this code, i actually do get a message saying the trigger has been compiled. but when when i try to insert values into the table by using the following code, i get the error message I mentioned above.
INSERT INTO PAYS(ID,NAME) VALUES (19,'SS');
You're getting the error you specified, ORA-01422, because you're returning more than one row with the following SELECT:
SELECT ID,NAME INTO V_ID,V_NAME FROM PAYS;
You need to restrict the result set. For example, I'll use the :NEW psuedorecord to grab the row's new ID value, which if unique, will restrict the SELECT to one row:
SELECT ID,NAME INTO V_ID,V_NAME FROM PAYS WHERE ID = :NEW.ID;
Here is the Oracle docs on using triggers: https://docs.oracle.com/database/121/TDDDG/tdddg_triggers.htm#TDDDG99934
However, I believe your trigger has other issues, please see my comments and we can discuss.
EDIT: Based on our discussion.
ORA-04088: error during execution of trigger
Using INSERT inside a BEFORE INSERT trigger on the same table will create an infinite loop. Please consider using an AFTER INSERT and change your INSERTS to UPDATES, or an INSTEAD OF INSERT.
Additionally, remove DELETE from the trigger definition. That makes no sense in this context.
Let's begin clearing up a few things. You were told "triggers are very useful when fetching multiple rows" this is, as a general rule and without additional context, false. There are 4 types of DML triggers:
Before Statement - fires 1 time for the statement regardless of the number of rows processed.
Before Row - fires once for each row processed during the statement before old and new values are merged into a single set of values. At this point you are allowed to change the values in the columns.
After Row - fires once for row processed during the statement after merging old and new values into a single set of values. At this point you cannot change the column values.
After statement - fires once for the statement regardless of the number of rows processed.
Keep in mind that the trigger is effectively part of the statement.
A trigger can be fired for Insert, Update, or Delete. But, there is no need to fire on each. In this case as suggested, remove the Delete. But also the Update as your trigger is not doing anything with it. (NOTE: there are compound triggers, but they contain segments for each of the above).
In general a trigger cannot reference the table that it is fired upon. See error ORA-04091.
If you're firing a trigger on an Insert it cannot do an insert into that same table (also see ORA-04091) and even if you get around that the Insert would fire the trigger, creating a recursive and perhaps a never ending loop - that would happen here.
Use :New.column_name and :Old.column_name as appropriate to refer to column values. Do not attempt to select them.
Since you are attempting to determine the value of a column you must use a Before trigger.
So applying this to your trigger the result becomes:
CREATE OR REPLACE TRIGGER PRICTICKET
BEFORE INSERT ON PAYS
FOR EACH ROW ENABLE
BEGIN
if :new.id is not null
if :new.ID<3 then
:new.Price :=100;
else
:new.Price := 130;
end if ;
else
null; -- what should happen here?
end if ;
END PRICTICKET ;
I'm quite a newbie in PL/SQL and I'm trying to do quite complex data integrity checks via triggers.
I've already understood how to avoid problems when calling a table inside a trigger that is used on the same table (via a temporary external table) but now I'm facing a really mind-blowing problem : I thought that ":NEW" was referencing the value in my table AFTER an update but things don't look that simple... It is the new value SET by the update or insert... which looks to be NULL if nothing has been specified, even if the corresponding field value is NOT NULL after the update... wich is driving me crazy.
My trigger is set when inserting or updating several variables :
CREATE OR REPLACE TRIGGER TRG_INS_UP_INSTRUMENT_EVENT
AFTER INSERT OR UPDATE OF EVENT_ID, DATE_BEGIN,DATE_END,INSTR_ID,TYPE_EVENT_ID ON AIS_INSTRUMENT_EVENT
But now... If there already is a line with non-null fields and I do an
UPDATE AIS_INSTRUMENT_EVENT SET INSTR_ID='642' WHERE EVENT_ID='6479'
I actually get a ":NEW.DATE_BEGIN" which is NULL... event thought nor the older or newer values are NULL (because I just didn't update it).
How can I distinguish - in my trigger - the case when the DATE_BEGIN is updated and SET voluntary to NULL from the case in which nothing has been specified (and this field must thus remain the same but not necessarily NULL...). I have to many possible combination to check one by one...
Thanks in advance for your help!
What you are saying is not true. :new contains the full row regardless whether the column is referenced in the UPDATE statement:
CREATE TABLE test (test INTEGER, last_changed DATE);
CREATE OR REPLACE TRIGGER TRG_INS_UP_TEST
AFTER INSERT OR UPDATE OF test, last_changed ON test
FOR EACH ROW
BEGIN
dbms_output.put_line('LAST CHANGED IS ' || :new.last_changed);
END;
INSERT INTO test (test, last_changed) VALUES (1, SYSDATE);
COMMIT;
UPDATE test SET test = test + 1;
DBMS Output:
LAST CHANGED IS 01.09.17
To achieve what you want the mechanism works slightly different. You have to look at two different use cases:
1.) You want the trigger not to fire unless a certain column is mentioned. This use cases is by the reference in the trigger declaration (INSERT OR UDATE OF "column_name"). If the INSERT/UPDATE statement only affects columns that are not mentioned the trigger will not fire.
2.) You want the trigger not to fire unless a certain row is modified. So you want the trigger to only if fire is a value has actually changed. This is done by the WHEN restriction of the trigger. It is usually used in conjunction with DECODE, like so:
CREATE OR REPLACE TRIGGER TRG_INS_UP_TEST
AFTER INSERT OR UPDATE OF test, last_changed ON test
FOR EACH ROW
WHEN (DECODE(new.test,old.test,0,1)=1 OR DECODE(new. last_changed,old. last_changed,0,1)=1)
BEGIN
...
END;
So to answer your original question: If you want to the trigger too only fire in cases where the column DATE_BEGIN is set to NULL you will have to declare your trigger using both approaches
CREATE OR REPLACE TRIGGER TRG_INS_UP_INSTRUMENT_EVENT
AFTER INSERT OR UPDATE OF DATE_BEGIN ON AIS_INSTRUMENT_EVENT
FOR EACH ROW
WHEN (DECODE(new.DATE_BEGIN,old. DATE_BEGIN,0,1)=1 AND new.DATE_BEGIN IS NULL)
The limitation to certain columns ("INSERT OR UPDATE OF DATE_BEGIN") is not strictly necessary but it is good practice since it improves performance since it excludes the trigger from firing at all.
Sorry I think I made a to quick conclusion... The bug was mine. I've tested on a "Toy" table and, indeed, the :NEW was not null, even when not set by the UPDATE. I found the bug in the meantime. All this is too new to me ;-).
Sorry for disturbing.
In Oracle Forms 10g, I have the following code in WHEN-VALIDATE-RECORD trigger.
if(some_condition > 0) then
message('test');
RAISE FORM_TRIGGER_FAILURE;
end if;
Problem is message('test'); appears multiple times. How can I make sure it appears only once.
The trigger WHEN-VALIDATE-RECORD will go off for the record that needs to be validated after leaving the record or pressing commit.
In your case I assume the message appears after a commit and you changed all your rows or at least more then one in for example the post-query trigger.
Because more then one row is changed the trigger will fire for all of these rows and you will get the message multiple times.
Try just after you query your records without changing anything to commit.
It should say there is nothing changed to commit. If it just commit for example 10 rows then this is your problem.
I have a table A into which I am inserting data. Then some calculation is being done updating the same table A.
I want to fire a trigger, which calls a Procedure A after the completion of data insertion ( after insert and update ).
How do I do this?
Is there any other way to do it automatically... Or do I have to run Procedure A manualy after the completion of data insertion in table A.
More simply, I would like to know how to fire a trigger after inserting a few rows and a commit, i.e. not for each row.
You can define your trigger to be fired for each row or for each statement (FOR EACH ROW option).
If I understood you right, you would like to fire the trigger after a bunch of statements? Don't think you can. Even if you can, I would rather not do it. They scatter your program flow / logic and make it harder to understand later how your software works.
Regards
If I understand your question correctly, you want the trigger to fire after you completed your transaction consisting of several insert/update statements? If that is the case, I think you should consider calling your Procedure A in your program flow right after the insert/update operations are done.
In other words: A trigger would only be useful, if it should be called for each row or for each statement.
Add one column to your table: e.g "FINAL_ACTION". Leave this column untouched untill your anticipated final action. Then have your trigger get fired only with this clause:
REFERENCING NEW AS NEWREC OLD AS OLDREC
FOR EACH ROW
WHEN (NEWREC.FINAL_ACTION <> OLDREC.FINAL_ACTION)
DECLARE
--YOUR DECLARATIONS
BEGIN
--DO SOMETHING
END;