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.
Related
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...
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.
Devs,
I've searched everywhere I can, but I could not find solution to this simple problem.
Situation:
I need to write a procedure where it takes a column name as the input and return all the distinct values present in that column as output. And then I have to use this procedure in some c# code.
In MS server, it is very easy as it will directly give the set of results unlike in PL/SQL.
Script I could write (which is not giving me the result I need):
CREATE OR REPLACE
PROCEDURE GetCol(PARAM IN STRING, recordset OUT sys_refcursor)
AS
BEGIN
OPEN recordset FOR
SELECT DISTINCT(PARAM)
FROM my_table;
END
;
When I try to check the data in the recordset using this code:
DECLARE
l_cursor SYS_REFCURSOR;
l_sname VARCHAR2(50);
BEGIN
GetCol('col_name',l_cursor);
LOOP
FETCH l_cursor INTO l_sname;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_sname);
END LOOP;
CLOSE
Can someone help me with this code please.
You can also open a ref_cursor for a string value. Please take a look at this:
CREATE OR REPLACE PROCEDURE GetCol(PARAM IN VARCHAR2, recordset OUT sys_refcursor)
AS
QRY varchar2(100);
BEGIN
QRY := 'SELECT DISTINCT '||PARAM||' FROM my_table';
OPEN recordset FOR QRY;
END;
Then:
DECLARE
l_cursor SYS_REFCURSOR;
l_sname VARCHAR2(50);
BEGIN
GetCol('col_name',l_cursor);
LOOP
FETCH l_cursor INTO l_sname;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_sname);
END LOOP;
END;
Your problem is caused by ambiguity about what PARAM is in the procedure's SELECT statement:
CREATE OR REPLACE
PROCEDURE GetCol(PARAM IN STRING, recordset OUT sys_refcursor)
AS
BEGIN
OPEN recordset FOR
SELECT DISTINCT(PARAM) -- Ambiguity here
FROM my_table;
END;
Does PARAM refer to the table column or to the first parameter of the procedure? Oracle has assumed the parameter. You can explicitly say which like this:
SELECT DISTINCT(my_table.PARAM)
FROM my_table;
You could if appropriate (it probably isn't here) specify the procedure parameter instead:
SELECT DISTINCT(GetCol.PARAM)
FROM my_table;
Generally this is best avoided by:
always using table aliases in column references select statements, and
having a standard for parameter names that makes them less likely to clash e.g. P_PARAM.
This is my first (edited) stackoverflow question, so please bear with me.
In Oracle 11g, I have a need to describe/interrogate the underlying columns of a reference cursor returned from a procedure call on another database over a dblink, in which the actual SQL is not always "explicit", but sometimes dynamically generated.
For example:
declare
v_ref_cur sys_refcursor;
v_cur_num number;
v_col_count number;
v_col_table dbms_sql.desc_tab3;
begin
myProc#myDblink(v_ref_cur, 'myvalue');
v_cur_num := dbms_sql.to_cursor_number(v_ref_cur);
dbms_sql.describe_columns3(v_cur_num, v_col_count, v_col_table);
...
end
If myProc() on the other database has an "explicit" SQL statement like:
open cursor for select * from foo where bar = myParam;
The cursor conversion and description (still) work just fine - I can determine the column names, types, lengths, etc returned by the procedure.
BUT, if myProc() on the other database involves dynamic SQL, like:
v_sql := 'select * from foo where bar = ''' || myParam || '''';
open cursor for v_sql;
I get an ORA-01001 invalid cursor error when attempting to call dbms_sql.to_cursor_number().
Is there a way to convert/describe a reference cursor derived from dynamic SQL as called from a remote procedure? If so, how? If not, why not?
Thanks for any/all assistance!
Using DBMS_SQL across a database link raises many different errors, at least some of which are Oracle bugs. Those problems can be avoided by putting all of the logic in a function compiled on the remote server. Then call that function remotely.
--Create and test a database link
create database link myself connect to <schema> identified by "<password>"
using '<connect string or service name>';
select * from dual#myself;
--myProc
create procedure myProc(p_cursor in out sys_refcursor, p_value varchar2) is
begin
--open p_cursor for select * from dual where dummy = p_value;
open p_cursor for 'select * from dual where dummy = '''||p_value||'''';
end;
/
--Simple function that counts and displays the columns. Expected value is 1.
create or replace function count_columns return number is
v_ref_cur sys_refcursor;
v_cur_num number;
v_col_count number;
v_col_table dbms_sql.desc_tab3;
begin
--ORA-01001: invalid cursor
--myProc#myself(v_ref_cur, 'myvalue');
myProc(v_ref_cur, 'myvalue');
--ORA-03113: end-of-file on communication channel
--v_cur_num := dbms_sql.to_cursor_number#myself(v_ref_cur);
v_cur_num := dbms_sql.to_cursor_number(v_ref_cur);
--Compilation error: PLS-00306:
-- wrong number or types of arguments in call to 'DESCRIBE_COLUMNS3'
--dbms_sql.describe_columns3#myself(v_cur_num, v_col_count, v_col_table);
dbms_sql.describe_columns3(v_cur_num, v_col_count, v_col_table);
return v_col_count;
end;
/
begin
dbms_output.put_line('Number of columns: '||count_columns#myself());
end;
/
Number of columns: 1
I really avoided the "best" word for my question but it really is the most suitable word for it.
What's the best(most efficient) way of returning records from a function?
Currently I have something like:
FUNCTION myFunct(param1 VARCHAR2) RETURN SYS_REFCURSOR AS
myCursor SYS_REFCURSOR;
BEGIN
OPEN myCursor FOR
SELECT *
FROM myTable
WHERE field = param1;
RETURN(myCursor);
END myFunct;
I can run this fine but with everything else I am reading like (TABLE type, implicit cursor, etc) I am really confused about what is most suitable.
P.S. how can I loop over this cursor after I call it from a proc?
EDIT:
I've read that I can only iterate through cursors ONCE (forums.oracle.com/thread/888365) but in reality I want to loop contents several times. Does this mean that I am opt to use associative arrays instead?
create or replace
PACKAGE example_pkg AS
/*
** Record and nested table for "dual" table
** It is global, you can use it in other packages
*/
TYPE g_dual_ntt IS TABLE OF SYS.DUAL%ROWTYPE;
g_dual g_dual_ntt;
/*
** procedure is public. You may want to use it in different parts of your code
*/
FUNCTION myFunct(param1 VARCHAR2) RETURN SYS_REFCURSOR;
/*
** Example to work with a cursor
*/
PROCEDURE example_prc;
END example_pkg;
create or replace
PACKAGE BODY example_pkg AS
FUNCTION myFunct(param1 VARCHAR2) RETURN SYS_REFCURSOR
AS
myCursor SYS_REFCURSOR;
BEGIN
OPEN myCursor FOR
SELECT dummy
FROM dual
WHERE dummy = param1;
RETURN(myCursor);
END myFunct;
PROCEDURE example_prc
AS
myCursor SYS_REFCURSOR;
l_dual g_dual_ntt; /* With bulk collect there is no need to initialize the collection */
BEGIN
-- Open cursor
myCursor := myFunct('X');
-- Fetch from cursor / all at onece
FETCH myCursor BULK COLLECT INTO l_dual;
-- Close cursor
CLOSE myCursor;
DBMS_OUTPUT.PUT_LINE('Print: ');
FOR indx IN 1..l_dual.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('element: ' || l_dual(indx).dummy );
END LOOP;
END example_prc;
END example_pkg;
EXECUTE example_pkg.example_prc();
/*
Print:
element: X
*/
Please take a look at this link: http://www.oracle-base.com/articles/misc/using-ref-cursors-to-return-recordsets.php
You might find it useful...