how do i verify a row before and after update - sql

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.

Related

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

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;

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.

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

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.