Oracle nested cursor with for loop - oracle

I am trying to make this work but no matter what I do the od_total var is always empty. I just cannot make it work.
I am trying to run a cursor to get a list of ordersid and save it into o_id every loop. Then, I use the second cursor to use the value stored into o_id to do another query that stores the result into od_total. While o_id seems to be storing the values, od_total for some reason is always empty.
CREATE OR REPLACE PROCEDURE p_practicum2_practice IS
o_id CHAR(5);
od_total NUMBER := 0;
CURSOR ordersid IS
SELECT DISTINCT orderdetails.orderid FROM orderdetails
INNER JOIN orders ON orderdetails.orderid = orders.orderid
INNER JOIN customers ON orders.customerid = customers.customerid
WHERE orders.customerid = 'LILAS';
CURSOR total IS
SELECT SUM(unitprice*quantity) FROM orderdetails WHERE orderid = o_id;
BEGIN
OPEN ordersid;
OPEN total;
LOOP
FETCH ordersid INTO o_id;
EXIT WHEN ordersid%notfound;
LOOP
FETCH total INTO od_total;
EXIT WHEN total%notfound;
END LOOP;
dbms_output.put_line(o_id || ' ---- ' || od_total);
END LOOP;
CLOSE ordersid;
CLOSE total;
END;
this is what I get as result:
enter image description here

As far i could understand your question you wanted to display od_total for all od_id passed from cursor ordersid. You are not getting anything because od_id is not getting passed to your second cursor. Read the code below inline comments how to pass the argument to cursor.
CREATE OR REPLACE PROCEDURE p_practicum2_practice
IS
o_id CHAR(5);
od_total NUMBER := 0;
CURSOR ordersid
IS
SELECT DISTINCT orderdetails.orderid
FROM orderdetails
INNER JOIN orders
ON orderdetails.orderid = orders.orderid
INNER JOIN customers
ON orders.customerid = customers.customerid
WHERE orders.customerid = 'LILAS';
CURSOR total(v_oid char)
IS
SELECT SUM(unitprice*quantity)
FROM orderdetails
WHERE orderid = v_oid;
BEGIN
OPEN ordersid;
LOOP
FETCH ordersid INTO o_id;
EXIT WHEN ordersid%notfound;
OPEN total(o_id); --<--This is how you pass the paramater to your cursor
LOOP
FETCH total INTO od_total;
EXIT WHEN total%notfound;
dbms_output.put_line(o_id || ' ---- ' || od_total);
END LOOP;
CLOSE total;
--If you print here then only the last od_total value will be printed.
--dbms_output.put_line(o_id || ' ---- ' || od_total);
END LOOP;
CLOSE ordersid;
END;
Output:
17465 ---- 6
12345 ---- 3
14435 ---- 4
19045 ---- 6
19345 ---- 8

Related

function not returning a value for no rows returned

I created a function that takes a movie id as input and returns stock information based from the ID. The function mostly works but if I want to retrieve information from a movie that is not in the database(returns no rows) nothing returns. Can't figure out why?
doesn't give me an error when i call an ID that returns no rows so exception handling wouldn't work.
create or replace function stock_info
(p_id IN NUMBER
)
return VARCHAR2
IS
cursor c1 is
select movie_id, movie_title, movie_qty
from mm_movie
where p_id = movie_id;
lv_movie_info VARCHAR2(100);
BEGIN
for i in c1 loop
if p_id = i.movie_id then
lv_movie_info := i.movie_title || ' is available: ' || i.movie_qty || ' on the shelf';
else
lv_movie_info := 'no data found';
end if;
end loop;
return lv_movie_info;
END STOCK_INFO;
/
The reason you don't get anything when there is no data is that the loop doesn't execute. Logically the For expression says "execute the following loop for every row returned in the cursor" but there are no rows in the cursor so it never executes the loop. Further the structure actually indicates you are expecting multiple for a given p_id. If that's not the case you can eliminate the cursor all together. Assuming p_id is the primary key you have either 0 or 1 row so:
create or replace function stock_info (p_id in number)
return text
is
lv_movie_info varchar2(100);
begin
select i.movie_title || ' is available: ' || i.movie_qty || ' on the shelf'
into lv_movie_info
from mm_movie i
where p_id = movie_id;
return lv_movie_info;
exceptions
when no_data_found
then return 'no data found';
end stock_info;
Of course if do expect more that 1 row the cursor is needed, but the IF is not as the were clause guarantees it's true. Still with 0 rows the loop will not be executed so the 'no data found' message needs to go after "End Loop".
Belayer
the cursor statement you used fetches data from the in parameter. i.e., in the cursor select you limiting based on the movie id passed.
on passing a movie id which is not in the data base, the cursor select statement would not fetch any records, and so the flow won't even go inside the for loop.
if you wanted to return no data found - on passing a movie id which is not in the database, two ways to resolve
1. before the loop, have select statement to set a flag to Y or N if exists according and to have your requirement.
2. in if not using for cursor, there is an option to check not found...
sample:
declare
cursor c1 is select * from table_sample; -- empty table
c_rec c1%rowtype;
begin
open c1;
fetch c1 into c_rec;
if c1%notfound then
dbms_output.put_line('not found');
end if;
close c1;
end;
-- output
not found

Collect multiple rows and store values in PL/SQL

I have the following query:
DECLARE
type INV_TASK_GLOBAL is varray(10) of NUMBER(20,0);
type STUDY_CASE_GLOBAL is varray(10) of NUMBER(20,0);
BEGIN
SELECT T_INVESTIGATIONTASK.ID, T_STUDYCASE.ID
into INV_TASK_GLOBAL, STUDY_CASE_GLOBAL
FROM T_VALIDATIONCARRIER
left join T_ESTIMATIONOBJECT on T_VALIDATIONCARRIER.IA_ESTIMATIONOBJECT_ID = T_ESTIMATIONOBJECT.ID
left join T_INVESTIGATIONTASK on T_ESTIMATIONOBJECT.INVESTIGATIONTASK_ID = T_INVESTIGATIONTASK.ID
left join T_STUDYCASE on T_INVESTIGATIONTASK.STUDYCASE_ID = T_STUDYCASE.ID
WHERE T_VALIDATIONCARRIER.ESTIMATIONOBJECT_ID = 940;
dbms_output.Put_line('INVESTIGATIONTASK_ID: ' || INV_TASK_GLOBAL);
dbms_output.Put_line('STUDYCASE_ID: ' || STUDY_CASE_GLOBAL);
END;
The compiler is telling me that the number specified in exact fetch is less than the rows returned. The fact is that I want those lines to be returned. To be specific: I would like to collect all the T_INVESTIGATIONTASK.ID and T_STUDYCASE.ID (one per each row which is captured by the WHERE clause), store them into the INV_TASK_GLOBAL and in STUDY_CASE_GLOBAL and then display all the values returned (separated maybe by a comma).
I may change the WHERE condition in the future but the maximum number of elements I expect to be returned for both variables is 10 anyway.
I know that I am using the varray datatype in a wrong way: I need some sort of cycle and a list/array datatype to store all the returned values in the INV_TASK_GLOBAL and STUDY_CASE_GLOBAL variables and then print the array on screen. Do you have any idea of how to accomplish it?
After some tests the following code solved my problem:
DECLARE
type collection_id is table of NUMBER(20,0);
INV_TASK_GLOBAL collection_id := collection_id(10);
STUDY_CASE_GLOBAL collection_id := collection_id(10);
BEGIN
SELECT T_INVESTIGATIONTASK.ID, T_STUDYCASE.ID
BULK COLLECT into INV_TASK_GLOBAL, STUDY_CASE_GLOBAL
FROM T_VALIDATIONCARRIER
left join T_ESTIMATIONOBJECT on T_VALIDATIONCARRIER.IA_ESTIMATIONOBJECT_ID = T_ESTIMATIONOBJECT.ID
left join T_INVESTIGATIONTASK on T_ESTIMATIONOBJECT.INVESTIGATIONTASK_ID = T_INVESTIGATIONTASK.ID
left join T_STUDYCASE on T_INVESTIGATIONTASK.STUDYCASE_ID = T_STUDYCASE.ID
WHERE T_VALIDATIONCARRIER.ESTIMATIONOBJECT_ID = 940;
FOR indx IN 1 .. INV_TASK_GLOBAL.COUNT
LOOP
dbms_output.Put_line('INVESTIGATIONTASK_ID: ' || INV_TASK_GLOBAL(indx));
END LOOP;
FOR indx IN 1 .. STUDY_CASE_GLOBAL.COUNT
LOOP
dbms_output.Put_line('STUDY_CASE_ID: ' || STUDY_CASE_GLOBAL(indx));
END LOOP;
END;

The procedure below(when i uncomment the commented parts) shows 'no data found'..?

Hi I'm trying the code the below logic..i need help..
When i run the following procedure i get all the sids along with the corresponding pid for a particular month. but when i uncomment the parts that i have commented here i get the month and year displayed and then a message saying 'no data found'. Where am i going wrong?
create or replace PROCEDURE mas(V_MONTH NUMBER DEFAULT NULL,V_YEAR NUMBER DEFAULT NULL,V_AID VARCHAR2) AS
V_MID VARCHAR2(50);
V_SID VARCHAR2(50);
v_v_month number := nvl(V_MONTH,to_number(to_char(sysdate,'mm')));
v_v_year number := nvl(V_YEAR,to_number(to_char(sysdate,'yyyy')));
v_is_sub PM.IS_SUB%TYPE;
V_DIST_s NUMBER;
V_DIST_t NUMBER;
cursor c1
is
select distinct a.mid,
b.sid
from pmt a
inner join smt b
on (a.mid = b.mid)
where a.AID = V_AID
AND A.AID = B.AID
AND EXTRACT(MONTH FROM A.RDATE)= v_v_month AND
EXTRACT(YEAR FROM A.RDATE)= v_v_year
order by mid;
BEGIN
dbms_output.put_line('month : ' || v_v_month);
dbms_output.put_line('year : ' || v_v_year);
/*
select IS_SUB into v_is_sub from program_master where pid = 'V_AID';
IF v_is_sub = 1 then
select count(*) into V_DIST_S from (select distinct sid from smt where aid = 'v_aid');
select count(*) into V_DIST_T from (select distinct sid from tm where aid = 'v_aid');
if(V_DIST_S = V_DIST_T) then
*/
for rec1 in c1
loop
dbms_output.put_line('MID : ' || rec1.MID);
dbms_output.put_line('SID : ' || rec1.SID);
end loop;
-- else
-- dbms_output.put_line('count of sids do not match');
--end if;
--else
--dbms_output.put_line('No sids available for the mentioned pid.');
--end if;
END MAS;
where pid = 'V_AID';
V_AID is a variable, however, you have enclosed it within single-quotation marks, which makes it a string. So, you are actually looking for the value 'V_AID' and not the value of the variable.
Modify it to:
select IS_SUB into v_is_sub from program_master where pid = V_AID;
And do the same wherever you have enclosed the variable in single-quotation marks.

Using Rownum in Cursor Bulk Collect Oracle

I'm trying to use the rownum to simulate a column autonumbered as I need to use it as an ID. Since it is an ID, I look at the final table if no record with MAX (ID).
The problem I have is when I want to do arithmetic operations within the cursor or when you invoke, or when you want to use a function. The ROWNUM (v_id) field is empty me when I want to print with DBMS_OUTPUT . Anyone have any idea how to solve it without using sequences ?
Here put the sample code.
declare
max_id number;
CURSOR INSRT(w_max number) IS
SELECT f_max_fact_sap(to_number(V_ID),w_max) AS V_ID,Seriei,serief
FROM (SELECT To_Char(ROWNUM) AS V_ID, A.*
FROM (SELECT DISTINCT a.matnr, a.seriei, a.serief,a.xblnr,a.fecha_sap, ((SERIEF-SERIEI)+1) AS rango
FROM SOPFUN.TT_ZMOVIMI_FACTURADAS a
WHERE 0 =(SELECT COUNT(1)
FROM PA_ZMOVIMI_FACTURADAS B
WHERE A.SERIEI = B.SERIEI
AND A.SERIEF = B.SERIEF
AND A.MATNR = B.MATNR
AND A.FECHA_SAP=B.FECHA_SAP)
AND A.FECHA_SAP IS NOT NULL) A);
TYPE T_INSRT IS TABLE OF INSRT%ROWTYPE INDEX BY PLS_INTEGER;
V_INSRT T_INSRT;
begin
SELECT Max(Nvl(ID,10000)) INTO MAX_ID-- To Proof because the table is empty
FROM PA_ZMOVIMI_FACTURADAS;
OPEN INSRT(MAX_ID);
LOOP
FETCH INSRT BULK COLLECT INTO V_INSRT LIMIT 1000;
FOR I IN 1 .. V_INSRT.Count loop
DBMS_OUTPUT.PUT_LINE('ID: ' ||V_INSRT(I).V_ID||' SI: '||V_INSRT(I).SERIEI||' SI: '||V_INSRT(I).SERIEF||' OPERACION: '||to_char(f_max_fact_sap(V_INSRT(I).V_ID,MAX_ID)));
end loop;
EXIT WHEN INSRT%NOTFOUND;
END LOOP;
end;

How to find number of rows in cursor

I would like to find the number of rows in a cursor. Is there a keyword that can help? Using COUNT, we have to write a query. Any help will be greatly appreciated.
The cursor_variable.%ROWCOUNT is the solution. But its value will be 0 if you check it after opening. You need to loop through all the records, to get the total row count. Example below:
DECLARE
cur sys_refcursor;
cur_rec YOUR_TABLE%rowtype;
BEGIN
OPEN cur FOR
SELECT * FROM YOUR_TABLE;
dbms_output.put_line(cur%rowcount);--returning 0
LOOP
FETCH cur INTO cur_rec;
EXIT WHEN cur%notfound;
dbms_output.put_line(cur%rowcount);--will return row number beginning with 1
dbms_output.put_line(cur_rec.SOME_COLUMN);
END LOOP;
dbms_output.put_line('Total Rows: ' || cur%rowcount);--here you will get total row count
END;
/
You must open the cursor and then fetch and count every row. Nothing else will work.
You can also use BULK COLLECT so that a LOOP is not needed,
DECLARE
CURSOR c
IS SELECT *
FROM employee;
TYPE emp_tab IS TABLE OF c%ROWTYPE INDEX BY BINARY_INTEGER;
v_emp_tab emp_tab;
BEGIN
OPEN c;
FETCH c BULK COLLECT INTO v_emp_tab;
DBMS_OUTPUT.PUT_LINE(v_emp_tab.COUNT);
CLOSE c;
END;
/
Edit: changed employee%ROWTYPE to c%ROWTYPE
You can use following simple single line code to print cursor count
dbms_output.put_line(TO_CHAR(cur%rowcount));
This should work for you
DECLARE
CURSOR get_data_ IS
SELECT *
FROM table_abc_
WHERE owner = user_; -- your query
counter_ NUMBER:= 0;
BEGIN
FOR data_ IN get_data_ LOOP
counter_ := counter_ + 1;
END LOOP;
dbms_output.put_line (counter_);
END;
DECLARE #STRVALUE NVARCHAR(MAX),
#CREATEDDATE DATETIME,
#STANTANCEVALUE NVARCHAR(MAX),
#COUNT INT=0,
#JOBCODE NVARCHAR(50)='JOB00123654',
#DATE DATETIME=GETDATE(),
#NAME NVARCHAR(50)='Ramkumar',
#JOBID INT;
CREATE TABLE #TempContentSplitValue (ITEMS NVARCHAR(200))
SELECT #JOBID = i.Id FROM JobHeader_TBL i WHERE Id=1201;
IF EXISTS (SELECT 1 FROM JobHeader_TBL WHERE Id=#JOBID)
BEGIN
SELECT #STRVALUE= Description from ContentTemplate_TBL where Id=1
INSERT INTO #TempContentSplitValue SELECT * FROM dbo.split(#STRVALUE, '_')
SET #STRVALUE=''
DECLARE db_contentcursor CURSOR FOR SELECT ITEMS FROM #TempContentSplitValue
OPEN db_contentcursor
FETCH NEXT FROM db_contentcursor
INTO #STANTANCEVALUE
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #STRVALUE += #STANTANCEVALUE + 'JOB00123654'
SET #COUNT += 1
SELECT #COUNT
FETCH NEXT FROM db_contentcursor INTO #STANTANCEVALUE
END
CLOSE db_contentcursor
DEALLOCATE db_contentcursor
DROP TABLE #TempContentSplitValue
SELECT #STRVALUE
END
Here I am trying to count the total number of customers with age greater than 25. So store the result in the cursor first. Then count the size of the cursor inside the function or in the main begin itself.
DECLARE
cname customer24.cust_name%type;
count1 integer :=0;
CURSOR MORETHAN is
SELECT cust_name
FROM customer24
where age>25;
BEGIN
OPEN MORETHAN;
LOOP
FETCH MORETHAN into cname;
count1:=count1+1;
EXIT WHEN MORETHAN%notfound;
END LOOP;
-- dbms_output.put_line(count1);
dbms_output.put_line(MORETHAN%ROWCOUNT);
CLOSE MORETHAN;
END;
There is a possible work around that may be useful/needed because of the overhead of accessing a database server over a network (e.g., when using Ajax calls)
Consider this:
CURSOR c_data IS
SELECT per_first_name , null my_person_count
FROM person
UNION
SELECT null as per_first_name , count( distinct per_id ) as my_person_count
FROM person
order by my_person_count ;
The first row fetched has the count of records. One MUST add specific columns fetched (the use of the * does not work), and one can add additional filters.
Try this:
print(len(list(cursor)))
I always read that people loop through results. Why not using a count(*)?
An example from my production code:
PROCEDURE DeleteStuff___(paras_ IN Parameters_Type_Rec)
IS
CURSOR findEntries_ IS
select * from MyTable
where order_no = paras_.order_no;
counter_ NUMBER;
CURSOR findEntries_count_ IS
SELECT COUNT(*) from MyTable
where order_no = paras_.order_no;
BEGIN
OPEN findEntries_count_;
FETCH findEntries_count_ INTO counter_;
CLOSE findEntries_count_;
dbms_output.put_line('total records found: '||counter_);
IF (counter_ = 0) THEN
-- log and leave procedure
RETURN;
END IF;
FOR order_rec_ IN findEntries_ LOOP
EXIT WHEN findEntries_%NOTFOUND OR findEntries_%NOTFOUND IS NULL;
-- do stuff - i.e. delete a record.
API_Package.Delete(order_rec_);
END LOOP;
END DeleteStuff___;
If the query is small, that is my prefered way.
In this example, I just want to know (and log) how many entries I'll delete.
p.s. Ignore the three underlines. In IFS, this is used when you want private procedures or functions.
You can’t have cursor count at start. For that you need to fetch complete cursor; that is the way get cursor count.
declare
cursor c2 is select * from dept;
var c2%rowtype;
i number :=0;
begin
open c2;
loop
fetch c2 into var;
exit when c2%NOTFOUND;
i: = i+1;
end loop;
close c2;
dbms_output.put_line('total records in cursor'||i);
end;
You can use %ROWCOUNT attribute of a cursor.
e.g:
DECLARE
CURSOR lcCursor IS
SELECT *
FROM DUAL;
BEGIN
OPEN lcCursor ;
DBMS_OUTPUT.PUT_LINE(lcCursor%ROWCOUNT);
CLOSE lcCursor ;
END;

Resources