Dynamic SQL LOOP - sql

Dynamic SQL is not my friend, basically the idea is that I can use the procedure with the "p_in_table" paramter to get the number of rows contained in the table.
CREATE OR REPLACE PROCEDURE how_many_rows(p_in_table VARCHAR2)
IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
BEGIN
v_cur_txt := 'SELECT * FROM ' || p_in_table;
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
dbms_output.put_line(v_rowcount);
END;
Would preciate it if someone would tell me what am I doing wrong?

The problem is that you not iterating through cursor - no fetch statement or something like that, so, basically, you have an infinite loop. To avoid this you need to do something like this:
CREATE OR REPLACE PROCEDURE how_many_rows
(p_in_table VARCHAR2) IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
v_row SOME_TABLE%ROWTYPE; --add row variable
BEGIN
v_cur_txt := 'SELECT * FROM '|| p_in_table;
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
FETCH v_cur_cur INTO v_row; --fetch a row in it
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
DBMS_OUTPUT.PUT_LINE(v_rowcount);
END;
But, as you can see, to do this you need to know, what table you're quering, so this is not general solution. Maybe there is a workaround for this, but i suggest, you use more simple and efficient approach, for example with EXECUTE IMMEDIATE:
CREATE OR REPLACE PROCEDURE HOW_MANY_ROWS(p_in_table VARCHAR2)
IS
v_tmp NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(1) FROM ' || p_in_table INTO v_tmp;
DBMS_OUTPUT.PUT_LINE(v_tmp);
END;
Ok, I gave a thought on how to achieve this using your way, and here is what i've ended up with - just fetch ROWNUM from your table, every table has it and you know it's type - NUMBER. So this procedure will work in general case:
CREATE OR REPLACE PROCEDURE how_many_rows
(p_in_table VARCHAR2) IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
v_row NUMBER; --add rownum variable
BEGIN
v_cur_txt := 'SELECT ROWNUM FROM '|| p_in_table; --select only rownum from target table
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
FETCH v_cur_cur INTO v_row; --fetch rownum in it
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
DBMS_OUTPUT.PUT_LINE(v_rowcount);
END;

Related

how to check if SYS_REFCURSOR is empty

I have a general SYS_REFCURSOR that I want to check if it is empty or not.
The code is like this:
declare
v_cursor SYS_REFCURSOR;
begin
OPEN v_cursor FOR <any select statement>.
check if v_cursor is empty.
end;
Can someone tell me how to check if the weak cursor is empty, please?
I have to mention that the base SELECT statement can be anything from any table.
The column numbers or type it is known only at runtime.
Thank you,
You can't see if a ref cursor contains data without fetching, and consuming at least one row from it.
If you really need to determine this at the point the cursor is opened, without knowing the structure at that point, you could execute a modified query that just counts the rows returned by your real query - possibly limited to a single row if that helps performance - either as a simple execute immediate ... into ... or with a separate open/fetch/close for consistency; something like:
declare
v_cursor SYS_REFCURSOR;
v_query VARCHAR2(4000);
v_count PLS_INTEGER;
begin
v_query := <any select statement>;
-- see if the query finds any data
OPEN v_cursor FOR 'select count(*) from (' || v_query || ')'; -- could limit rows
FETCH v_cursor INTO v_count;
CLOSE v_cursor;
if v_count = 0 then
dbms_output.put_line('No data');
return;
end if;
dbms_output.put_line('Found data, opening cursor for real');
OPEN v_cursor FOR v_query;
-- loop over results, return to caller, etc.
end;
/
db<>fiddle
Try to fetch a row and then use v_cursor%NOTFOUND to determine of the cursor is empty:
DECLARE
v_cursor SYS_REFCURSOR;
v_value DUAL.DUMMY%TYPE;
BEGIN
OPEN v_cursor FOR SELECT DUMMY FROM DUAL WHERE 1 = 0;
FETCH v_cursor INTO v_value;
IF v_cursor%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('Cursor empty');
ELSE
DBMS_OUTPUT.PUT_LINE('Cursor not empty');
END IF;
END;
/
or
DECLARE
CURSOR v_cursor IS
SELECT DUMMY FROM DUAL WHERE 1 = 0;
v_row v_cursor%ROWTYPE;
BEGIN
OPEN v_cursor;
FETCH v_cursor INTO v_row;
IF v_cursor%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('Cursor empty');
ELSE
DBMS_OUTPUT.PUT_LINE('Cursor not empty');
END IF;
END;
/
Both output:
Cursor empty
db<>fiddle here

How to write Execute immediate in cursor select Query

How to wite EXECUTE IMMEDIATE in the cursor select Query.
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from table X where c1='|| in_from_date;
CURSOR cur_attendee_data
IS
EXECUTE IMMEDIATE l_cur_query;
TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
INDEX BY PLS_INTEGER;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data;
LOOP
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
EXIT WHEN l_cur_attendee_data.COUNT = 0;
DBMS_OUTPUT.put_line ('here in first insert');
lrec := return_attendee_report ();
out_attendee_tab :=
return_attendee_arr_result (return_attendee_report ());
out_attendee_tab.DELETE;
FOR i IN 1 .. l_cur_attendee_data.COUNT
LOOP
BEGIN
NULL;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error occurred : ' || SQLERRM);
END;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('HERE INSIIDE OTHERS' || SQLERRM);
END;
here I try to use initialize cursor outside begin block but it rises exception, how to write a dynamic query for the cursor,
cur_attendee_data is
EXECUTE IMMEDIATE l_cur_query;
Error(113,8): PLS-00103: Encountered the symbol "CUR_ATTENDEE_DATA" when expecting one of the following: := . ( # % ;
If you fetch all rows with BULK COLLECT INTO ... then you need only one loop, the second loop is useless.
The basic solution would be this one:
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE) IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from {table X} where c1=:d';
cur_attendee_data SYS_REFCURSOR;
TYPE rec_attendee_data IS TABLE OF {table X}%ROWTYPE;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR l_cur_query USING in_from_date;
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
FOR i IN 1 .. l_cur_attendee_data.COUNT LOOP
-- do whatever you like to do with l_cur_attendee_data(i)
END LOOP;
CLOSE cur_attendee_data;
END;
However, I don't see any reason to make dynamic SQL. You can simply run
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE) IS
cur_attendee_data SYS_REFCURSOR;
TYPE rec_attendee_data IS TABLE OF {table X}%ROWTYPE;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR SELECT * from {table X} where c1 = in_from_date;
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
FOR i IN 1 .. l_cur_attendee_data.COUNT LOOP
-- do whatever you like to do with l_cur_attendee_data(i)
END LOOP;
CLOSE cur_attendee_data;
END;
You can use below code instead -
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (100) := 'SELECT * from table X where c1=:in_from_date';
TYPE t_cur IS REF CURSOR;
cur_attendee_data t_cur
TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
INDEX BY PLS_INTEGER;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR l_cur_query USING in_from_date;
LOOP
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
EXIT WHEN l_cur_attendee_data.COUNT = 0;
DBMS_OUTPUT.put_line ('here in first insert');
lrec := return_attendee_report ();
out_attendee_tab :=
return_attendee_arr_result (return_attendee_report ());
out_attendee_tab.DELETE;
FOR i IN 1 .. l_cur_attendee_data.COUNT
LOOP
BEGIN
NULL;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error occurred : ' || SQLERRM);
END;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('HERE INSIIDE OTHERS' || SQLERRM);
END;
You need to use open cursor for '' as following:
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from table X where c1='|| in_from_date;
cur_attendee_data SYS_REFCURSOR; -- JUST DECLARED THE CURSOR
--TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
-- INDEX BY PLS_INTEGER; -- this declaration must be at schema level
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data for l_cur_query; -- OPEN THE CURSOR WITH DYNAMIC QUERY
..
.. -- YOUR CODE AS IT IS
..
Cheers!!

how to get value of column from sys_refcursor returned by a function being called

I am calling a function pck_my_pack.f_my_func in my anonymous block , f_my_func returns a sys_refcursor.
I want the value of req_column from sys_refcursor to used as
IF req_column>0 THEN
do this and that
END IF;
Problem is sys_refcursor which is being returned by my_func does not select values from a specific table. it selects values from multiple tables and few locals variables which were calculated in my_func.
Please help me out to get the value from this sys_refcursor.
Here is what I am trying:
DECLARE
TYPE cu_income_detail is ref cursor;
income_det cu_income_detail;
BEGIN
income_det := pck_my_pack.f_my_func(2542586);
FOR CURRVAL IN income_det LOOP -- I have also tried CUR_VAL
IF CURRVAL.pcls_paid_as_income > 0 THEN
-- Logic yet to be implemented once get the value
dbms_output.put('PCLS paid');
dbms_output.put_line(CUR_VAL.pcls_paid_as_income);
END IF;
END LOOP;
END;
declare
src_cur sys_refcursor;
curid NUMBER;
v_jobtitle varchar2(35);
v_min_salary number;
begin
open src_cur for 'select job_id,job_title,min_salary,max_salary from jobs';
curid := DBMS_SQL.TO_CURSOR_NUMBER(src_cur);
dbms_sql.define_column(curid,2,v_jobtitle,35);
dbms_sql.define_column(curid,3,v_min_salary);
WHILE DBMS_SQL.FETCH_ROWS(curid) > 0 LOOP
DBMS_SQL.COLUMN_VALUE(curid, 2, v_jobtitle);
DBMS_SQL.COLUMN_VALUE(curid, 3, v_min_salary);
DBMS_OUTPUT.put_line('job_titile='||v_jobtitle||' ;min_salary='||v_min_salary);
END LOOP;
DBMS_SQL.CLOSE_CURSOR(curid);
end;
You have to know type and name of columns in your sys_refcursor.
And perform fetch into operation on it.
DECLARE
TYPE cu_income_detail is ref cursor;
income_det cu_income_detail;
BEGIN
income_det := pck_my_pack.f_my_func(2542586);
loop
fetch income_det into var_1, var_2 ... var_3;
-- Logic here
exit when income_det%notfound;
end loop;
END;

Dynamic Declaration of Cursor or Record

Can we dynamically declare a cursor or record?
CURSOR employee_id_cur
IS
select * from <user entry table name>
Similar for the record.
Please refer following example:
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
emp_cv EmpCurTyp;
emp_rec emp%ROWTYPE;
sql_stmt VARCHAR2(200);
my_job VARCHAR2(15) := 'CLERK';
BEGIN
sql_stmt := 'SELECT * FROM emp WHERE job = :j';
OPEN emp_cv FOR sql_stmt USING my_job;
LOOP
FETCH emp_cv INTO emp_rec;
EXIT WHEN emp_cv%NOTFOUND;
-- process record section.....
END LOOP;
CLOSE emp_cv;
END;
/

How to add a table name in EXECUTE IMMEDIATE query?

I have one question about "EXECUTE IMMEDIATE".
I have dynamicly changed table name in next plsql statement
DECLARE
TYPE CurTyp IS REF CURSOR;
cur CurTyp;
str1 VARCHAR2(30);
str2 VARCHAR2(30);
table_name VARCHAR2(30);
BEGIN
select data
into table_name
from ref
where o_id = 111
and a_id = 222;
OPEN cur FOR
'select name, sname from :1 b,myobjects a where a.obj_id = b.obj_id'
USING table_name;
LOOP
FETCH cur INTO str1, str2;
EXIT WHEN cur%NOTFOUND;
dbms_output.put_line(str1||str2);
END LOOP;
CLOSE cur;
END
Is it possible to read the result of next Execute Immediate query to cursor?
'select name, sname from :1 b,myobjects a where a.obj_id = b.obj_id'
USING table_name;
Or maybe is there any way to do this?
Thanks in advance.
For object names you must use concatenation, not bind variables.
From the Dynamic SQL chapter of the PL/SQL Language Reference:
The database uses the values of bind variables exclusively and does
not interpret their contents in any way.
Bind variables help with security and performance. But they won't work with objects like tables. If you pass in a table name then Oracle must interpret the contents, which will negate the security and performance benefits.
You can use ref_cursor
see http://docs.oracle.com/cd/B10500_01/appdev.920/a96590/adg09dyn.htm
the example:
CREATE OR REPLACE PROCEDURE query_invoice(
month VARCHAR2,
year VARCHAR2) IS
TYPE cur_typ IS REF CURSOR;
c cur_typ;
query_str VARCHAR2(200);
inv_num NUMBER;
inv_cust VARCHAR2(20);
inv_amt NUMBER;
BEGIN
query_str := 'SELECT num, cust, amt FROM inv_' || month ||'_'|| year
|| ' WHERE invnum = :id';
OPEN c FOR query_str USING inv_num;
LOOP
FETCH c INTO inv_num, inv_cust, inv_amt;
EXIT WHEN c%NOTFOUND;
-- process row here
END LOOP;
CLOSE c;
END;
/
but as #jonearles said, you can't insert the table names as params