Oracle PLSQL setting a cursor from a variable - oracle

Im new to cursors in Oracle. I have a piece of SQL that is contained in a variable. I want to open a cursor with this sql. How do I do this? Seems simple but all the examples I find just have the sql typed directly below the "open cursor_name for" statement.
Here is what I would like to run (assume I have variable v_sql with my sql query):
open my_cursor for v_sql;
Oracle doesnt like this though. I also tried
open my_cursor for
execute immediate v_sql;
Help please.

You need to declare it as a ref cursor and then open it for the your SQL statement. Please look at the example below. This, of course, is assuming you do not have any input bindings to your sql.
sql> ed
Wrote file afiedt.buf
1 declare
2 c1 sys_refcursor;
3 v_empno number;
4 v_ename varchar2(30);
5 begin
6 open c1 for 'select empno, ename from emp';
7 loop
8 fetch c1 into v_empno, v_ename;
9 dbms_output.put_line(v_empno || '--' || v_ename);
10 exit when c1%notfound;
11 end loop;
12 close c1;
13* end;
sql> /
7369--SMITH
7499--ALLEN
7521--WARD
7566--JONES
7654--MARTIN
7698--BLAKE
7782--CLARK
7788--SCOTT
7839--KING
7844--TURNER
7876--ADAMS
7900--JAMES
7902--FORD
7934--MILLER
7934--MILLER
Check this link...
http://download.oracle.com/docs/cd/B14117_01/appdev.101/b10807/11_dynam.htm#i13057

The first snippet you have will work fine, as long as v_sql is a VARCHAR and my_cursor is declared as a REF CURSOR. You can then FETCH from that just like you would with a static cursor.
But as OMG Ponies says, you have to be careful about where your SQL is coming from.

OMG Ponies is completely correct,
but here is just a different way to do the same thing
Var X Refcursor;
Begin
Open :X For
Select 1 Num, 'b' Co
From Dual
Union
Select 2 Num, 'c' Co
From Dual;
end;
/
print x;
Note when you do anything in Oracle like opening cursors or whatnot you will need to be within a BEGIN/END and you cannot simply do:
Var X Refcursor;
Open X For
Select 1 Num, 'b' Co
From Dual
Union
Select 2 Num, 'c' Co
From Dual;
This will not Work! You must enclose the OPEN cursor within a BEGIN/END block (be it an anonomous block or a procedure...)
Create or replace Procedure Ccc(X Out sys_Refcursor)
As
begin
Open X For
Select 1 Num, 'b' Co
From Dual
Union
Select 2 Num, 'c' Co
From Dual;
End Ccc;
/
Var X Refcursor;
Begin
Ccc(:X);
End;
/
print x;
note the :x in the begin/end in the anonomous blocks is to tell the sql engine you are utilizing a variable created outside the block. within packages/procs it is unnecessary.

Related

Pass PL/SQL parameter as SCHEMA NAME

I'm trying to send variable schema name to cursor via procedure input
Here is my lame try, but you can see what I want to do:
CREATE OR REPLACE PROCEDURE HOUSEKEEPING
(SCHEMANAME in varchar2)
IS
CURSOR data_instances IS select table_name
from SCHEMANAME.table_name where TYPE='PERMANENT' and rownum<200 ;
BEGIN
DBMS_OUTPUT.PUT_LINE(SCHEMANAME);
END;
/
it throws expected
PL/SQL: ORA-00942: table or view does not exist
is there lawful way to make schema name work as variable? thanks
There is a way; you'll need some kind of dynamic SQL because you can't use schema (or object) names like that. For example, you could use refcursor instead.
Sample table:
SQL> create table table_name as
2 select 'EMP' table_name, 'PERMANENT' type from dual union all
3 select 'DEPT' , 'TEMPORARY' from dual union all
4 select 'BONUS' , 'PERMANENT' from dual;
Table created.
Procedure; note the way I composed SELECT statement first (so that I could display it and check whether it is correct), and then used it in OPEN. Loop is here to ... well, loop through the cursor. I'm just displaying table names I found - you'd probably do something smarter.
SQL> create or replace procedure housekeeping (par_schemaname in varchar2)
2 is
3 l_str varchar2(500);
4 l_rc sys_refcursor;
5 l_table_name varchar2(30);
6 begin
7 l_str := 'select table_name from ' ||
8 dbms_assert.schema_name(upper(par_schemaname)) ||
9 '.table_name where type = ''PERMANENT'' and rownum < 200';
10 open l_rc for l_str;
11
12 loop
13 fetch l_rc into l_table_name;
14 exit when l_rc%notfound;
15
16 dbms_output.put_line(l_table_name);
17 end loop;
18 close l_rc;
19 end;
20 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec housekeeping('SCOTT');
EMP
BONUS
PL/SQL procedure successfully completed.
SQL>

Get count value by using cursor

DECLARE
l_rcursor SYS_REFCURSOR;
BEGIN
OPEN l_rcursor FOR SELECT * FROM all_users;
dbms_output.put_line(l_rcursor%ROWCOUNT);
END;
This is long code so i cant use the cursor inside the declare portion. Here i need the count of rows fetched. I cant use rowtype because it is a join query.
Well, you can not.
There's a way to find out whether cursor contains anything (using the %NOTFOUND attribute), such as
SQL> declare
2 l_rcursor sys_refcursor;
3 l_rec all_users%rowtype;
4 begin
5 open l_rcursor for select * From all_users
6 where 1 = 2; --> will cause nothing to be returned
7
8 -- check whether there is (or is not) anything there
9 fetch l_rcursor into l_rec;
10 if l_rcursor%notfound then
11 dbms_output.put_line('There''s nothing there');
12 end if;
13 end;
14 /
There's nothing there
PL/SQL procedure successfully completed.
SQL>
but you can't know how many rows it'll return, which means that you'll have to count number of rows elsewhere (probably code that uses what you wrote in your question).
Possible duplicate to counting rows from a cursor in pl/sql
You can also just use COUNT in your cursor query to solve your problem, sample code below
DECLARE
l_rcursor SYS_REFCURSOR;
v_count NUMBER;
BEGIN
OPEN l_rcursor FOR SELECT COUNT(1) FROM (SELECT * FROM all_users);
-- OR
-- OPEN l_rcursor FOR SELECT COUNT(1) FROM all_users;
FETCH l_rcursor INTO v_count;
dbms_output.put_line(v_count);
END;
/

How to output all rows in a pl/sql dynamic select without dbms_output.put_line

I have three sql blocks below. The first and second blocks work fine. But the third only returns one row. In my real world example, I have 13 refcursors and each query has several columns in it. I want to avoid writing hundreds of dbms_out.put_line(cur.column_name) statements
--#1 correctly returns 8 rows.
VAR rc REFCURSOR
BEGIN
OPEN :rc FOR SELECT object_id,object_name from user_objects where rownum < 9;
END;
print rc
--------------------------------------------------------------
--#2 correctly returns 8 rows
set serveroutput on
BEGIN
for cur in (select object_id,object_name from user_objects where rownum < 9)
loop
dbms_output.put_line(cur.object_id);
dbms_output.put_line(cur.object_name);
end loop;
END;
---------------------------------------------------------------
--#3 FAIL, only returns 1 row
set serveroutput on
VAR rc REFCURSOR
BEGIN
for cur in (select object_id,object_name from user_objects where rownum < 9)
loop
OPEN :rc FOR SELECT object_id,object_name from user_objects where object_id = cur.object_id;
end loop;
END;
print rc
It's not terribly pretty, but you could do something like this:
VAR rc1 REFCURSOR
VAR rc2 REFCURSOR
VAR rc3 REFCURSOR
BEGIN
for cur in (select object_id,rownum from user_objects where rownum < 4)
loop
case cur.rownum
when 1 then OPEN :rc1 FOR
SELECT object_id,object_name from user_objects
where object_id = cur.object_id;
when 2 then OPEN :rc2 FOR
SELECT object_id,object_name from user_objects
where object_id = cur.object_id;
when 3 then OPEN :rc3 FOR
SELECT object_id,object_name from user_objects
where object_id = cur.object_id;
end case;
end loop;
END;
/
print rc1
print rc2
print rc3
That works, in as much as you get multiple cursors printed out after the block runs.
If you were on 11g you could perhaps have done something like this article suggests, via its rc_to_dbms_sql procedure. I'm not sure if that would quite have met the brief anyway, but it at least automates the dbms_output generation. You could probably do something similar with dbms_sql instead of a sys_refcursor, parsing your inner select and still using a procedure to display the results automatically. I'm not sure if it would be overkill for your real world case though.
set serveroutput on
VAR rc REFCURSOR
BEGIN
OPEN :rc FOR SELECT object_id, object_name from user_objects where object_id IN (
SELECT object_id FROM user_objects WHERE rownum < 9
);
END;
print rc
I don't know what do you need cur for if a subquery is sufficient. Anyway cursor is bound to one select, and there is no easy way to append to it another select dynamically (this is what UNION ALL does statically
Don't worry about performance, engine (the optimizer) is smart enough to execute the IN ( SELECT ) only once in this case.

Dynamically resolve to reference cursor

I am trying to dynamically resolve the value from cursor something like below.
create or replace
PROCEDURE test(
PI_JANUS_ID IN VARCHAR2,
PO_dummy out Types.CursorType
)AS
PO_ACTUALCUROSR Types.CursorType;
cur_row tab%ROWTYPE;
val1 varchar2(100);
val2 varchar2(200);
BEGIN
open PO_ACTUALCUROSR for select * from tab;
LOOP
FETCH PO_ACTUALCUROSR into cur_row;
EXIT WHEN PO_ACTUALCUROSR%NOTFOUND;
val1 := 'TNAME';
SELECT 'cur_row.'||val1 INTO val2 FROM DUAL;
dbms_output.put_line('Column Value ' || val2);
END LOOP ;
CLOSE PO_ACTUALCUROSR;
END;
Here if you see I have to call cur_row. to get the value but here I have the column name in a variable (val1). So how will I take the value from the cursor.
If I execute the above block I would see "cur_row.TNAME" but I actually need the value in the cursor.
Is there a way of doing this.
Any help is very much appreciated.
just do
BEGIN
open PO_ACTUALCUROSR for select * from tab;
LOOP
FETCH PO_ACTUALCUROSR into cur_row;
EXIT WHEN PO_ACTUALCUROSR%NOTFOUND;
val2 := cur_row.tname;
dbms_output.put_line(val2);
END LOOP ;
CLOSE PO_ACTUALCUROSR;
end;
or
dbms_output.put_line(cur_row.tname);
directly will also work.
p.s. your variable
val2 varchar2(200);
is better as
val2 tab.tname%type;
if you're saying you need to dynamically pull columns without knowing the column name ahead of time you'd have to use dynamic sql, and as you're using a pl/sql array, we need to put that in a package spec so that we can reference it (as you cannot pass pl/sql only types into dynamic SQL).
SQL> create table tab(id number, col1 varchar2(10));
Table created.
SQL> insert into tab values (1, 'a');
1 row created.
SQL> commit;
Commit complete.
SQL> create package global_var
2 as
3 cur_row tab%rowtype;
4 end;
5 /
Package created.
SQL> declare
2 po_actualcurosr sys_refcursor;
3 val1 varchar2(10) := 'COL1';
4 val2 tab.col1%type;
5 begin
6 open po_actualcurosr for select * from tab;
7
8 loop
9 fetch po_actualcurosr into global_var.cur_row;
10 exit when po_actualcurosr%notfound;
11 execute immediate 'begin :a := global_var.cur_row.'||dbms_assert.simple_sql_name(val1)||'; end;' using out val2;
12 dbms_output.put_line(val2);
13 end loop ;
14 close po_actualcurosr;
15 end;
16 /
a
PL/SQL procedure successfully completed.

using pl/sql to update sequences

We have a sql script to update a set of sequences after seed data populated our tables. The code below would not work:
declare
cursor c1 is
select
'select nvl(max(id),0) from '||uc.table_name sql_text,
uc.table_name||'_SEQ' sequence_name
from
user_constraints uc,
user_cons_columns ucc
where uc.constraint_type='P'
and ucc.constraint_name = uc.constraint_name
and ucc.column_name='ID'
and uc.owner='ME';
alter_sequence_text varchar2(1024);
TYPE generic_cursor_type IS REF CURSOR;
max_id number;
c2 generic_cursor_type;
begin
for r1 in c1 loop
open c2 for r1.sql_text;
fetch c2 into max_id;
close c2;
if( max_id != 0 ) then
dbms_output.put_line( 'seq name = '||r1.sequence_name );
execute immediate 'alter sequence '||r1.sequence_name||' increment by '||to_char(max_id);
dbms_output.put_line( 'max_id = '||to_char(max_id) );
execute immediate 'select '||r1.sequence_name||'.nextval from dual';
dbms_output.put_line( 'sequence value = '||to_char(next_id) );
execute immediate 'alter sequence '||r1.sequence_name||' increment by 1';
dbms_output.put_line( 'sequence: '||r1.sequence_name||' is at '||to_char(max_id+1) );
end if;
end loop;
end;
After searching I found a reference that stated I needed to change the line:
execute immediate 'select '||r1.sequence_name||'.nextval from dual'
and add 'into next_id;' (of course declaring next_id appropriately) so the result would be:
execute immediate 'select '||r1.sequence_name||'.nextval from dual into next_id;
I've only dealt lightly with pl/sql and sql in general and am interested to know why this change was necessary to make the script work correctly.
Thanks.
When you are using select inside PL/SQL block you have to place data returned by that select statement somewhere. So you have to declare a variable of appropriate data type and use select into clause to put data select returns into that variable even if select statement is executed by execute immediate statement.
Examples
declare
x number;
begin
select count(*)
into x
from all_objects;
end;
declare
x number;
begin
execute immediate 'select count(*)from all_objects' into x;
end;
So your execute immediate statement would be
execute immediate 'select '||sequence_name||'.nextval from dual' into newseqval;
If you are using Oracle 11g onward you can assign sequence's value directly to a variable, there is no need of using select into clause.
declare
x number;
begin
x := Sequence_Name.nextval;
end;
select seq_name.nextval from dual implies the implicit cursor creation and the results of the cursor should be fetched somewhere so you need fetch it into any externally declared bind variable.

Resources