update/insert PL/SQL - sql

Let's say that Customer 4 wishes to increase their order from 23 to 100. Enable the user to type in:
The number 4 for customer_ID
100 for the updated quantity.
I want to Write a PL/SQL function to receive these two values and update the sales table to reflect this change. Print out on screen from the main section of code the total quantity for customer 4 before and after the update. Please help I have tried with the following code but not sure about the structure
CREATE OR REPLACE TRIGGER orders_before_insert
BEFORE INSERT
ON ord
FOR EACH ROW
DECLARE
v_price NUMBER;
new number;
BEGIN
SELECT pr INTO v_price FROM product where product_id =:new.product_id
;
-- Update create_date field to current system date
:new.total_cost := :new.quantity * v_price;
END;

Here is a proof of concept procedure. It doesn't have the validation or error handling we would expect in a proper procedure.
create or replace procedure update_order
(p_order_id in orders.id%type
, p_additional_qty in orders.qty%type
, p_orig_total out number
, p_new_total out number )
is
l_total number;
l_orig number;
begin
update orders
set qty = qty + p_additional_qty
where id = p_order_id
returning (qty - p_additional_qty)* price
, qty * price into l_orig, l_total;
p_orig_total := l_orig;
p_new_total := l_total;
end update_order;
/
In SQL*Plus we use the ACCEPT command to get input values from a user. We declare variables to hold computed values with VAR, and output them with PRINT.
Here is the test data:
SQL> select * from orders;
ID QTY PRICE
---------- ---------- ----------
42 23 19.99
SQL>
And here is how we call the procedure using the SQL*Plus EXECUTE command:
SQL> var tot number
SQL> var orig number
SQL> accept order_id prompt "enter order ID: "
enter order ID: 42
SQL> accept add_qty prompt "please enter add qty: "
please enter add qty: 77
SQL> exec update_order (&order_id, &add_qty, :orig, :tot)
PL/SQL procedure successfully completed.
SQL> print :orig
ORIG
----------
459.77
SQL> print :tot
TOT
----------
1999
SQL>
To script it, just place all the commands in a text file, and run it in SQL*Plus like this:
SQL> #your_script.sql
Note that I have deviated from your assignment's instructions in a couple of ways.
I don't display the original total cost upfront. To do that you would need to select the record first then update it. In real life we would want to avoid two operations.
I'm calculating the totals rather then storing them. Again this is the approach we would take in a real system. However, if your table actually has an TOTAL_COST column you would need to amend the UPDATE according.

Related

Define, initialise and use variables in SQL developer and SSIS ODBC connection

I am working on a script to be later used in my SSIS ETL, the source DB is oracle and I am using SQL Developer 20.0.2.75 .
I spent so much time declaring 100 variables but it doesn't see to work in SQL developer.
Define & Initialise:
Declare
V1 number;
V2 number;
.
.
.
V100 number;
Begin
Select UDF(params1,param2) into V1 from dual;
Select UDF(params3,param4) into V2 from dual;
...
End;
I was hoping I'd be able to use these variables in my script like :
select columns from table where Col1=:V1 and Col2=:V2
When used "Run Statement" prompts for values, "Run Script" doesn't see to like into Variable statements.
I even tried :
select columns from table where Col1=&&V1 and Col2=&&V2
Now my query doesn't work !
After below responses, I changed my script to :
Variable V1 Number;
Variable V2 Number;
exec select MyFunction(p1,p2) into :V1 from Dual;
/
Select columns from table where col1=:V1 and col2=:V2
It still prompts for value
This is how I defined my function
Create Function MyFunction(m IN Varchar, s IN Number)
Return Number
IS c Number;
select code into c from table where col1=m and col2=s;
Return(c);
End;
Is there anything wrong with the function?
You define variables as per you would in SQL Plus or SQLcl and then run it as a script
Text below
variable x1 number
begin
select 123 into :x1 from dual;
end;
/
print x1
Similar example in SQL Plus (and will work in SQL Dev as well)
SQL> set serverout on
SQL> variable x1 number
SQL> begin
2 select 5 into :x1 from dual;
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> print x1
X1
----------
5
SQL>
SQL> select rownum from dual
2 connect by level <= :x1;
ROWNUM
----------
1
2
3
4
5
SQL>
SQL> begin
2 dbms_output.put_line('X1 is '||:x1);
3 end;
4 /
X1 is 5
PL/SQL procedure successfully completed.
I spent so much time declaring 100 variables
To me, it looks like a wrong approach. OK, declare a few variables, but 100 of them?! Why wouldn't you switch to something easier to maintain. What? A table, for example.
create table params
(var varchar2(20),
value varchar2(20)
);
Pre-populate it with all variables you use (and then just update their values), or just insert rows:
insert into params (var, value) values ('v1', UDF(params1, param2));
insert into params (var, value) values ('v2', UDF(params3, param4));
...
Fetch values through a function:
create or replace function f_params (par_var in varchar2)
return varchar2
is
retval varchar2(20);
begin
select value
into retval
from params
where var = par_var;
return retval;
end;
Use it (in your query) as:
select columns
from table
where Col1 = f_params('v1')
and Col2 = f_params('v2')
If many users use it, consider creating one "master" params table (which contains all the variables) and a global temporary table (which would be populated and used by each of those users).

SQL: No records found. Handling no errors found [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I created this SQL code because I need to rank an specific list of ISIN's. Therefore, I have been using the following code to get the results:
var r refcursor;
begin
msa.exl_stifel_ms ('01/01/2017',
'21/01/2018',
'GBP',
'CB',
'JE00BYR8GK67',
'UBS',
:r);
end;
and I get 'no records' found.. which is obviously wrong and indicates that's something wrong with the query.... any advice?
create or replace procedure msa.exl_stifel_ms (
tradedatestart in date,
tradedateend in date,
inccy in varchar,
inbtype in varchar,
invariable in varchar,
inbroker in varchar,
test out sys_refcursor)
as
inbrokerid integer;
begin
delete from tt_exl_out;
delete from isins_tt;
delete from tt_exl_indexdetail;
delete from index_tt;
select brokerid
into inbrokerid
from msa.client
where clientname = inbroker;
insert into isins_tt (isin)
select distinct effectiveisin
from msa.instrument inst
where inst.isin = invariable; --will need to loop through all the ISINs here
rp_calctrnbicdetailtotals (inccy,
inbtype,
tradedatestart,
tradedateend);
insert into tt_exl_indexdetail (isin,
brokerid,
brokercode,
brokertotal)
select i.effectiveisin,
ba.brokerid,
(select br2.brokercode
from broker br2
where br2.brokerid = ba.brokerid),
sum (t.total)
from bicisintotal_tt t
inner join bank ba
on t.bicid = ba.bicid and ba.includeinreport = 1
inner join instrument i on t.isin = i.isin
group by ba.brokerid, i.effectiveisin;
update tt_exl_indexdetail tt1
set percentage =
(select case x.totalvalue
when 0 then 0
else (i.brokertotal / x.totalvalue)
end
as percentage
from index_tt i,
(select sum (brokertotal) as totalvalue from index_tt) x
where brokerid = tt1.brokerid);
insert into tt_exl_out (yearno,
rank,
turnover,
marketshare)
select id1.brokerid,
dense_rank () over (order by id1.brokertotal desc),
id1.brokertotal,
id1.percentage
from index_tt id1;
open test for
select rank,
c.clientname,
turnover,
marketshare
from tt_exl_out tt3
inner join msa.client c on tt3.yearno = c.brokerid
where c.clientname = inbroker;
end;
Unless I'm wrong this:
select brokerid
into inbrokerid
from msa.client
where clientname = inbroker
is the only SELECT that could return NO-DATA-FOUND (if that's what you call "no records found"). Other SELECTs are part of INSERT or UPDATE statements, so they simply won't do anything, but won't raise an error either.
You also call rp_calctrnbicdetailtotals - I don't know what it does, but - it could be a candidate for an error as well.
Try to run that code in SQL*Plus, it'll tell you exact spot of an error (PL/SQL object name, line number) so you might be able to fix it. Make sure you remove all WHEN OTHERS exception handlers (if there are any).
[EDIT, after seeing #kfinity's comment]
As I said, I think not. Here's an example; the first SELECT returns nothing, there's no employee with EMPNO = -1 (so, as you said, it would return no-data-found):
SQL> var pe refcursor
SQL> create or replace procedure p_test (test out sys_refcursor) is
2 begin
3 open test for
4 select ename
5 from emp
6 where empno = -1;
7 end;
8 /
Procedure created.
SQL> exec p_test (:pe)
PL/SQL procedure successfully completed.
SQL> print pe
no rows selected
See? No error.
However, if it was an ordinary SELECT ... INTO, then yes - it would raise it:
SQL> create or replace procedure p_test as
2 l_ename varchar2(20);
3 begin
4 select ename into l_ename
5 from emp
6 where empno = -1;
7 end;
8 /
Procedure created.
SQL> exec p_test
BEGIN p_test; END;
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "HR.P_TEST", line 4
ORA-06512: at line 1
SQL>
[EDIT #2]
Yes, as Alessandro said, it should then be handled by the EXCEPTION section.

SQL procedure called from trigger

In Oracle environment (SQLPLUS I used). My question is how to alter the data of a newly inserted tuple.
Here is an example: I need to make sure whenever an insert happen to table "Orders", if its attribute "weight" is bigger than 100, its another attribute "size_level" must be 1. Otherwise (weight<=100), size_level must be 0.
In order to do this, I figured that I need to call a stored procedure.
CREATE OR REPLACE TRIGGER new_ship_trigger
AFTER INSERT ON Orders
FOR EACH ROW
BEGIN ATOMIC
CALL UpdateShipSizeLevel(:new)
END;
/
How should I write the code for the stored procedure part? Or maybe I need a cursor?
I am required NOT to add a constraint to table "Orders" and I should use no more than one trigger.
Here you'll find yet another Oracle trigger example. Also be sure to read e.g. PL/SQL Triggers.
create table so54b (
id number
,weight number
,weight_level number
);
create or replace trigger so54b_trg
-- note the trigger is also run in update
before insert or update on so54b
for each row
begin
-- you don't need to implement the trigger logic in a separate
-- subroutine. however sometimes it might make sense.
:new.weight_level :=
case
when :new.weight > 100 then 1
else 0
end;
end;
/
show errors
insert into so54b(id, weight) values (1, 99);
insert into so54b(id, weight) values (2, 100);
-- weight_level is overwritten by the trigger
insert into so54b(id, weight, weight_level) values (3, 101, 13);
select * from so54b order by id;
ID WEIGHT WEIGHT_LEVEL
---------- ---------- ------------
1 99 0
2 100 0
3 101 1
update so54b set weight = 80 where weight > 100;
select * from so54b order by id;
ID WEIGHT WEIGHT_LEVEL
---------- ---------- ------------
1 99 0
2 100 0
3 80 0

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

Using SEQUENCE(Oracle) in WHERE Clause [duplicate]

The following Oracle SQL code generates the error "ORA-02287: sequence number not allowed here":
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA');
SELECT * FROM Customer where CustomerID=Customer_Seq.currval;
The error occurs on the second line (SELECT statement). I don't really understand the problem, because this does work:
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA');
SELECT Customer_Seq.currval from dual;
You have posted some sample code, so it is not clear what you are trying to achieve. If you want to know the assigned value, say for passing to some other procedure you could do something like this:
SQL> var dno number
SQL> insert into dept (deptno, dname, loc)
2 values (deptno_seq.nextval, 'IT', 'LONDON')
3 returning deptno into :dno
4 /
1 row created.
SQL> select * from dept
2 where deptno = :dno
3 /
DEPTNO DNAME LOC
---------- -------------- -------------
55 IT LONDON
SQL>
Edit
We can use the RETURNING clause to get the values of any column, including those which have been set with default values or by trigger code.
You don't say what version of Oracle you are using. There have in the past been limitations on where sequences can be used in PL/SQL - mostly if not all gone in 11G. Also, there are restrictions in SQL - see this list.
In this case you may need to write:
SELECT Customer_Seq.currval INTO v_id FROM DUAL;
SELECT * FROM Customer where CustomerID=v_id;
(Edited after comments).
This doesn't really directly answer your question, but maybe what you want to do can be resolved using a the INSERT's RETURNING clause?
DECLARE
-- ...
last_rowid rowid;
-- ...
BEGIN
-- ...
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA') RETURNING rowid INTO last_rowid;
SELECT * FROM Customer where rowid = last_rowid;
-- ...
END;
/
You may not use a sequence in a WHERE clause - it does look natural in your context, but Oracle does not allow the reference in a comparison expression.
[Edit]
This would be a PL/SQL implementation:
declare
v_custID number;
cursor custCur is
select customerid, name from customer
where customerid = v_custID;
begin
select customer_seq.nextval into v_custID from dual;
insert into customer (customerid, name) values (v_custID, 'AAA');
commit;
for custRow in custCur loop
dbms_output.put_line(custRow.customerID||' '|| custRow.name);
end loop;
end;
You have not created any
sequence
First create any sequence its cycle and cache. This is some basic example
Create Sequence seqtest1
Start With 0 -- This Is Hirarchy Starts With 0
Increment by 1 --Increments by 1
Minvalue 0 --With Minimum value 0
Maxvalue 5 --Maximum Value 5. So The Cycle Of Creation Is Between 0-5
Nocycle -- No Cycle Means After 0-5 the Insertion Stopes
Nocache --The cache Option Specifies How Many Sequence Values Will Be Stored In Memory For Faster Access
You cannot do Where Clause on Sequence in SQL beacuse you cannot filter a sequence . Use procedures like #APC said