PL/SQL: convert explicit cursor to ref cursor? - oracle

Can I convert an explicit cursor into a ref cursor? I was thinking of something like:
declare
cursor c is
select x from tab;
rc sys_refcursor;
begin
open c;
rc:=c;
close c;
end;
/
I would like to use the ref coursor as an input parameter for a procedure.
I know I can always do it like this:
OPEN rc FOR select x from tab;
But I'm in the process of refactoring some old code an I would have liked to keep the explicit cursor definitions just for the sake of clarity.

As mentioned in my comment, opening a sys_refcursor for another cursor is not allowed till Oracle 11g. As you are trying to do something which demands use of sys_refcursor, once way could be like below:
Create a type
CREATE TYPE va IS TABLE OF NUMBER;
/
Block:
DECLARE
CURSOR c
IS
SELECT employee_id FROM employee;
rc SYS_REFCURSOR;
var va;
BEGIN
OPEN c;
FETCH c BULK COLLECT INTO var;
CLOSE c;
OPEN rc FOR SELECT COLUMN_VALUE FROM TABLE (var);
END;
/
You would see here that at the end am using again a SELECT statement for ref_cursor. It's just like if you dont want to use usual way, i used an alternative way.

Related

Create record from refcursor

I would like to create record from refcursor. My code:
set serveroutput on
DECLARE
c_curs SYS_REFCURSOR;
v_id NUMBER;
BEGIN
pck_prov.get_value_type_list (1, c_curs); --> procedure called here
-- I guess this is the place where record can be created from cursor.
LOOP
FETCH c_curs
INTO v_id;--instead of fetching into variable I would like to fetch into row
EXIT WHEN c_curs%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_id);--if fetching is done into row, only selected columns can be printed, like myrow.id
END LOOP;
CLOSE c_curs;
END;
Please note: I know how to create record from cursor which is defined with select statement as it is described here. What I don't know is how to use same technique for refcursors.
EDIT:
Code from here is just what I need, but it throws error:
set serveroutput on
VAR c_curs refcursor;
EXECUTE pck_prov.get_value_type_list(1, :c_curs);
BEGIN
FOR record_test IN :c_curs LOOP
dbms_output.put_line(record_test.id);
END LOOP;
END;
Error: error PLS-00456: item 'SQLDEVBIND1Z_1' is not a cursor.
Just to clarify question:
In my database there is around 200 packages.
Every package has several stored procedures inside - and usually each procedure is combined with columns from different tables. That is why it would be the best to have some dynamically created cursor, so I can make simple select just like in the example I've posted.
From Oracle 12.1 onward, you could use the DBMS_SQL.return_result procedure. SQL Plus displays the contents of implicit statement results automatically. So, rather than defining explicit ref cursor out parameters, the RETURN_RESULT procedure in the DBMS_SQL package allows you to pass them out implicitly.
DECLARE
c_curs SYS_REFCURSOR;
v_id NUMBER;
BEGIN
pck_prov.get_value_type_list (1, c_curs);
DBMS_SQL.return_result(c_curs); --> Passes ref cursor output implicitly
END;
/
In fact, no need of this separate PL/SQL block, you could add the DBMS_SQL.return_result(c_curs) statement in your original pck_prov.get_value_type_list procedure itself.
Just define a PL/SQL RECORD type that matches the cursor and FETCH into it.
DECLARE
c_curs SYS_REFCURSOR;
TYPE rec_t IS RECORD ( object_name VARCHAR2(30), object_type VARCHAR2(30) );
v_rec rec_t;
BEGIN
OPEN c_curs FOR
SELECT object_name,
object_type
FROM dba_objects
WHERE object_name like 'DBA%TAB%';
LOOP
FETCH c_curs
INTO v_rec;
EXIT WHEN c_curs%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_rec.object_name || ' - ' || v_rec.object_type);
END LOOP;
CLOSE c_curs;
END;
DBA_ADVISOR_SQLA_TABLES - VIEW
DBA_ADVISOR_SQLA_TABVOL - VIEW
DBA_ADVISOR_SQLW_TABLES - VIEW
DBA_ADVISOR_SQLW_TABVOL - VIEW
DBA_ALL_TABLES - VIEW
etc...

Oracle - open a sys_refcursor for an explicit cursor

I have a function that returns a sys_refcursor so that a Java application can process the results:
function fn_stuff (in_array in t_id_array) return sys_refcursor is
rc sys_refcursor;
begin
open rc for
--query has been significantly shortened since most details are not
--relevant for this question.
select * from some_table where id in table(in_array);
return rc;
end;
I'd also like to be able to use this function with PL/SQL. It would be very convenient to be able to reference the rowtype of this result like this:
procedure sp_f is
rc sys_refcursor;
arr t_id_array ;
r c_stuff%rowtype;
begin
arr := t_id_array('11','22','33','44');
rc := pkg_stuff.fn_stuff(arr);
loop
fetch rc into r
exit when rc%notfound;
dbms_output.put_line(r.col1||' '||r.col2);
end loop;
end;
To do this, I'd need to declare the SELECT statement in fn_stuff as a proper cursor:
cursor c_stuff (in_array in t_id_array) is
select * from some_table where id in table(in_array);
So then I thought to update the function to simply reference the new cursor I declared:
function fn_stuff (in_array in t_id_array) return sys_refcursor is
rc sys_refcursor;
begin
open rc for
select * from c_stuff(in_array);
return rc;
end;
Which yields:
Error(52,37): PL/SQL: ORA-00904: : invalid identifier
I could keep the SELECT statement as it was inside fn_stuff but it's still in development so it would be easier to just have the query in a single place.
Is there any way to open and return a sys_refcursor on a declared cursor?
I guess another option is that I could declare a record type which has all of the columns that are selected by the query, but since the query is still under development and columns may be added ore removed from the select, the record type would have to be kept in sync. But since the sp_f procedure only is interested in a handful of columns that probably won't change, I thought the %rowtype would be better.

How do I return the rows from an Oracle Stored Procedure using SELECT?

I have a stored procedure which returns a ref cursor as follows:
CREATE OR REPLACE PROCEDURE AIRS.GET_LAB_REPORT (ReportCurTyp OUT sys_refcursor)
AS
v_report_cursor sys_refcursor;
report_record v_lab_report%ROWTYPE;
l_sql VARCHAR2 (2000);
BEGIN
l_sql := 'SELECT * FROM V_LAB_REPORT';
OPEN v_report_cursor FOR l_sql;
LOOP
FETCH v_report_cursor INTO report_record;
EXIT WHEN v_report_cursor%NOTFOUND;
END LOOP;
CLOSE v_report_cursor;
END;
I want to use the output from this stored procedure in another select statement like:
SELECT * FROM GET_LAB_REPORT()
but I can't seem to get my head around the syntax.
Any ideas?
Whenever I've had to do this; I've used the Oracle TYPE and CAST features.
Something like:
SELECT *
FROM TABLE(CAST(F$get_Cassette_Tracking('8029241') AS cass_tracking_tab_type))
You need to setup the TYPE and all the columns you need and have them use:
pipe ROW(out_obj)
to capture your data. There are many ways to do this and if I can dig out a better example I will but this might give you an idea.
See this SO for a working example: Oracle Parameters with IN statement?

In Oracle what is the difference between open-for and opening a cursor with parameters?

What is the difference between these two pieces of code?
TYPE t_my_cursor IS REF CURSOR;
v_my_cursor t_my_cursor;
OPEN v_my_cursor FOR SELECT SomeTableID
FROM MYSCHEMA.SOMETABLE
WHERE SomeTableField = p_parameter;
And...
CURSOR v_my_cur(p_parameter VARCHAR2) IS
SELECT SomeTableID
FROM MYSCHEMA.SOMETABLE
WHERE SomeTableField = p_parameter;
OPEN presf_cur(p_subscriber_id);
They both seem to work. Are they the same or is there some difference I should be aware of?
The second example is an explicit cursor, and it is static. That is, it is a variable associated with one SQL statement. There is a implicit equivalent...
FOR lrec in ( SELECT SomeTableID
FROM MYSCHEMA.SOMETABLE
WHERE SomeTableField = p_parameter )
LOOP
do_something_with (lrec.sometableid);
END LOOP;
The first example is a ref cursor, which is a pointer to a SQL statement and so can be dynamic. For instance we can extend that example like this:
TYPE t_my_cursor IS REF CURSOR;
v_my_cursor t_my_cursor;
...
if flag = 1 then
OPEN v_my_cursor FOR SELECT SomeTableID
FROM MYSCHEMA.SOMETABLE
WHERE SomeTableField = p_parameter;
else
OPEN v_my_cursor FOR SELECT SomeTableID
FROM MYSCHEMA.ANOTHERTABLE
WHERE AnotherTableField = p_parameter;
end if;
Or even:
l_stmt := 'SELECT * FROM your_table WHERE ';
if p_parameter is not null then
l_stmt := l_stmt ||'id = :1';
open v_my_cursor for l_stmt using p_parameter;
else
l_stmt := l_stmt ||'created_date > trunc(sysdate)';
open v_my_cursor for l_stmt;
end if;
So using a ref cursor gives us a lot more control over the final SQL statement which gets executed. The other difference is that, because a ref cursor is a pointer it can be passed between programs. This is very useful for passing data from PL/SQL to other languages, for instance a JDBC result set.
Strongly typed cursors can be 'described'.
If your building an
API (a package) you can place your
cursor definitions at the
specification level and give the
client programmer a better sense of
what your API does and returns
without needing to be aware of the
source code.
Layout/IDE/GUI tools
will likely play nicer with a named
cursor.
There is possibly a negligible performance benefit having a known typed cursor; but I wouldn't count on it being anything significant.

How to return a resultset / cursor from a Oracle PL/SQL anonymous block that executes Dynamic SQL?

I have this table:
ALLITEMS
---------------
ItemId | Areas
---------------
1 | EAST
2 | EAST
3 | SOUTH
4 | WEST
The DDL:
drop table allitems;
Create Table Allitems(ItemId Int,areas Varchar2(20));
Insert Into Allitems(Itemid,Areas) Values(1,'east');
Insert Into Allitems(ItemId,areas) Values(2,'east');
insert into allitems(ItemId,areas) values(3,'south');
insert into allitems(ItemId,areas) values(4,'east');
In MSSQL, to get a cursor from a dynamic SQL I can do:
DECLARE #v_sqlStatement VARCHAR(2000);
SET #v_Sqlstatement = 'SELECT * FROM ALLITEMS';
EXEC (#v_sqlStatement); --returns a resultset/cursor, just like calling SELECT
In Oracle, I need to use a PL/SQL Block:
SET AUTOPRINT ON;
DECLARE
V_Sqlstatement Varchar2(2000);
outputData SYS_REFCURSOR;
BEGIN
V_Sqlstatement := 'SELECT * FROM ALLITEMS';
OPEN outputData for v_Sqlstatement;
End;
--result is : anonymous block completed
**But all I get is
anonymous block completed".
How do I get it to return the cursor?
(I know that if I do AUTOPRINT, it will print out the information in the REFCURSOR (it's not printing in the code above, but thats another problem))
I will be calling this Dynamic SQL from code (ODBC,C++), and I need it to return a cursor. How?
You can write a PL/SQL function to return that cursor (or you could put that function in a package if you have more code related to this):
CREATE OR REPLACE FUNCTION get_allitems
RETURN SYS_REFCURSOR
AS
my_cursor SYS_REFCURSOR;
BEGIN
OPEN my_cursor FOR SELECT * FROM allitems;
RETURN my_cursor;
END get_allitems;
This will return the cursor.
Make sure not to put your SELECT-String into quotes in PL/SQL when possible. Putting it in strings means that it can not be checked at compile time, and that it has to be parsed whenever you use it.
If you really need to use dynamic SQL you can put your query in single quotes:
OPEN my_cursor FOR 'SELECT * FROM allitems';
This string has to be parsed whenever the function is called, which will usually be slower and hides errors in your query until runtime.
Make sure to use bind-variables where possible to avoid hard parses:
OPEN my_cursor FOR 'SELECT * FROM allitems WHERE id = :id' USING my_id;
in SQL*Plus you could also use a REFCURSOR variable:
SQL> VARIABLE x REFCURSOR
SQL> DECLARE
2 V_Sqlstatement Varchar2(2000);
3 BEGIN
4 V_Sqlstatement := 'SELECT * FROM DUAL';
5 OPEN :x for v_Sqlstatement;
6 End;
7 /
ProcÚdure PL/SQL terminÚe avec succÞs.
SQL> print x;
D
-
X
You should be able to declare a cursor to be a bind variable (called parameters in other DBMS')
like Vincent wrote, you can do something like this:
begin
open :yourCursor
for 'SELECT "'|| :someField ||'" from yourTable where x = :y'
using :someFilterValue;
end;
You'd have to bind 3 vars to that script. An input string for "someField", a value for "someFilterValue" and an cursor for "yourCursor" which has to be declared as output var.
Unfortunately, I have no idea how you'd do that from C++. (One could say fortunately for me, though. ;-) )
Depending on which access library you use, it might be a royal pain or straight forward.
This setting needs to be set:
SET SERVEROUTPUT ON

Resources