"Table is mutating" error occurs when inserting into a table that has an after insert trigger - sql

I'm trying to create an after insert trigger that inserts the details of a purchase made into a purchases log table and, as well, update the total cost column in the purchases table to add GCT of 16.5% to the total. The two tables have these columns:
CREATE TABLE purchases
(
p_Id INTEGER PRIMARY KEY,
product VARCHAR(25) ,
quantity INTEGER ,
unit_Cost FLOAT ,
total_Cost FLOAT
);
CREATE TABLE purchases_log
(
event_Date DATE ,
p_Id INTEGER PRIMARY KEY,
description varchar(75)
);
The trigger I'm trying to use is:
CREATE OR REPLACE TRIGGER PURCHASES_AUDIT
AFTER INSERT ON purchases
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE TAX FLOAT;
BEGIN
INSERT INTO purchases_log
VALUES(SYSDATE,:NEW.p_Id,'INSERT INTO PURCHASES TABLE');
tax := 1.165;
UPDATE purchases SET total_Cost = quantity * unit_Cost;
UPDATE purchases SET total_Cost = total_Cost*tax;
END PURCHASES_AUDIT;
/
however when trying to run an insert on the purchase table oracle gives me this error
ERROR at line 1:
ORA-04091: table PURCHASES is mutating, trigger/function may
not see it
ORA-06512: at "PURCHASES_AUDIT", line 9
ORA-04088: error during execution of trigger 'PURCHASES_AUDIT'
Please Help

Don't update the table on which the trigger is defined.
It sounds like you want a before insert trigger, not an after insert trigger, that modifies the :new pseudo-record. If I understand your intention correctly
CREATE OR REPLACE TRIGGER PURCHASES_AUDIT
BEFORE INSERT ON purchases
FOR EACH ROW
DECLARE
TAX FLOAT;
BEGIN
INSERT INTO purchases_log
VALUES(SYSDATE,:NEW.p_Id,'INSERT INTO PURCHASES TABLE');
tax := 1.165;
:new.total_Cost = :new.quantity * :new.unit_Cost * tax;
END PURCHASES_AUDIT;
As an aside, do you really, really want to use a float rather than a number? Do you fully understand the approximate nature of floating point numbers? I've never come across anyone working with money that wanted to use a float.

As a point of clarification: before triggers allow you to update the :new values, after triggers do not. The :new values if after triggers will always contain the final value.

Related

Trigger for before insert to add in x number of days to a date

I am trying to add x number of days to a variable within a table by deriving another date from the same table.
For example, in my BILLING table, it has 2 dates - BillDate and DueDate.
And so, I am trying to add a trigger before the insertion, such that it will takes in the BillDate and add 30 days to derive the DueDate.
While doing so, I got a bunch of errors, as follows:
dbfiddle
CREATE TABLE BILLING
(
BillDate DATE NOT NULL,
DueDate DATE NULL
);
-- Got ORA-24344: success with compilation error
CREATE TRIGGER duedate_trigger BEFORE INSERT ON BILLING
FOR EACH ROW
begin
set DueDate = :new.DueDate: + 30
end;
-- Got ORA-04098: trigger 'FIDDLE_FBHUOBXMWRPYBBXPIKTW.DUEDATE_TRIGGER' is invalid and failed re-validation
INSERT INTO BILLING
VALUES ((Date '2020-07-23'), NULL);
For the insertion, I have tried removing the NULL, but still I am getting a bunch of errors.
Any ideas?
Also, in the event, if the insertion statement also does includes in the due date too, will this affects the trigger? Trying to cater for 2 scenarios, generate a due date if not give, else if given, check if it is within 30 days from BillDate and update it... (likely I may have overthink/ overestimated that this is doable?)
CREATE OR REPLACE TRIGGER duedate_trigger BEFORE INSERT ON BILLING
FOR EACH ROW
begin
:new.DueDate := :new.BillDate + 30;
end;
INSERT INTO BILLING (BillDate ) values (sysdate);
CREATE OR REPLACE TRIGGER duedate_trigger
BEFORE INSERT ON billing
FOR EACH ROW
DECLARE
v_dueDate_derive NUMBER;
BEGIN
v_dueDate_derive = 30;
:new.DueDate = :new.BillDate + v_dueDate_derive;
END;
Days can be easily added by +, so it should not be the problem.
I believe there may be something wrong with INSERT itself.
Could you try to put INSERT like this?
INSERT INTO BILLING
VALUES (TO_DATE('2020-07-23'), NULL);

What is the right prototype for my SQL code?

I am new in learning SQL and I am trying to apply a trig on those two tables by using SQLcl:
prodc
( PRODC_NAME, DISCOUNTED CHAR(1) DEFAULT 'N',
PK PRIMARY KEY (PRODC_NAME));
peopleOrder
( ORDERID, PRODC_NAME,
PRIMARY KEY (ORDERID, PRODC_NAME),
FOREIGN KEY (PRODC_NAME) REFERENCES PRODC (PRODC_NAME));
I want my trig to do the following:
when I update or insert rows to peopleOrder, the trig should check if a row's product_name has value 'N' in the discounted column that is located in the product table; if it has no 'N' value, it should show an error message.
I tried many ways and the following trig, but when I insert rows, the trig seems to not be working and have no effect on the rows:
CREATE OR REPLACE TRIGGER constraint_1
After UPDATE OR INSERT on peopleOrder
for each row
begin
if not (:new.prodc_name in
(select prodc_name from prodc where discounted = 'N' )) then
RAISE_ERROR(-30001, 'No product can be ordered
!');
END IF;
INSERT INTO peopleOrder VALUES (:new.ORDERID, :new.PRODC_NAME);
END;
/
My insert command is:
INSERT INTO peopleOder (ORDERID, PRODC_NAME)
VALUES (251, 'Puton');
and 'Puton' has value 'N' in the discounted column in the prodc table.
The first thing you need is to understand what a trigger is. It is a piece of code that runs as part of the statement causing it to fire. For this reason you cannot reference the table causing it to fire. Normally that is completely unnecessary anyway. Every row trigger has access to to 2 rows: a copy of the existing data (old) and the resulting data (new). Update having both filled and Insert/Delete just new/old as appropriate for the action. Before statement triggers can modify the new data, after triggers cannot. Those rows allow you to process the columns in the table without DML or select on it. (However you cannot 'scan' the table). At any level a trigger may raise an exception which halts the entire process and the invoking statement. If no exception is raised then processing the invoking proceeds. For a more complete description see PL/SQL Language Reference or the version of Oracle you are running.
In this case your trigger breaks down to a single if
statement.
-- Revised. Orig missed that flag comming from different table.
create or replace trigger constraint_1
before update or insert on peopleOrder
for each row
declare
dis_num integer;
begin
select count(*)
into dis_num
from product
where discounted != 'N'
and product_name = :new.product_name
and rownum < 2;
if dis_num != 0 then
raise_application_error(-20001, 'No discounted product can be ordered!');
end if;
end constraint_1;
-- Orig ==============================================
CREATE OR REPLACE TRIGGER constraint_1
before UPDATE OR INSERT on peopleOrder
for each row
begin
if :new.discounted = 'N' then
RAISE_APPLICATION_ERROR(-20001, 'No discounted product can be ordered')
end if;
END;
/

how to trigger an update on a table row value using a row value from another table in Apex Oracle SQL?

i have two tables; Purchase order:
CREATE TABLE "PURCHASE_ORDER"
( "PO_NUMBER" NUMBER(*,0) NOT NULL ENABLE,
"CUSTOMER_NUMBER" NUMBER(*,0),
"PO_DATE" DATE,
"PRICE" NUMBER(*,0),
"ORDER_QUANTITY" NUMBER,
"STOCK_ID" NUMBER(*,0),
PRIMARY KEY ("PO_NUMBER")
and the bulk stock table:
CREATE TABLE "BULK_STOCK"
( "STOCK_ID" NUMBER(*,0) NOT NULL ENABLE,
"STOCK_DESCRIPTION" VARCHAR2(50),
"STOCK_UNITOF_MEASUREMENT" VARCHAR2(50),
"STOCK_STATUS" VARCHAR2(50),
"FLOOR_ID" NUMBER(*,0),
"STOCK_NAME" VARCHAR2(50),
"BULK_QUANTITY" NUMBER NOT NULL ENABLE,
PRIMARY KEY ("STOCK_ID")
I am creating a trigger that updates the BULK_QUANTITY in the bulk stock table when purchase table is inserted with values:
create or replace trigger "UPDATE_ON_PURCHASE"
BEFORE
insert or update or delete on "PURCHASE_ORDER"
for each row
begin
UPDATE bulk_stock
SET BULK_QUANTITY =BULK_QUANTITY-:old.ORDER_QUANTITY
WHERE STOCK_ID=:old.STOCK_ID;
end;
when i run this form
Nothing changes in the bulk stock table
but if I hard code it
create or replace trigger "UPDATE_ON_PURCHASE"
BEFORE
insert or update or delete on "PURCHASE_ORDER"
for each row
begin
UPDATE bulk_stock
SET BULK_QUANTITY =BULK_QUANTITY- 20 //the 20 would be the inserted ORDER_QUANTITY
WHERE STOCK_ID= 12 // 12 would be the stock_ID
it works.
This is done in Oracle Apex software. I need help with the trigger. Many thanks
end;
You're using the :OLD values in your trigger which seems problematic. On an INSERT the :OLD values are all NULL. So in the case of an INSERT, at the very least, it seems you'd want to use the :NEW values.
On an UPDATE the :OLD values contain the pre-update values. I don't know how your stock table is being maintained but it seems to me that you'd want to add the :OLD values back into stock then remove the :NEW values from stock, assuming that both the ORDER_QUANTITY and STOCK_ID can change.
When you're doing a DELETE the :OLD values contain the pre-deletion values, but the :NEW values are all NULL (makes sense, if you think about it). So in the case of a deletion it would seem that you'd want to use the :OLD values. However, if you're deleting a PO do you really want to adjust the stock? I'd think you'd need some type of status on the order to let you know if it's been fulfilled or cancelled or whatever, and only add the stock back into the bulk stock table if the order was never fulfilled.
In any case, one way to re-write your trigger would be:
create or replace trigger UPDATE_ON_PURCHASE
BEFORE insert or update or delete on PURCHASE_ORDER
for each row
begin
IF INSERTING THEN
UPDATE bulk_stock
SET BULK_QUANTITY = BULK_QUANTITY - :NEW.ORDER_QUANTITY
WHERE STOCK_ID = :NEW.STOCK_ID;
ELSIF UPDATING THEN
UPDATE BULK_STOCK
SET BULK_QUANTITY = BULK_QUANTITY + :OLD.ORDER_QUANTITY
WHERE STOCK_ID = :OLD.STOCK_ID;
UPDATE BULK_STOCK
SET BULK_QUANTITY = BULK_QUANTITY - :NEW.ORDER_QUANTITY
WHERE STOCK_ID = :NEW.STOCK_ID;
ELSIF DELETING THEN
UPDATE BULK_STOCK
SET BULK_QUANTITY = BULK_QUANTITY + :OLD.ORDER_QUANTITY
WHERE STOCK_ID = :OLD.STOCK_ID;
END IF;
end;
I'm not sure that this logic is really what you wanted as I don't understand completely what you're trying to do, particularly in the DELETE case, so take it as an example and apply whatever logic your situation calls for.
I'll also say that I think putting this logic in a trigger is a bad choice. Business logic such as this should not be implemented in a trigger - better to put it into a procedure and call the procedure when needed. Putting business logic into triggers can be problematic.
Best of luck.

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;
/

How to get date from two tables using trigger before insert into third table?

I'm trying to create a trigger TrgDisAmount to read PROD_NAME and PRICE from PRODUCT table and to calculate the DISCOUNT and AMOUNT of SALES Table for every new row inserted with the values of SERIAL and PCODE.
These are the tables information
Table SALES (SERIAL, PCODE, PROD_NAME, PRICE, DISCOUNT, AMOUNT)
Table PRODUCT (PCODE, PROD_NAME, PROD_CAT, PRICE)
Table DISCOUNT (PROD_CAT, DISCOUNT_RATE)
Note:
Enter the values for SERIAL and PCODE only, the remaining columns should entered by the trigger TrgDisAmount only
Get the DISCOUNT_RATE using Function GetDiscount.
I have already created GetDiscount function to get DISCOUNT_RATE from table DISCOUNT.
This is my try:
create or replace trigger TrgDisAmount
before insert on SALES for each row
begin
if :new.PCODE = :old.PRODUCT.PCODE then
:new.PROD_NAME := :old.PRODUCT.PROD_NAME;
:new.PRICE := :old.PRODUCT.PRICE;
:new.DISCOUNT := :old.product.PRICE / (GetDiscount(:old.PRODUCT.PROD_CAT));
:new.AMOUNT := :new.PRICE - :new.DISCOUNT;
end if;
insert into SALES columns (PROD_NAME, PRICE, DISCOUNT, AMOUNT)
values (:new.PROD_NAME, :new.PRICE, :new.DISCOUNT, :new.AMOUNT);
end;
/
When I run that block it shows me this error:
PLS-00049: bad bind variable 'OLD.PRODUCT'
I use Table_Name.Column_name to reach to the specific column. Is it legal to do it?
Sample output should be like this:
SQL> insert into sales values (1,'MB-101',null, null, null, null);
Result in SALES table:
SERIAL : 1,
PCODE : MB-101,
PROD_NAME : IPHONE 6+,
PRICE : 250 ,
DISCOUNT : 25,
AMOUNT : 225
When you create a before insert trigger, you only have to set the :new values. You don't actually insert into the table. That is the next step in the processing.
Also, in an insert trigger, only refer to the :new values, not the :old. There is no :old value, which is the specific error you are getting.
Then, you need to do a query to get the relevant information from the other tables. The result should look something like this:
create or replace trigger TrgDisAmount
before insert on SALES
for each row
begin
select p.price / d.discount into :new.discount
from product p join
discount d
on p.prod_cat = d.prod_cat
where :new.pcode = p.pcode;
:new.AMOUNT := :new.PRICE - :new.DISCOUNT;
end;
The arithmetic for defining the discount looks very suspicious. A 10% discount would be represented as 1.11 using this methodology. However, this is the arithmetic in your question and you provide no sample data to suggest any other method.
Can you try changing , the way you are calling your function.
As mentioned by Gordon, :old shouldn't be used in this case
CREATE or replace trigger TrgDisAmount
Before insert
on SALES
for each row
DECLARE
v_prod_cat varchar2(20);
BEGIN
select PROD_NAME into :NEW.PROD_NAME
from PRODUCT
where PRODUCT.PCODE = :NEW.PCODE;
select PRICE into :NEW.PRICE
from PRODUCT
where PRODUCT.PCODE = :NEW.PCODE;
select PROD_CAT into v_prod_cat from discount where PROD_CAT
in( select PROD_CAT from PRODUCT where PCODE = :NEW.PCODE);
select GetDiscount(v_prod_cat) into :NEW.discount from dual;
:new.AMOUNT := :new.PRICE - :new.DISCOUNT;
END;
/
Trigger created.
SQL> show err;
No errors.
insert into sales values(701,1,NULL,NULL,NULL,NULL);