update table pl/sql - 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

Related

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.

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

Displaying table values through procedures by using a parameter Oracle SQL

TABLE DESC:
Product (Prodid, Prodesc, Price, Stock)
Purchase (Purid, Proid, qty, supname)
Sales (Saleid, Proid, qty, custname)
I need to create a procedure which accepts a prodid and displays all the sales and purchase records of it.
This is my code:
create or replace procedure display (p in varchar2)
AS
pur_dis purchase%rowtype;
sal_dis sales%rowtype;
BEGIN
select * INTO pur_dis from purchase where proid=p;
select * INTO sal_dis from sales where proid=p;
end;
/
when i try to execute It displays only "PL/SQL procedure successfully completed"
But I needed the purchase table values and sales table values to be displayed for that particular id. What to do?
If you need to display in your worksheet, you may use dbms_output.put_line
make necessary changes in the procedure to display:
create OR REPLACE
PROCEDURE display(
p IN VARCHAR2)
AS
pur_dis purchase%rowtype;
sal_dis sales%rowtype;
BEGIN
SELECT * INTO pur_dis FROM purchase WHERE proid=p;
dbms_output.put_line('Displaying your values:'||pur_dis.Purid||' '||pur_dis.Proid||' '||pur_dis.qty||' '||pur_dis.supname);
SELECT * INTO sal_dis FROM sales WHERE proid=p;
END;
/
call the procedure:
SET SERVEROUTPUT ON
begin
display(value_of_p);
end;
/

Oracle SQL Trigger mutating when implemeneted

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.