Novice to DB - Oracle - sql

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;

Related

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

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

postgresql iterate n records

I have table student_list it contains student_id only one column and 100 rows.
i need to fetch 10-10 records concurrently and then perform some operation.
CREATE OR REPLACE FUNCTION loop_fetch()
RETURNS void AS
$BODY$
DECLARE
myrow student_list%rowtype;
cur1 CURSOR FOR SELECT * FROM student_list ;
BEGIN
OPEN cur1;
LOOP
-- i need to fetch rows based on limit
FETCH NEXT 10 FROM cur1 INTO myrow;
exit when myrow IS NULL;
INSERT INTO new_tbl SELECT myrow.student_id ;
END LOOP;
CLOSE cur1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Any suggestion to achive this method - FETCH NEXT 5 FROM cur1 INTO myrow
Your code should not to work. FETCH NEXT 10 takes 10 rows from cursor, but INTO clause takes only first and other are lost. MyRow is composite variable - it can hold only one row.
I got a error:
ERROR: FETCH statement cannot return multiple rows
LINE 6: fetch 10 from r into re;
^
It is correct result.
The short and probably most correct solution is using FOR IN SELECT. This statement uses cursors internally, and it fetch 10 rows in first iteration, and 50 in other iterations.
So:
DECLARE r record;
BEGIN
FOR r IN SELECT * FROM student_list
LOOP
INSERT INTO newtbl VALUES(r.*);
END LOOP;
END;
does almost all what you want.
If you want to use cursors explicitly (and really you want to fetch in block), you need to use more cycles. The following code is little bit strange, and I write it here only for education - don't think so it has any benefit for practical life.
CREATE OR REPLACE FUNCTION loop_fetch()
RETURNS void AS
$BODY$
DECLARE
myrow student_list%rowtype;
cur1 CURSOR FOR SELECT * FROM student_list ;
rows int DEFAULT 0;
BEGIN
LOOP
-- i need to fetch rows based on limit
FOR myrow IN cur1
LOOP
INSERT INTO new_tbl SELECT myrow.student_id ;
END LOOP;
EXIT WHEN NOT FOUND;
rows := rows + 1;
EXIT WHEN rows = 10;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
This code should to work, but it is crazy.
or you can use use FOR IN EXECUTE
do $$
declare
c cursor for select * from generate_series(1,100);
r record;
begin
-- *** UGLY CODE, DON'T DO IT!!! ***
open c;
-- iteration over FETCH is possible only via
-- dynamic SQL. FOR statement uses FETCH internally
-- by default, and is nonsense use FETCH 2x
for r in execute 'fetch 10 from c'
loop
raise notice '%', r;
end loop;
close c;
-- *** UGLY CODE, DON'T DO IT!!! ***
end;
$$;
Internally FOR IN query use a cursor. So FOR IN EXECUTE FETCH is reading via cursor from another cursor, what is performance nonsense and it really ugly code.
Important things - PostgreSQL has not tabular variables - so you cannot to assign more rows to one variable, and you cannot to fill more rows from one variable.
But your request looks like premature optimization. The most fast and most effective is command:
INSERT INTO newtbl SELECT studentId FROM student_list
SQL can process very well massive operations. 1M rows is nothing. Use cursors only when you really really need it.

How to execute results of dbms_output.put_line

There is a table contains this kind of data: select to_char(sysdate,'day') from dual in a column. I want to get results of the every query that the table keeps.
My result set should be the result of select to_char(sysdate,'day') from dual query. So in this case it is a tuesday.
SO_SQL_BODY is Varchar2.
I wrote this code but it returns only table data.
CREATE or replace PROCEDURE a_proc
AS
CURSOR var_cur IS
select SO_SQL_BODY FROM SO_SUB_VARS group by SO_SQL_BODY;
var_t var_cur%ROWTYPE;
TYPE var_ntt IS TABLE OF var_t%TYPE;
var_names var_ntt;
BEGIN
OPEN var_cur;
FETCH var_cur BULK COLLECT INTO var_names;
CLOSE var_cur;
FOR indx IN 1..var_names.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(var_names(indx).SO_SQL_BODY);
END LOOP;
END a_proc;
DECLARE
res varchar2(4000);
sql_str varchar2(1000);
BEGIN
FOR r IN
(select SO_SQL_BODY FROM SO_SUB_VARS WHERE SO_SQL_BODY IS NOT NULL
)
LOOP
sql_str := r.SO_SQL_BODY;
EXECUTE immediate sql_str INTO res;
dbms_output.put_line(sql_str);
dbms_output.put_line('***********************');
dbms_output.put_line(res);
dbms_output.put_line('***********************');
END LOOP;
END;
/
Try this - iterate to not null records - execute them and print the result.This script works supposing the fact that SO_SQL_BODY contains a query which projects only one column.Also if the projection is with more than two columns then try to use a refcursor and dbms_sql package
İf var_names(indx).SO_SQL_BODY output is a runnable sql text;
CREATE or replace PROCEDURE a_proc
AS
CURSOR var_cur IS
select SO_SQL_BODY FROM SO_SUB_VARS group by SO_SQL_BODY;
var_t var_cur%ROWTYPE;
TYPE var_ntt IS TABLE OF var_t%TYPE;
var_names var_ntt;
BEGIN
OPEN var_cur;
FETCH var_cur BULK COLLECT INTO var_names;
CLOSE var_cur;
FOR indx IN 1..var_names.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(var_names(indx).SO_SQL_BODY);
EXECUTE IMMEDIATE var_names(indx).SO_SQL_BODY;
END LOOP;
END a_proc;
You don't need a full cursor for this example. An implicit one would make it a lot shorter.
create or replace procedure a_proc is
lReturnValue varchar2(250);
begin
for q in (select so_sql_body from so_sub_vars group by so_sql_body)
loop
execute immediate q.so_sql_body into lReturnValue;
dbms_output.put_line(lReturnValue);
end loop;
end a_proc;
You should add an exception handler that will care for cases where there is a bad SQL query in your table. Also note that executing querys saved in a database table is your entry point to SQL injection.

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;

How to repeat Select statements in a loop in Oracle?

I am reading a lot of stuff about repeating Select statements within a loop but I am having some difficulties as I have not found something clear till now. I want to execute some queries (Select queries) several times, like in a FOR loop. Can anyone help with some example please?
The basic structure of what you are asking can be seen below.
Please provide more information for a more specific code sample.
DECLARE
l_output NUMBER;
BEGIN
FOR i IN 1..10 LOOP
SELECT 1
INTO l_output
FROM dual;
DBMS_OUTPUT.PUT_LINE('Result: ' || l_output);
END LOOP;
END;
PS: If you need to enable output in SQL*Plus, you may need to run the command
SET SERVEROUTPUT ON
UPDATE
To insert your results in another table:
DECLARE
-- Store the SELECT query in a cursor
CURSOR l_cur IS SELECT SYSDATE DT FROM DUAL;
--Create a variable that will hold each result from the cursor
l_cur_rec l_cur%ROWTYPE;
BEGIN
-- Open the Cursor so that we may retrieve results
OPEN l_cur;
LOOP
-- Get a result from the SELECT query and store it in the variable
FETCH l_cur INTO l_cur_rec;
-- EXIT the loop if there are no more results
EXIT WHEN l_cur%NOTFOUND;
-- INSERT INTO another table that has the same structure as your results
INSERT INTO a_table VALUES l_cur_rec;
END LOOP;
-- Close the cursor to release the memory
CLOSE l_cur;
END;
To create a View of your results, see the example below:
CREATE VIEW scott.my_view AS
SELECT * FROM scott.emp;
To view your results using the view:
SELECT * FROM scott.my_view;