Oracle SQL Trigger mutating when implemeneted - sql

Having trouble with this trigger when it runs suring a insert or update operation. The trigger gets created without errors though. The objective is to check if the invoice_total is bigger than the total of payment_total + credit_total. Any help would be much appreciated:
Create or Replace Trigger invoices_before_update_payment
Before Insert or Update On invoices
For Each Row
Declare
InvTotal Number;
t_payment Number;
t_credit Number;
Begin
select invoice_total, payment_total, credit_total
Into
InvTotal, t_payment, t_credit
From invoices
Where invoice_id = :New.invoice_id;
DBMS_OUTPUT.PUT_LINE(InvTotal);
If (InvTotal) < (t_payment + t_credit)
Then
Raise_application_error(-20005, 'Invoice total is larger than the credit total and payment total');
End if;
End;
/

You can't select from the table that a trigger is firing for, else you get this error.
Why not simply use the :new values in your trigger?
BEGIN
IF :new.invoice_total > :new.payment_total + :new.credit_total THEN
I reversed the relational operator based on the error message semantics.

Related

Oracle Trigger - Condition before insert

I don't know what my problem when created new trigger.
Is my syntax correct? Thanks! Console Logging pane
p/s: This my console display when I try to insert values
CREATE OR REPLACE TRIGGER EX03_3
BEFORE INSERT ON HR.CHITIETDATHANG
FOR EACH ROW
DECLARE
TONGHANG NUMBER; -- Total Items
HANGHIENCO NUMBER; -- Items present
HANGDABAN NUMBER; -- Items was sales.
BEGIN
-- Get total Items
SELECT SUM(MH.SOLUONG) INTO TONGHANG
FROM HR.MATHANG MH;
-- Get total Items was sales
SELECT SUM(CTDH.SOLUONG) INTO HANGDABAN
FROM HR.CHITIETDATHANG CTDH;
-- Items present
HANGHIENCO := TONGHANG - HANGDABAN;
IF(HANGHIENCO >= HANGDABAN) THEN
HANGHIENCO := HANGHIENCO-1;
INSERT INTO HR.CHITIETDATHANG VALUES(:NEW.SOHOADON,:NEW.MAHANG,
:NEW.GIABAN,:NEW.SOLUONG,:NEW.MUCGIAMGIA);
ROLLBACK;
END IF;
NULL;
END;
Seem two critical mistakes
1) trigger tries to insert into HR.CHITIETDATHANG in the body of insert trigger of HR.CHITIETDATHANG.
2) to use rollback after an insert statement is useless.
Note : I can see nothing relevant to make raise no_data_found in those select statements. EX03_3 and EX04_4 are confused, as Kaushik Nayak says.

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

Oracle SQL Creating Trigger to Increment a Sequence Number For Each Row

I'm trying to create a trigger but I have learned I can not design it as in my first attempt, which I'm showing below. This will cause a 'mutating table' error due to selecting from the table as it is being modified. It actually didn't cause this error when inserting only one record at a time, but when I insert multiple records at once it does.
The purpose of the trigger is to count the number of records in the table where the customer is equal to the customer about to be inserted, and to set the new order_num value as count+1. I also have a public key value set by the trigger which draws from a sequence. This part works ok once I remove the order_num part of the trigger and the associated SELECT. How can I achieve what I am trying to do here? Thanks in advance.
CREATE OR REPLACE TRIGGER t_trg
BEFORE INSERT ON t
FOR EACH ROW
DECLARE
rec_count NUMBER(2,0);
BEGIN
SELECT COUNT(*) INTO rec_count
FROM t
WHERE customer_id = :NEW.customer_id;
:NEW.order_num:= rec_count+1;
:NEW.order_pk_id:= table_seq.NEXTVAL;
END;
Two triggers and temp table approach can provide solution to you seek, preventing mutating table error. However performance will most likely suffer.
create global temporary table cust_temp(customer_id number, cust_cnt number);
create or replace trigger t_trig1
before insert on t
declare
begin
insert into cust_temp select customer_id, count(*) from t group by customer_id;
end;
/
CREATE OR REPLACE TRIGGER t_trg2
BEFORE INSERT ON t
FOR EACH ROW
DECLARE
rec_count number;
BEGIN
BEGIN
SELECT cust_cnt INTO rec_count
FROM cust_temp
WHERE customer_id = :NEW.customer_id;
EXCEPTION when no_data_found then rec_count := 0;
END;
:NEW.order_num:= rec_count+1;
:NEW.order_pk_id:= table_seq.NEXTVAL;
update cust_temp set cust_cnt = rec_count + 1
where customer_id = :NEW.customer_id;
END;
/

update table pl/sql

I would like to update the following table using pl/sql function, the thing is I managed to write a trigger code but I want to re-write it using 'function' instead.
I would like for Customer 5 to increase their order from 30 to 200. and Enable the user to type in:
1) The number 5 for customer_ID and
2) 200 for the updated quantity.
and print out the total quantity for customer 5 before and after the update.
Create table sales (customer_ID number(10), product_ID number(10), quantity number(10));
INSERT INTO sales (customer_ID, product_ID, quantity) Values(3,1,23);
INSERT INTO sales (customer_ID, product_ID, quantity) Values(1,2,34);
INSERT INTO sales (customer_ID, product_ID, quantity) Values(1,3,654);
INSERT INTO sales (customer_ID, product_ID, quantity) Values(3,7,32);
INSERT INTO sales (customer_ID, product_ID, quantity) Values(4,3,23);
INSERT INTO sales (customer_ID, product_ID, quantity) Values(3,3,111);
INSERT INTO sales (customer_ID, product_ID, quantity) Values(5,4,6);
Trigger code I wrote:
create or replace trigger quantity_change
before insert or update of quantity on sales
for each row
WHEN (NEW.customer_id > 0)
DECLARE
qua number;
BEGIN
qua := :NEW.quantity - :OLD.quantity;
dbms_output.put_line('Old quangtity: ' || :OLD.quantity);
dbms_output.put_line('New quantity: ' || :NEW.quantity);
dbms_output.put_line('diiference quangtity: ' || qua);
END;
UPDATE sales
SET quantity = 200 WHERE customer_id = 5;
I managed to write this procedure but still stuck, dont know how to enable use
CREATE or replace PROCEDURE Updatesales
(
customer_ID number,
product_ID number,
quantity number)
AS
BEGIN
UPDATE sales
SET quantity= 100
WHERE customer_id= 4;
END;
I want to use a function to solve the issue , a function would be somthing like this
CREATE [OR REPLACE] FUNCTION function_name [(parameter_name [IN | OUT | IN OUT] type [, ...])]
RETURN return_datatype {IS | AS} BEGIN < function_body > END [function_name];
Please advice
Your procedure isn't using the parameters you have declared; the body should be more like:
UPDATE sales
SET quantity= quantity
WHERE customer_id= customer_id;
... but that won't do what you expect because you've used the same names for the parameters and columns (and haven't referenced the product ID at all), so every row in the table will be updated with its current value. It's common to use a prefix for your formal parameter names to avoid that confusion, though you can also use the procedure name explicitly when you refer to them.
You said you want a function but it isn't clear why. It's conventional to modify data in procedures and not in functions, and if a function does do any DML then it can't be called from a query and would have to be called in a PL/SQL context. So I'll start with a procedure.
You said you wanted to 'print out' the quantity before and after the update. The procedure shouldn't do that; you should not assume that the user or client can handle dbms_output or will have it enabled. You could use an OUT parameter to return the pre-update value to the caller though:
CREATE OR REPLACE PROCEDURE update_sales
(
p_customer_id IN sales.customer_id%type,
p_product_id IN sales.product_id%type,
p_new_quantity IN sales.quantity%type,
p_old_quantity OUT sales.quantity%type
) AS
BEGIN
SELECT quantity
INTO p_old_quantity
FROM sales
WHERE customer_id = p_customer_id
AND product_id = p_product_id
FOR UPDATE;
UPDATE sales
SET quantity = p_new_quantity
WHERE customer_id = p_customer_id
AND product_id = p_product_id;
END;
/
This gets the current value of the quantity into an OUT variable, and also locks the record with for update to stop the value changing while you're working on it (probably overkill here, but you want to learn...)
It then updates the same row with the new value that was passed in. That is finding the row again using the customer and product IDs, and you could do that differently if you want to experiment - get the rowid into another local variable from your first query and use that for the update, or use a cursor, etc.
You could call it from an anonymous block as a test, and use dbms_output to show the old and new values; again, don't use dbms_output in production code, only for debugging:
SET serveroutput ON
DECLARE
l_customer_id sales.customer_id%type;
l_product_id sales.product_id%type;
l_new_quantity sales.quantity%type;
l_old_quantity sales.quantity%type;
BEGIN
l_customer_id := 5;
l_product_id := 4;
l_new_quantity := 200;
update_sales(l_customer_id, l_product_id, l_new_quantity, l_old_quantity);
dbms_output.put_line('Quantity changed from ' || l_old_quantity
|| ' to ' || l_new_quantity
|| ' (' || to_char(l_new_quantity - l_old_quantity, 'FMS999') || ')');
END;
/
PL/SQL procedure successfully completed.
Quantity changed from 6 to 200 (+194)
You can call this from an application, using bind variables, in a similar way, and have the application display the values.
Note that I haven't committed or rolled back the changes, and another session trying to call the procedure with the same values will block until I do; but will then see the new value (200) when it does run. I also haven't done any validation or exception handling in the procedure, so the caller needs to do both.
You could make this a function that returns the old value instead of using an OUT parameter, but you'd need to call it in a similar way, and generally people don't expect functions to change anything - just to return the current state. But if this is really how you want to do it, you need to modify the declaration to have a return type and local variable; select the old value into that local variable; and then return that too:
CREATE OR REPLACE FUNCTION update_sales
(
p_customer_id IN sales.customer_id%type,
p_product_id IN sales.product_id%type,
p_new_quantity IN sales.quantity%type
)
RETURN sales.quantity%type
AS
l_old_quantity sales.quantity%type;
BEGIN
SELECT quantity
INTO l_old_quantity
FROM sales
WHERE customer_id = p_customer_id
AND product_id = p_product_id;
UPDATE sales
SET quantity = p_new_quantity
WHERE customer_id = p_customer_id
AND product_id = p_product_id;
RETURN l_old_quantity;
END;
/
You still have to call it from a PL/SQL context (or something like a JDBC callable statement):
DECLARE
l_old_quantity sales.quantity%type;
BEGIN
l_old_quantity := update_sales(5, 4, 200);
dbms_output.put_line('Quantity was ' || l_old_quantity);
END;
/
PL/SQL procedure successfully completed.
Quantity was 6
You can't call it from plain SQL because it is doing a DML operation:
select update_sales(5, 4, 200) from dual;
Error report -
SQL Error: ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "MY_SCHEMA.UPDATE_SALES", line 17
14551. 00000 - "cannot perform a DML operation inside a query "
*Cause: DML operation like insert, update, delete or select-for-update
cannot be performed inside a query or under a PDML slave.
*Action: Ensure that the offending DML operation is not performed or
use an autonomous transaction to perform the DML operation within
the query or PDML slave.
What I have understood from your question is that you want to automatically update the quantity of the customer 5 to 200 whenever a new record is inserted in the SALES Table.
Trigger Code:-
CREATE OR REPLACE TRIGGER quantity_change
before insert on sales
for each row
WHEN (NEW.customer_id > 0)
DECLARE
var number;
BEGIN
var:=update_sales(:new.customer_id,:new.quantity);
:new.quantity:=var;
END;
Function Code:-
CREATE OR REPLACE FUNCTION update_sales(CUSTOMER_ID NUMBER,ORIG_QUANT NUMBER) RETURN NUMBER IS RETURNVALUE NUMBER;
BEGIN
IF customer_id = 5 THEN
returnvalue:=200;
RETURN returnvalue;
ELSE
returnvalue:= orig_quant;
RETURN returnvalue;
END IF;
END;
Sorry if I understood it otherwise.
Regards
Andy
To update a table you could use for example:
CREATE OR REPLACE PROCEDURE Updatesales(
xCustomer_ID IN Table1.custumerId%TYPE,
xProduct_ID IN Table1.productId%TYPE,
xQuantity IN Table1.quantity%TYPE)
AS
BEGIN
UPDATE sales
SET quantity = xQuantity
WHERE customer_id = xCustomer_ID;
COMMIT;
END;
/
To call this procedure you use:
Updatesales(4, 25, 100);
So what now I have done is I am calling a function from a procedure (created procedure as I as was not able to print all of the three values ), have made some assumptions from my side.
create or replace FUNCTION QUANTITY_CHANGE_NEW (CUSTOMER NUMBER, QUANT NUMBER) RETURN NUMBER
IS PRAGMA AUTONOMOUS_TRANSACTION;
old_quantity NUMBER;
BEGIN
select quantity into old_quantity from sales where customer_id=customer and rownum=1;
update sales set quantity=quant where customer_id= customer;
COMMIT;
RETURN old_quantity;
END;
CREATE OR REPLACE PROCEDURE PROCEDURE1(customer IN NUMBER, new_quantity IN NUMBER) IS
var1 NUMBER;
BEGIN
dbms_output.put_line('Customer Id is ' || customer);
var1 := QUANTITY_CHANGE_NEW(customer,new_quantity);
dbms_output.put_line('old quantity is '|| var1);
dbms_output.put_line('New quantity is '|| new_quantity);
END;
Regards
Andy

Sql oracle trigger error

Hi I was wondering if someone can help me adjust my trigger. I have only been working with triggers for only couple of weeks and quite new to it. My trigger is suppose to add $75 per day a boat is return late to the customer_balance account. If the boat is return early then it adds $20 per day the boat was return early to the customer_Balance account. I keep getting errors trying to get this trigger to work right so I was wondering can help or explain to me what I am doing wrong. Thank you.
create or replace trigger trig_penalty
before update of customer_balance
on customer for each row
declare
i number;
s varchar(20);
begin
select customer_balance into s
from customer
where customer_ID =:new.customer_id;
i := :new.charter_returndate - :old.charter_duedate;
if i>=0 then
dbms_output.put_line('Late return fee added');
update customer
set customer_balance=customer_balance+75*i
where customer_id=s;
else
dbms_output.put_line('Early return refund added');
update customer
set customer_balance=customer_balance+20*i
where customer_id=s;
end if;
end;
/
Well you have to understand that a trigger is an artifact that work for (in your case) each row that are being modified. So you are doing unecessary work on it.
Try this:
create or replace trigger trig_penalty
before update of customer_balance
on customer for each row
declare
i number;
s varchar(20);
begin
--You dont need to select the id is it is already on :new.customer_id
-- your trigger is for UPDATE
---select customer_balance into s
--- from customer
--- where customer_ID := :new.customer_id;
i := :new.charter_returndate - :old.charter_duedate;
if i>=0 then
--Do not use dbms_output in an internal object.
--dbms_output.put_line('Late return fee added');
--You don't need to run an update for an update trigger you
-- just need to set the new value on :NEW.FIELD
--update customer
-- set customer_balance=customer_balance+75*i
-- where customer_id=s;
--As you select this value I'm using the OLD which means the value that
--Was already on the table
:NEW.customer_balance := :OLD.customer_balance+75*i;
else
--dbms_output.put_line('Early return refund added');
--update customer
-- set customer_balance=customer_balance+20*i
-- where customer_id=s;
:NEW.customer_balance := :OLD.customer_balance+20*i;
end if;
end;
/
That should do it. Remeber for an BEFORE trigger the value setted on :NEW will end up on the table.