ORA-04091 tableT is mutating, trigger/function may not see it - sql

create or replace trigger discount
after insert or update
on product
for each row
declare
newcost number;
quant number;
id number;
BEGIN
id:= :NEW.pid;
quant:= (30/100)*:NEW.req_quant;
newcost:= :NEW.pcost - (10/100)*:NEW.pcost;
if :NEW.act_quant>quant then
update product set pcost=newcost where pid=id;
end if;
dbms_output.put_line('success');
end;
while writing query insert into product values(107,20,20,1000);
i get an this error ORA-04091 tableT is mutating, trigger/function may not see it
my table is
CREATE TABLE "PRODUCT"
( "PID" NUMBER(5,0),
"ACT_QUANT" NUMBER(5,0),
"REQ_QUANT" NUMBER(5,0),
"PCOST" NUMBER(10,0),
PRIMARY KEY ("PID") ENABLE
)
After inserting or updating trigger must check that whether the actualquantity of product is greater than 30% of requaired quantity if it is true we need to give discount of 10% on that product

Don't literally update table which is just being modified (which caused trigger to fire) because trigger can't see it; that's the good, old mutating table error.
if :NEW.act_quant > quant then
:new.pcost := newcost; --> use this
-- update product set pcost=newcost where pid=id; --> not this
end if;
Though, the whole trigger can be shortened to
create or replace trigger discount
after insert or update on product
for each row
begin
:new.pcost := case when :new.act_quant > (30/100) * :new.req_quant then 0.9 * :new.pcost
else :new.pcost
end;
end;

Related

TRIGGER AUTONOMOS TRANSACTION fails each time when trigger needs to run with different ORA errors

I try to create a custom table, which is created properly. The last 2 fields are matching the type from Orders table. The trigger's idea is when a certain date field is changed to INSERT this into the new table. The trigger is created in Oracle correctly, but once the field in question changes I get ORA errors (below) and cannot find out why they appear.
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at "shc.trigger_table_1", line 20
ORA-04088: error during execution of trigger 'shc.trigger_table_1'
I tried changing the datatypes, tried introducing an EXCEPTION, tried removing the :old.info23 check.
Here is the table I am creating:
CREATE TABLE table1(
id number generated by default as identity,
table_name varchar2(20),
field_changed varchar2(20),
old_value date,
new_value date,
changed_by varchar(20),
date_of_change date,
orderRef varchar(50),
Ref varchar2(256));
Here is the problematic trigger:
CREATE OR REPLACE TRIGGER trigger_table_1
AFTER UPDATE ON consignment
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
DECLARE
my_AEXTERNAUFTRAGSNR varchar2(50);
my_AUFTRAGSREFERENZ2 varchar2(256);
BEGIN
IF :OLD.ANKUNFTBELDATUMVON <> :NEW.ANKUNFTBELDATUMVON AND :old.info23='I' THEN
Select AEXTERNAUFTRAGSNR, AUFTRAGSREFERENZ2
Into my_AEXTERNAUFTRAGSNR, my_AUFTRAGSREFERENZ2
From orders
Where orders.nr = :old.ordernr;
insert into table1(table_name, field_changed, old_value, new_value, changed_by, date_of_change, orderRef, Ref)
values ('consignment', 'ANKUNFTBELDATUMVON', :OLD.ANKUNFTBELDATUMVON, :NEW.ANKUNFTBELDATUMVON, sys_context('userenv','OS_USER'), SYSDATE, my_AEXTERNAUFTRAGSNR, my_AUFTRAGSREFERENZ2);
END IF;
END;
END;
The immediate problem is that you don't commit your autonomous transaction. If you change it to commit at the end:
...
END IF;
END;
commit;
END;
/
then it will work - fiddle. (You don't need the nested block, but it doesn't stop it working... and you could do insert ... select ... to avoid needing any local variables...)
But as you can see from the db<>fiddle result, if the update on consignment is rolled back then the insert into table1 is retained, since that was committed independently.
If you remove the PRAGMA AUTONOMOUS_TRANSACTION; instead then that won't happen. Then you won't need to, and indeed can't, commit within the trigger. You would just need:
CREATE OR REPLACE TRIGGER trigger_table_1
AFTER UPDATE ON consignment
FOR EACH ROW
DECLARE
my_AEXTERNAUFTRAGSNR varchar2(50);
my_AUFTRAGSREFERENZ2 varchar2(256);
BEGIN
IF :OLD.ANKUNFTBELDATUMVON <> :NEW.ANKUNFTBELDATUMVON AND :old.info23='I' THEN
Select AEXTERNAUFTRAGSNR, AUFTRAGSREFERENZ2
Into my_AEXTERNAUFTRAGSNR, my_AUFTRAGSREFERENZ2
From orders
Where orders.nr = :old.ordernr;
insert into table1(table_name, field_changed, old_value, new_value, changed_by, date_of_change, orderRef, Ref)
values ('consignment', 'ANKUNFTBELDATUMVON', :OLD.ANKUNFTBELDATUMVON, :NEW.ANKUNFTBELDATUMVON, sys_context('userenv','OS_USER'), SYSDATE, my_AEXTERNAUFTRAGSNR, my_AUFTRAGSREFERENZ2);
END IF;
END;
/
or with insert ... select ...:
CREATE OR REPLACE TRIGGER trigger_table_1
AFTER UPDATE ON consignment
FOR EACH ROW
BEGIN
IF :OLD.ANKUNFTBELDATUMVON <> :NEW.ANKUNFTBELDATUMVON AND :old.info23='I' THEN
insert into table1(table_name, field_changed,
old_value, new_value,
changed_by, date_of_change,
orderRef, Ref)
select 'consignment', 'ANKUNFTBELDATUMVON',
:OLD.ANKUNFTBELDATUMVON, :NEW.ANKUNFTBELDATUMVON,
sys_context('userenv','OS_USER'), SYSDATE,
o.AEXTERNAUFTRAGSNR, o.AUFTRAGSREFERENZ2
from orders o
where o.nr = :old.ordernr;
END IF;
END;
/
fiddle
Incidentally, if you only want the trigger to fire if a specific column (or columns) was included in an update statement then you could do:
AFTER UPDATE OF ANKUNFTBELDATUMVON ON consignment
but you would still need to check it had actually changed. Your current check doesn't cover ANKUNFTBELDATUMVON being update to or from null, which is only potentially an issue if the column is nullable.

how do i verify a row before and after update

Need to code in a way that if a buyer buys quantity more than inventory console should display a message that you can buy only available quantity as of now, rest of the quantity will be updated soon, also if the inventory is sufficient to buy it should display please go ahead
In short, "function, procedure to verify the quantity on hand before insert a row in table order_line and to update also the quantity on hand of table inventory"
CREATE OR REPLACE PACKAGE order_package IS
global_inv_id NUMBER (6);
global_quantity NUMBER (6);
PROCEDURE create_new_order(current_c_id NUMBER,
current_meth_pmt VARCHAR2, current_os_id NUMBER);
PROCEDURE create_new_order_line(current_o_id NUMBER);
END;
/
CREATE OR REPLACE PACKAGE BODY order_package IS
PROCEDURE create_new_order(current_c_id NUMBER,
current_meth_pmt VARCHAR2, current_os_id NUMBER) AS
current_o_id NUMBER;
BEGIN
SELECT order_seq.NEXTVAL
INTO current_o_id
FROM dual;
INSERT INTO orders
VALUES(current_o_id, sysdate,current_meth_pmt, current_c_id,
current_os_id);
COMMIT;
create_new_order_line(current_o_id);
END create_new_order;
PROCEDURE create_new_order_line(current_o_id NUMBER)AS
BEGIN
INSERT INTO order_line
VALUES(current_o_id,global_inv_id, global_quantity);
COMMIT;
END create_new_order_line;
END;
/
You don't show your inventory table and your procedures don't seem to have a quantity ordered value, so some of this is conjecture. What you might want to do is first update that table and use the RETURNING INTO clause to get the updated inventory.
UPDATE inventory SET global_quantity = global_quantity - order_quantity
WHERE global_inv_id = current_c_id
RETURNING global_quantity INTO l_global_quantity;
IF l_global_quantity < 0 THEN
ROLLBACK;
raise_application_error( -20001, 'You ordered too much!' );
ELSE
[... create order goes here ...]
END IF;
Is current_c_id is the item being ordered? This will raise an exception, which should be caught by whatever is calling your procedure. How you display the error to the user will depend on the application layer being used.

Updating the record of same table when new record is inserted or updated in oracle

I am new to learning Oracle. I have a task in which I need to update value of any previous record if new record contains its reference.
Table structure is as below :
Review_Table
(review_id number pk,
review_name varchar2,
previous_review number null,
followup_review number null
)
Here previous_review and followup_review columns are objects of same table i.e Review_table.
Now consider we have two records in Review_table A and B, A does not have any previous or followup review. When user creates/updates the record B and he selects record A as previous record, then we want to automatically update (via trigger) the value of A record's followup review with B's Review ID.
I have tried writing following trigger
create or replace trigger "REVIEW_T1"
AFTER insert or update on "REVIEW_TABLE"
for each row
begin
update REVIEW_TABLE
set review_follow_up_review = :new.REVIEW_ID
where REVIEW_ID = :new.REVIEW_PREVIOUS_REVIEW;
end;
But I am getting error as : REVIEW_TABLE is mutating, trigger/function may not see it ORA-06512
I have tried searching everything but was unable to find any solution for it
TL;DR: No trigger, no mutating. Do not use trigger to change another row in the same table.
I absolutely agree with #StevenFeuerstein's comment:
I also suggest not using a trigger at all. Instead, create a package that contains two procedures, one to insert into table, one to update. And within these procedures, implement the above logic. Then make sure that the only way developers and apps can modify the table is through this package (don't grant privs on the table, only execute on the package).
Take a look at the following example.
Prepare the schema:
create table reviews (
id number primary key,
name varchar2 (32),
previous number,
followup number
);
create or replace procedure createNextReview (name varchar2, lastId number := null) is
lastReview reviews%rowtype;
nextReview reviews%rowtype;
function getLastReview (lastId number) return reviews%rowtype is
begin
for ret in (
select * from reviews where id = lastId
for update
) loop return ret; end loop;
raise_application_error (-20000, 'last review does not exist');
end;
procedure insertReview (nextReview reviews%rowtype) is
begin
insert into reviews values nextReview;
exception when others then
raise_application_error (-20000, 'cannot insert next review');
end;
procedure setFollowUp (nextId number, lastId number) is
begin
update reviews set
followup = nextId
where id = lastId
;
exception when others then
raise_application_error (-20000, 'cannot update last review');
end;
begin
if lastId is not null then
lastReview := getLastReview (lastId);
end if;
nextReview.id := coalesce (lastReview.id, 0)+1;
nextReview.name := name;
nextReview.previous := lastId;
insertReview (nextReview);
if lastReview.Id is not null then
setFollowUp (nextReview.id, lastReview.Id);
end if;
exception when others then
dbms_output.put_line (
'createNextReview: '||sqlerrm||chr(10)||dbms_utility.format_error_backtrace ()
);
end;
/
Execute:
exec createNextReview ('first review')
exec createNextReview ('next review', 1)
See the outcome of work done:
select * from reviews;
ID NAME PREVIOUS FOLLOWUP
---------- ---------------- ---------- ----------
1 first review 2
2 next review 1
First you need to read about triggers, mutating table error and compound triggers: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS2005
Your trigger is AFTER UPDATE OR INSERT. Means if you run UPDATE OR INSERT statements on this table, the trigger will fire. But you are trying to update the same table again inside your trigger, which is compl. wrong.
I think you can fix this by rewriting this as a before trigger, rather than an after trigger.

Update Trigger PL/SQL in Oracle [duplicate]

I create the following tables:
create table products
(
code varchar(9),
group_code varchar(9),
price number,
CONSTRAINT pk_code PRIMARY KEY (code)
);
I created a trigger , for that the price per group of products does not exceed 100 dollars:
create or replace trigger nomore100
before insert or update on products
for each row
declare
cursor c_total is
select group_code, sum(price) as total_price
from products
where group_code=:new.group_code
group by group_code;
v_total c_total%rowtype;
begin
for v_total in c_total loop
if v_total.total_price+:new.price > 100 then
raise_application_error(-20150,'No more than 100 dollars');
end if;
end loop;
end nomore100;
/
The trigger works for inserts, but when i try to do a update:
update products set price=120 where code='PX1';
Oracle return:
"table %s.%s is mutating, trigger/function may not see it"
Thanks for any suggestions or answers, have a nice day!

Oracle Sql trigger before update of column

I want to create an update trigger for a table called purchases that will update the total cost field and also log a record in the purchases log table to indicate which purchase was updated.
I don't have much experience with creating triggers but so far this is what i've got
CREATE OR REPLACE TRIGGER Update_Purchase_Audit BEFORE
UPDATE ON Purchases
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
WHEN(NEW.p_id>0)
DECLARE total_cost INTEGER;
BEGIN
set total_cost := (:NEW.quantity * :NEW.unitcost);
:NEW.total_cost:=total_cost*0.165;
INSERT INTO Purchase_Log(eventdate, p_id, descrip)
VALUES(SYSDATE, :NEW.p_id, :NEW.product);
END;
/
I get this error when creating the trigger:
trigger created with compilation errors. When I run show errors; I get ORA-00922: missing or invalid option
Any suggestion? Thanks
CREATE OR REPLACE TRIGGER Update_Purchase_Audit
BEFORE UPDATE ON Purchases
FOR EACH ROW
WHEN (NEW.p_id > 0)
DECLARE
total_cost number;
BEGIN
total_cost := :NEW.quantity * :NEW.unitcost;
:NEW.total_cost := total_cost * 0.165; --<< this is ok BEFORE update
-->> Do you have table relations here? THis may need to go AFTER update
-- in separate trigger
INSERT INTO Purchase_Log (eventdate, p_id, descrip)
VALUES(SYSDATE, :NEW.p_id, :NEW.product);
END;
/