Creating a result set (using a select statement ) within a loop - oracle

I have created a cursor which returns me a set of rows. While iterating through each of the row, I want to get another result set (by forming a SELECT statement by with a WHERE clause having value from the processed row) from another table. I am a newbie in PLSQL. Can you please guide me on how this could be done? (Can we have a Cursor defined inside the loop while looping for the resultset of the cursor)?
Please excuse me if I am not able to make myself clear.
Thanks in advance
DECLARE
CURSOR receipts IS
SELECT CREATED_T, ACCT_NO, AMT FROM receipt_t
WHERE OBJ_TYPE='misc';
receipts_rec receipts%ROWTYPE;
BEGIN
-- Open the cursor for processing
IF NOT receipts%ISOPEN THEN
OPEN receipts;
END IF;
LOOP
FETCH receipts INTO receipts_rec;
EXIT WHEN receipts%NOTFOUND;
/* Loop through each of row and get the result set from another table */
newQuery := 'SELECT * FROM ageing_data WHERE ACCT_NO = ' || receipts_rec.ACCT_NO;
-- Execute the above query and get the result set, say RS
LOOP
-- For above result set-RS
END LOOP;
END LOOP;
CLOSE receipts;
END;

Yes, you can define a cursor that takes a set of parameters and use those values in the WHERE clause.
DECLARE
CURSOR c_cursor1 IS
SELECT field1, field2, ... , fieldN
FROM table1
WHERE conditions;
CURSOR c_cursor2 (p_parameter NUMBER) IS
SELECT field1, field2, ..., fieldN
FROM table2
WHERE table2.field1 = p_parameter;
BEGIN
FOR record1 IN c_cursor1 LOOP
FOR record2 IN c_cursor2(record1.field1) LOOP
dbms_output.put_line('cursor 2: ' || record2.field1);
END LOOP
END LOOP;
END;

Yes, you can do that, but there is absolutely no reason to. Try the following:
BEGIN
FOR aRow IN (SELECT rt.CREATED_T, rt.ACCT_NO, rt.AMT, ad.*
FROM RECEIPT_T rt
INNER JOIN AGEING_DATA ad
ON (ad.ACCT_NO = rt.ACCT_NO)
WHERE rt.OBJ_TYPE='misc')
LOOP
-- Process the data in aRow here
END LOOP;
END;
This does exactly the same work as the original "loop-in-a-loop" structure but uses the database to join the tables together on the common criteria instead of opening and closing cursors multiple times.
Share and enjoy.

Something like this can be done in the following manner:
DECLARE
CURSOR cursor1 IS
SELECT *
FROM table1;
CURSOR cursor2 IS
SELECT *
FROM table2
WHERE column1 = I_input_param;
BEGIN
FOR table_1_rec in cursor1 LOOP
I_input_param := table_1_rec.column_1;
FOR table_2_rec in cursor2 LOOP
....
....
END LOOP;
END LOOP;
END;
I have used an implicit open/fetch here. I hope you get the idea.

Related

why if block not working within a procedure while comparing two columns of two tables

I need to check whether std_id is present same as in students table if this matches with std_id in std_grace_marks then I need to add that grace_marks of that std_id with marks column in students table.
I have created a procedure and created two cursors to fetch records in loop and I wrote if condition to check whether std_id matches if it is then I am adding marks with grace_marks but if condition is not working here... so please can anyone tell where I am going wrong
My code:
create or replace procedure std_info
IS
CURSOR stdcur IS SELECT std_id,std_name, marks,mark_status FROM students;
CURSOR gracer IS SELECT std_id,grace_marks from student_grace_marks;
myvar stdcur%ROWTYPE;
mycur gracer%ROWTYPE;
BEGIN
OPEN stdcur;
OPEN gracer;
LOOP
FETCH stdcur INTO myvar;
FETCH gracer INTO mycur;
EXIT WHEN stdcur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( myvar.std_id || ' '|| myvar.std_name||' '||myvar.marks );
if(myvar.std_id=mycur.std_id) then
update students set marks=myvar.marks+mycur.grace_marks;
end if;
END LOOP;
CLOSE stdcur;
CLOSE gracer;
END;
I'm not sure this will ever work. You're fetching a row of each resultset in a row variable, assuming that row 1 for the first resultset will match the std_id of row 1 for the 2nd resultset. There are 2 things wrong with this assumption
There is no ORDER BY clause in the statements, so std_id for the firs row of cursor stdcur could be 10 while std_id for the first row of cursor gracer could be 99. Oracle does not guarantee the order of the a resultset unless an ORDER BY clause is included in the statement.
If table student_grace_marks has more than 1 row, or no rows for a std_id, it will start failing from that row onwards since the count will no longer match.
One solution is to use explicit cursor for loops:
DECLARE
BEGIN
FOR r_student IN (SELECT * FROM students) LOOP
FOR r_student_gm IN (SELECT * FROM student_grace_marks WHERE std_id = r_student.std_id) LOOP
UPDATE students SET marks = marks + r_student_gm.grace_marks WHERE std_id = r_student.std_id;
END LOOP;
END LOOP;
END;
/
.. or .. if you want to use explicit cursors:
In this case the CURSOR gracer will have a where clause to only select the relevant row(s) for that particular student.
Note that I fixed some errors as well
DECLARE
--declare variable to be used in the where clause of the select for cursor gracer.
l_std_id students.std_id%TYPE;
CURSOR stdcur IS SELECT std_id,std_name, marks FROM students;
CURSOR gracer IS SELECT std_id,grace_marks from student_grace_marks WHERE std_id = l_std_id;
myvar stdcur%ROWTYPE;
mycur gracer%ROWTYPE;
BEGIN
OPEN stdcur;
LOOP
FETCH stdcur INTO myvar;
EXIT WHEN stdcur%NOTFOUND;
l_std_id := myvar.std_id;
OPEN gracer;
LOOP
FETCH gracer INTO mycur;
EXIT WHEN gracer%NOTFOUND;
-- use marks, not myvar.marks. If there is >1 record in student_grace_marks it will only add the last value.
-- add the where clause or every update will update all rows and every grace_marks will be added for every student in the table.
UPDATE students set marks= marks+mycur.grace_marks WHERE std_id = l_std_id;
END LOOP;
CLOSE gracer;
END LOOP;
CLOSE stdcur;
END;
/

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;

Oracle, how to open cursor and select one column of many into a variable

I have a Oracle stored procedure returning a reference cursor. I want to open the cursor before i return it to check a count and throw an exception if need be, but im having trouble with syntax and how im supposed to do this.
V_ASN_COUNT NUMBER;
OPEN O_CURSOR FOR
SELECT column1, -- a bunch of columns
column2,
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
OPEN O_CURSOR;
LOOP
FETCH ASN_COUNT INTO V_ASN_COUNT;
END LOOP;
CLOSE O_CURSOR;
IF(V_ASN_COUNT > 1) THEN
RAISE MULTIPLE_ASNS;
END IF;
I think you can do this one:
curid NUMBER;
desctab DBMS_SQL.DESC_TAB;
colcnt NUMBER; -- total number of columns
res NUMBER;
V_ASN_COUNT NUMBER;
BEGIN
OPEN O_CURSOR FOR
SELECT
column1, -- a bunch of columns
column2,
...
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
curid := DBMS_SQL.TO_CURSOR_NUMBER (O_CURSOR);
DBMS_SQL.DESCRIBE_COLUMNS(curid, colcnt, desctab);
-- "ASN_COUNT" is the last column, i. e. "colcnt" refers to column number of "ASN_COUNT"
-- or set colcnt directly, e.g. colcnt := 12;
FOR i IN 1..colcnt LOOP
IF desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, V_ASN_COUNT);
ELSIF desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, datevar);
.......
ELSE
DBMS_SQL.DEFINE_COLUMN(curid, i, namevar, 25);
END IF;
END LOOP;
-- I do not know if this loop is needed, perhaps you can simply do
-- DBMS_SQL.DEFINE_COLUMN(curid, colcnt, V_ASN_COUNT);
-- for a single column
res := DBMS_SQL.FETCH_ROWS(curid); -- Fetch only the first row, no loop required
DBMS_SQL.COLUMN_VALUE(curid, colcnt, V_ASN_COUNT); -- Loop over all column not required, you just like to get the last column
IF V_ASN_COUNT > 1 THEN
RAISE MULTIPLE_ASNS;
END IF;
DBMS_SQL.CLOSE_CURSOR(curid);
For further details, check Oracle documentation: DBMS_SQL.TO_CURSOR_NUMBER Function.
However, the problem with open/rewinded cursor remains!
Following on from the previous question, if you wanted to open the same cursor multiple times to count over it, you could do something like this:
CREATE OR REPLACE PROCEDURE YOUR_PROC(O_CURSOR OUT SYS_REFCURSOR) is
ASN_NO NUMBER; -- have to define all columns the cursor returns
V_CHECK_ASN_NO NUMBER;
-- local function to generate the cursor, to avoid repeating the text
-- or using dynamic SQL
FUNCTION GET_CURSOR RETURN SYS_REFCURSOR IS
V_CURSOR SYS_REFCURSOR;
BEGIN
OPEN V_CURSOR FOR
SELECT *
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
-- where bunch of stuff
RETURN V_CURSOR;
END;
BEGIN
-- open the cursor for your check; might be better to have a local
-- variable for this rather than touching the OUT parameter this early
O_CURSOR := GET_CURSOR;
LOOP
FETCH O_CURSOR INTO ASN_NO; -- and all other columns!
EXIT WHEN O_CURSOR%NOTFOUND;
IF V_CHECK_ASN_NO IS NOT NULL AND V_CHECK_ASN_NO != ASN_NO THEN
-- means we have two distinct values
CLOSE O_CURSOR;
RAISE MULTIPLE_ASNS;
END IF;
V_CHECK_ASN_NO := ASN_NO;
END LOOP;
-- close the check version of the cursor
CLOSE O_CURSOR;
-- re-open the cursor for the caller
O_CURSOR := GET_CURSOR;
END YOUR_PROC;
You could open the cursor twice with the same SQL string using dynamic SQL, but this version uses a local function to make the cursor SQL static (and thus parsed at compile-time).
The cursor is executed twice, and at least some rows are fetched from the first execution (all rows if there are no duplicates; if there are duplicates then not all may be fetched). The caller gets a fresh result set containing all the rows.
How about making sure the first row is expendable for the validation?
This code will only open cursor once - no concurrency issue. The two first rows of the cursor both represent the first row of the intended result set - fetch first copy for validation and return the rest if validation succeeds.
You still have to fetch all the columns though.
V_ASN_COUNT NUMBER;
OPEN O_CURSOR FOR
WITH qry AS ( SELECT column1, -- a bunch of columns
column2,
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
)
SELECT *
FROM qry
WHERE rownum = 1
UNION ALL
SELECT *
FROM qry;
-- Consume the expendable first row.
FETCH O_CURSOR INTO V_ASN_COUNT; -- and all the other columns!
IF(V_ASN_COUNT > 1) THEN
CLOSE O_CURSOR;
RAISE MULTIPLE_ASNS;
END IF;

how inner cursor use a field of outer cursor in oracle?

I have two nested cursors. I mean one cursor Is Inside of another one.
I want to use a field of outer cursor inside the inner one. something like this:
Inner_cursor.outer_cursor.outer_cursor_column;
but It does not work even I use like this:
Inner_cursor.(outer_cursor.outer_cursor_column);
Is there any way I could do this?
EDIT:
This Is My Code:
CREATE OR REPLACE PROCEDURE TEST1
AS
CURSOR loop_relation IS
SELECT * FROM RELATION_table;
relation_rec loop_relation%rowtype;
CURSOR loop_BIG_TABLE IS
SELECT * FROM BIG_TABLE;
BIG_TABLE_rec loop_BIG_TABLE%rowtype;
BEGIN
FOR RELATION_REC IN LOOP_RELATION
LOOP
FOR BIG_TABLE_rec in loop_BIG_TABLE
LOOP
IF (BIG_TABLE_REC.RELATION_REC.DESTINATION_PK IS NULL) THEN
UPDATE BIG_TABLE
SET BIG_TABLE.RELATION_REC.DESTINATION_PK = (
SELECT RELATION_REC.SOURCE_FK FROM RELATION_REC.SOURCE_TABLE
WHERE RELATION_REC.SOURCE_PK = BIG_TABLE_REC.RELATION_REC.SOURCE_PK)
WHERE BIG_TABLE_REC.ID = BIG_TABLE.ID;
END IF;
END LOOP;
END LOOP;
END TEST1;
/
my problem is in the lines that i use three dot(.) to use a value of outer cursor in inner cursor.
Here's an example of two nested cursors and variables from the outer one used in the inner one. I hope it helps you.
BEGIN
FOR r_outer in (
select tab1.field1
from table1 tab1 )
LOOP
FOR r_inner in (
select tab2.field2
from table2 tab2
where tab2.field2 = r_outer.field1 )
LOOP
dbms_output.put_line(r_outer.field1);
dbms_output.put_line(r_inner.field2);
END LOOP;
END LOOP;
END;
For reference, I created a procedure to display how to use outer cursor value inside inner cursor value. I hope this resolves your query.
CREATE OR REPLACE PROCEDURE cur_inside_cur(my_cur OUT sys_refcursor)
AS
CURSOR roy_cur IS
SELECT name FROM avrajit;
roy_cur1 roy_cur%ROWTYPE;
BEGIN
OPEN roy_cur;
LOOP
FETCH roy_cur INTO roy_cur1;
EXIT WHEN roy_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(roy_cur1.name);
OPEN my_cur FOR
SELECT department FROM avrajit
WHERE name=roy_cur1.name;
END LOOP;
END cur_inside_cur;
OUTPUT
var c refcursor;
begin
cur_inside_cur(:c);
end;
print c;

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