How to fetch into collection using ref cursor, when the record count in table is a multiple of the limit number specified? - sql

create table with id column
CREATE TABLE TEST_TAB
(ID NUMBER
);
create type
CREATE type numbertabletype IS TABLE OF NUMBER;
insert 100 records
INSERT INTO TEST_TAB
SELECT LL FROM
(SELECT LEVEL LL FROM DUAL CONNECT BY LEVEL<=100
);
Create function that loops through 100 records in 10 loops, cursor will fetch into numbertable type collection with LIMIT 10.
CREATE OR REPLACE FUNCTION LOOP_TEST
RETURN NUMBERTABLETYPE
IS
lv_coll NUMBERTABLETYPE ;
LV_COUNT NUMBER:=0;
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=100;
BEGIN
OPEN c1;
LOOP
dbms_output.put_line('BEFORE FETCH CURSOR COUNT '||C1%ROWCOUNT);
FETCH c1 bulk collect INTO lv_coll limit 10;
dbms_output.put_line('AFTER FETCH CURSOR COUNT '||C1%ROWCOUNT);
EXIT
WHEN c1%NOTFOUND;
LV_COUNT:=LV_COUNT+1;
dbms_output.put_line(' BELOW NOT FOUND '||LV_COUNT);
dbms_output.put_line('COLLECTION COUNT '||lv_coll.count);
END LOOP;
CLOSE c1;
RETURN lv_coll;
END;
/
Run sql as script, it returns null when rownum = 100
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=100;
Run sql as script, it returns values when rownum = 99, replace cursor c1 in function with below cursor.
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=99;
so How do you tackle a scenario when the limit is a multiple of total records to loop.

The code is working perfectly; it is just not doing what you expect.
When the code loops it will overwrite the lv_coll collection each time so that it will contain no more than 10 items.
When it fetches the 91-100th rows it fills the collection with 10 items and processes it but, because it has not tried to read another row, it is unaware that there are no more rows left in the cursor and has not yet reached the c1%NOTFOUND condition to terminate the loop.
When it repeats the loop, after that, it will find that the cursor has now been exhausted and will read zero rows. So, in this final loop, the lv_coll collection is not NULL but is a collection containing zero elements and that is what is going to get returned.
Compare this to when there are only 99 rows in the cursor. When the loop tries to read the 91st - 100th rows it will read the 91st - 99th rows and try to read the 100th but will find that the cursor has been exhausted and c1%NOTFOUND will be true exiting the loop and causing the collection to be returned with only 9 items.
If you want to return all the elements from the cursor then you will need to use a second collection to aggregate them as the one populated from the cursor will be overwritten at each loop:
CREATE OR REPLACE FUNCTION LOOP_TEST
RETURN NUMBERTABLETYPE
IS
lv_coll NUMBERTABLETYPE;
all_items NUMBERTABLETYPE := NUMBERTABLETYPE();
LV_COUNT NUMBER:=0;
ids VARCHAR2(30);
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=100;
BEGIN
OPEN c1;
LOOP
dbms_output.put_line('BEFORE FETCH CURSOR COUNT '||C1%ROWCOUNT);
FETCH c1 bulk collect INTO lv_coll limit 10;
dbms_output.put_line('AFTER FETCH CURSOR COUNT '||C1%ROWCOUNT);
EXIT WHEN c1%NOTFOUND;
LV_COUNT:=LV_COUNT+1;
all_items := all_items MULTISET UNION ALL lv_coll;
dbms_output.put_line(' BELOW NOT FOUND '||LV_COUNT);
dbms_output.put_line('COLLECTION COUNT '||lv_coll.count);
-- SELECT LISTAGG(COLUMN_VALUE,',') WITHIN GROUP( ORDER BY ROWNUM )
-- INTO ids
-- FROM TABLE(lv_coll);
-- dbms_output.put_line('IDS: '||ids);
END LOOP;
CLOSE c1;
RETURN all_items;
END;
/
Which can be called using:
DECLARE
ids numbertabletype;
vals VARCHAR2(3000);
BEGIN
ids := LOOP_TEST();
DBMS_OUTPUT.PUT_LINE( 'NUMBER OF IDs: ' || CASE WHEN ids IS NULL THEN 'NULL' ELSE TO_CHAR( ids.COUNT ) END );
SELECT LISTAGG(COLUMN_VALUE,',') WITHIN GROUP( ORDER BY ROWNUM )
INTO vals
FROM TABLE(ids);
DBMS_OUTPUT.PUT_LINE( 'values: ' || vals );
END;
/
db<>fiddle here

I tried to run your case in an anonymous block and it failed to output in case of ROWNUM<=100.
I changed the little bit in your code and it worked.
--
DECLARE
lv_coll NUMBERTABLETYPE ;
lv_col2 NUMBERTABLETYPE ; -- added this line to hold the data
LV_COUNT NUMBER:=0;
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=100;
BEGIN
OPEN c1;
LOOP
--dbms_output.put_line('BEFORE FETCH CURSOR COUNT '||C1%ROWCOUNT);
FETCH c1 bulk collect INTO lv_coll limit 10;
EXIT WHEN lv_coll.COUNT = 0; -- added this line
lv_col2 := lv_coll; -- added this line
--dbms_output.put_line('AFTER FETCH CURSOR COUNT '||C1%ROWCOUNT);
-- EXIT
--WHEN c1%NOTFOUND;
-- LV_COUNT:=LV_COUNT+1;
--dbms_output.put_line(' BELOW NOT FOUND '||LV_COUNT);
--dbms_output.put_line('COLLECTION COUNT '||lv_coll.count);
END LOOP;
CLOSE c1;
--RETURN lv_coll;
FOR I IN 1..lv_col2.COUNT LOOP -- added this line
dbms_output.put_line(lv_col2(I)); -- added this line
END LOOP;
END;
/
db<>fiddle demo

Related

Oracle: Iterate over table and print out an xml file for every row name

I have created a table where, for example, there is a id number in each row, but it is not continuous (1,2,4,6,7). Now I want to create an XML file for each number, which also contains certain other data, but which are not important for the question.
My question now is how do I set up this for loop to pull out the number one by one and transfer it to my PL SQL? And i need to write an export date into the table after the export. Thanks for your help.
Table
id_order | export date
This is my code for the xml files and now i need a loop for the id_order Number:
declare
fhandle utl_file.file_type;
-- Output Variablen
output_intro varchar2(23000);
ID_order NUMBER := 1;
filename varchar2(100) := 'test.xml';
-- Intro
BEGIN
fhandle := utl_file.fopen('EXPORT', filename , 'w');
begin
SELECT
'<"ID">'|| ID ||'</ID>' || CHR(10)
into output_intro FROM Table
WHERE ID = ID_order;
exception
when others THEN
output_intro := 'Issue Intro';
end;
BEGIN
utl_file.put(fhandle, output_intro);
END;
utl_file.fclose(fhandle);
exception
when others then
dbms_output.put_line('ERROR: ' || SQLCODE
|| ' - ' || SQLERRM);
raise;
end;
/
If the purpose is to simnply return the 'ID' value for each row as XML, you should look at the SQLXML functions.
In your case, try this:
SELECT XMLFOREST(id) FROM table
It handles multiple columns easily:
SELECT XMLFOREST(id, col2, col3,) FROM table
To nest this between 'Data' element:
SELECT XMLELEMENT( "Data", XMLFOREST(id) ) FROM table
Putting it all together:
DECLARE
l_file UTL_FILE.FILE_TYPE;
l_xmltype XMLType;
BEGIN
SELECT XMLELEMENT( "Data", XMLFOREST(id) )
INTO l_xmltype
FROM table;
l_file := UTL_FILE.fopen ('EXPORT', 'output.xml', 'w');
UTL_FILE.PUT_LINE(l_file , l_xmltype.getStringVal( ));
UTL_FILE.fclose (l_file);
END;
/
Note if the XML size is greater than 32K then you will need to write tout the the file in chunks.
You can use a cursor to create a loop
-- Declare a cursor
Cursor Cur is select * from YourTable where YourCondition;
Row_ Cur%rowtype;
begin
-- Open the cursor
Open Cur;
-- Loop over the cursor rows
loop
-- Fetch every into the object Row_
fetch cur into Row_;
-- Exit when no rows found
exit when Cur%notfound;
-- Perform the export to a file here
--You can access Row_ columns by appending columns to Row_. For example : Row_.FirstName
end loop;
Close Cur;
end;

Find total count from a sql query

I have a sql query with where condition as ROWNUM=10. And the query result i store it in one GTT table. But it is possible that the sql query is fetching more records than the mentioned WHERE condition i.e. ROWNUM=10.
So i wanted to know, is the query fetching >10 records are not.
This i can achieve by executing the same query twice i.e. once time to know the count and second time to insert the records into the gtt table.
But it is not an good idea to run the query twice.
So can any one help me to find the count of the sql query with out executing it twice.
If you are inserting those records in the GTT table, and you want to know how many rows you have selected/inserted, you could use SQL%ROWCOUNT
Begin
INSERT INTO GTT_TABLE
SELECT *
FROM QUERY_VIEW
WHERE Condition() = '1';
If SQL%ROWCOUNT > 10 Then
dbms_output.put_line('Query returns ' || SQL%ROWCOUNT || ' rows.');
End if;
End;
You can use the solution found at https://stackoverflow.com/a/17206119/7676742 to get records and count of these records together.
SELECT COUNT(*) OVER (), c.*
FROM CUSTOMER c
WHERE c.Name like 'foo%';
You could open a cursor for the query without the rownum condition and fetch until you run out of data or hit an 11th row:
declare
l_query varchar2(4000) := '<your query without rownum condition>';
l_counter pls_integer := 0;
l_cursor sys_refcursor;
l_row gtt%rowtype;
begin
open l_cursor for l_query;
loop
fetch l_cursor into l_row;
exit when l_cursor%notfound;
l_counter := l_counter + 1;
if l_counter > 10 then
dbms_output.put_line('Query got more than 10 rows');
exit;
end if;
-- first 1-10 rows so insert
insert into gtt values l_row;
end loop;
end;
/
Or with a collection to make it slightly more efficient:
declare
l_query varchar2(4000) := '<your query without rownum condition>';
l_cursor sys_refcursor;
type t_tab is table of gtt%rowtype;
l_tab t_tab;
begin
open l_cursor for l_query;
-- fetch gets at most 10 rows
fetch l_cursor bulk collect into l_tab limit 10;
-- up to 10 rows found are inserted
forall i in 1..l_tab.count
insert into gtt values l_tab(i);
-- fetch again, if it finds anything then total row count > 10
fetch l_cursor bulk collect into l_tab limit 1;
if l_cursor%found then
dbms_output.put_line('Query got more than 10 rows');
end if;
close l_cursor;
end;
/
However, the optimiser can often use a rownum condition to reduce the work it has to do (via a stopkey which you can see in the execution plan). It might still be faster and more efficient to run the query twice, once with the 10-row limit for the insert, and again with an 11-row limit to just get the count, and see if that is 11 or not. You should test both approaches to see which is actually better for you data. (And any others that are suggested, of course!)

Output multiple rows of data from cursor using if statement PL/SQL

I am trying to output multiple rows of data from a table called 'student' using pl/sql. I am able to output the first row of data but that is all. I am familiar with loop procedures in other programming languages but am new to pl/sql here is what I have:
DECLARE
variable_Score Number;
variable_LetterGrade Char(1);
name char(10);
cursor studentPtr is SELECT name, grade from STUDENT;
BEGIN
open studentPtr;
LOOP
fetch studentPtr into name, variable_Score;
IF variable_Score >= 90 THEN
variable_LetterGrade := 'A';
ELSIF variable_Score >= 80 THEN
variable_LetterGrade := 'B';
ELSIF variable_Score >= 70 THEN
variable_LetterGrade := 'C';
ELSIF variable_Score >= 60 THEN
variable_LetterGrade := 'D';
ELSE
variable_LetterGrade := 'F';
END IF;
DBMS_OUTPUT.PUT_LINE('Hello '||name||', you receive a '||variable_LetterGrade||' for the class');
EXIT;
END LOOP;
END;
/
The output is :
Hello Joe , you receive a B for the class
This is just the first row from the student table though, there is three more rows of data I would like to output.
Problem is with the EXIT statement. A bare exit statement skips the loop and comes out displaying the row from first fetch.
So, remove the EXIT; before END LOOP and include an EXIT statement like this after your fetch.
..
fetch studentPtr into name, variable_Score;
EXIT WHEN studentPtr%NOTFOUND;
..
..
%NOTFOUND is a cursor attribute and becomes true when all rows are fetched.
what the statement does is that it skips the loop only when no rows could be fetched from the cursor.

Novice to DB - Oracle

I'm pretty new to Oracle and Database.
I'm trying to write a stored procedure with cursors. How do I write a select statement inside the cursor loop, and how do I loop through the result set that I get from the select inside that cursor loop?
For example:
Open Curs1;
Exit When Curs1%NotFound;
Loop
Select column1,column2 from table -- Returns multiple records. How to loop through this record set and perform CRUD operations.
End loop;
Close Curs1;
Use a FOR-loop cursor - they are faster and simpler than the open/fetch/close syntax.
begin
for results1 in
(
select ...
) loop
--Do something here
for results2 in
(
select ...
where some_table.some_column = results1.some_column
) loop
--Do something here
end loop;
end loop;
end;
/
Although this literally answers the question, you generally do not want to have loops inside loops like this. If possible, it would be better to combine the two SQL statements with a join and then loop through the results.
Try to use the following example that fetches rows one at a time from the cursor variable emp_cv into the user-defined record emp_rec:
declare
TYPE YourType IS ref cursor return YourTable%rowtype;
tab_cv YourType;
tab_rec YourTable%rowtype;
begin
loop
fetch tab_cv into emp_rec;
exit when tab_cv%notfound;
...
end loop;
end;
The BULK COLLECT clause lets you fetch entire columns from the result set, or the entire result set at once. The following example, retrieves columns from a cursor into a collection:
declare
type NameList IS table of emp.ename%type;
names NameList;
cursor c1 is select ename from emp where job = 'CLERK';
begin
open c1;
fetch c1 bulk collect into names;
...
close c1;
end;
The following example uses the LIMIT clause. With each iteration of the loop, the FETCH statement fetches 100 rows (or less) into index-by table acct_ids. The previous values are overwritten.
declare
type NumList is table of number index by binary_integer;
cursor c1 is select acct_id from accounts;
acct_ids NumList;
rows natural := 100; -- set limit
begin
open c1;
loop
/* The following statement fetches 100 rows (or less). */
fetch c1 bulk collect into acct_ids limit rows;
exit when c1%notfound;
...
end loop;
close c1;
end;
You need to declare the CURSOR and FETCH the records in the loop.
DECLARE
CURSOR curs1
IS
SELECT column1,
column2
FROM yourtable;
v_column1 yourtable.column1%TYPE;
v_column2 yourtable.column2%TYPE;
BEGIN
OPEN curs1;
LOOP
FETCH curs1
INTO v_column1,
v_column2;
EXIT
WHEN curs1%NOTFOUND;
INSERT INTO yourtable2(col1)VALUES( '000'||v_column1 );
-- A sample DML operation.
--Do other operations on individual records here.
END LOOP;
CLOSE curs1;
END;

Print first 100 values using cursor in PL/SQL

I am trying to print the first 100 values for a field using cursor.I get an ORA-06550 error here though.Could someone tell me what is that I am missing .
Declare
BG_TOTAL number;
cursor c1 is
select BG_ID
from <tablename>;
Type BG_TAB_TYPE is table of c1%ROWTYPE;
BG_LIST BG_TAB_TYPE;
Begin
open c1;
FETCH c1 BULK COLLECT INTO BG_LIST;
close c1;
for i in 1..c1.count
loop
DBMS_OUTPUT.PUT_LINE(c1(i).BG_ID);
End loop;
end;
Yet another option is to use bulk collect with a limit.
This nicely separates the content, the limitation and the processing. The separation might not be an issue in your case but I have found this useful every now an then (helps me to write more modular code that's easy to test).
declare
-- data content
cursor tables_c is select * from all_tables;
type table_list_t is table of tables_c%rowtype;
v_table_list table_list_t;
begin
open tables_c;
-- limiting the data set
fetch tables_c bulk collect into v_table_list limit 8;
-- processing
for i in 1 .. v_table_list.count loop
dbms_output.put_line(v_table_list(i).table_name);
end loop;
close tables_c;
end;
/
You should loop through the nested table to which you bulk-collected records, not through the cursor. This is the corrected code:
Declare
BG_TOTAL number;
cursor c1 is
select BG_ID
from my_Tab524;
Type BG_TAB_TYPE is table of c1%ROWTYPE;
BG_LIST BG_TAB_TYPE;
Begin
open c1;
FETCH c1 BULK COLLECT INTO BG_LIST;
close c1;
for i in 1..BG_LIST.count
loop
DBMS_OUTPUT.PUT_LINE(BG_LIST(i).BG_ID);
EXIT WHEN i = 10;
End loop;
end;