ORA-01002: fetch out of sequence in procedure - oracle

I created a procedure in oracle database that returns data in a ref-cursor, and I want it to return the rowcount of this cursor also as an output variable. After testing, the P_count variable is filled correctly, but when I tried to open it an
ORA-01002: fetch out of sequence
error is fired. I have read before about it and I found that the problem is because I am using a fetch statement in. But till now I did not discover how to resolve it. Any helps are appreciated, thank you.
Below is my Procedure:
PROCEDURE IS_CLIENT_LOGGED_IN (P_CLIENT_NUM Varchar2,P_CURSOR out SYS_REFCURSOR ,P_COUNT OUT NUMBER,P_ERROR out Varchar2) AS
TYPE MyRec IS RECORD (ID VARCHAR2(100));
cur_rec MyRec;
lv_cur SYS_REFCURSOR;
BEGIN
BEGIN
Open lv_cur FOR
SELECT ID
FROM tbl_registration
WHERE tbl_client_id = P_CLIENT_NUM
AND tbl_logout_date is null;
LOOP
FETCH lv_cur INTO cur_rec;
EXIT WHEN P_CURSOR%notfound;
P_COUNT := P_CURSOR%rowcount;--will return row number beginning with 1
END LOOP;
P_CURSOR := lv_cur;
EXCEPTION WHEN OTHERS THEN
P_ERROR := 'Unable to select Data from tbl_registration' ||SQLERRM;
END;
Searching online I found reasons for the issue as below:
Fetching from a cursor after the last row has been retrieved and the ORA-1403 error returned.
If the cursor has been opened with the FOR UPDATE clause, fetching after a COMMIT has been issued will return the error.
Rebinding any placeholders in the SQL statement, then issuing a fetch before reexecuting the statement.
But I cannot find a proper solution.

You can only use a cursor once, so if you want both the row count and the actual result set, you will need two separate queries. Then the problem is that the data might have been updated by other sessions between the two queries, giving inconsistent results. (For example, another session inserted some rows earlier, and then commits while your procedure is running.)
One approach would be to use flashback query to ensure that the count and the fetch refer to the same point in time:
create or replace procedure is_client_logged_in
( p_client_num varchar2
, p_cursor out sys_refcursor
, p_count out number
, p_error out varchar2 )
as
k_starttime constant timestamp := systimestamp;
begin
select count(*) into p_count
from tbl_registration as of timestamp k_starttime
where tbl_client_id = p_client_num
and tbl_logout_date is null;
open p_cursor for
select id
from tbl_registration as of timestamp k_starttime
where tbl_client_id = p_client_num
and tbl_logout_date is null;
exception
when others then p_error := 'Unable to retrieve registration data.'||chr(10)||sqlerrm;
end;

What you want sounds very simple, but isn't. A cursor is for one-use only. So if you read through it, you are done with it. Returning the unused cursor plus its row count requires special handling hence.
One way to come to mind is two separate queries: One to count the rows, one to open the cursor. The problem, though: In the nanoseconds between the queries the row count may change because of other sessions inserting or deleting or updating data. Okay, you can lock the table exclusively, but then you will have to release it after the two statements. Unfortunately, there is no UNLOCK TABLE command in Oracle. Releasing a table is only possible via a COMMIT or ROLLBACK. So, in order not to interfere with the caller's transaction you need a SAFEPOINT, too.
Here is your procedure with the above method applied:
CREATE OR REPLACE PROCEDURE is_client_logged_in (p_client_num VARCHAR2, p_cursor OUT SYS_REFCURSOR, p_count OUT NUMBER, p_error OUT VARCHAR2) AS
BEGIN
SAVEPOINT for_unlock;
LOCK TABLE tbl_registration IN EXCLUSIVE MODE;
SELECT COUNT(*) INTO p_count
FROM tbl_registration
WHERE tbl_client_id = p_client_num
AND tbl_logout_date IS NULL;
Open p_cursor FOR
SELECT id
FROM tbl_registration
WHERE tbl_client_id = p_client_num
AND tbl_logout_date IS NULL;
ROLLBACK TO SAVEPOINT for_unlock;
EXCEPTION WHEN OTHERS THEN
ROLLBACK TO SAVEPOINT for_unlock;
p_error := 'Unable to select Data from tbl_registration' || SQLERRM;
END is_client_logged_in;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=552d7f76d03a9a221c17f05c6663d2c9

Here is an alternative. It's really muddling through, but well...
I've shown a solution with locking the table. But imagine a query that runs for an hour. You'd lock that table for twice this time.
William has shown a great solution with flashback queries. But with a query running for an hour, the flashback data may time out.
So, here is a weird solution getting count and results in one query. This is easy in your case, because I expect the ID you are selcting to be an integer, just as the count. So I select the count into the first row, fetch that and give back the cursor that is now at the position for the first actual data row (the first ID).
CREATE OR REPLACE PROCEDURE is_client_logged_in (p_client_num VARCHAR2, p_cursor OUT SYS_REFCURSOR, p_count OUT NUMBER, p_error OUT VARCHAR2) AS
BEGIN
Open p_cursor FOR
WITH data AS
(
SELECT id
FROM tbl_registration
WHERE tbl_client_id = p_client_num
AND tbl_logout_date IS NULL
)
SELECT id
FROM
(
SELECT 1 as sortkey, COUNT(*) AS id FROM data
UNION ALL
SELECT 1 as sortkey, id FROM data
)
ORDER BY sortkey;
FETCH p_cursor INTO p_count;
EXCEPTION WHEN OTHERS THEN
p_error := 'Unable to select Data from tbl_registration' || SQLERRM;
END is_client_logged_in;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=24ed333b9c1ed700af6d3a486ef47ccd

Related

Return updated rows from a stored function

Im trying to select some rows from a Table in ORACLE and at the same time update the selected rows state. I found a way to do so with a stored function and Cursors but I cant manage to return the rows after using the cursor to update. This is my code:
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
CURSOR c_operations IS
SELECT * FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
BEGIN
FOR r_operation IN c_operations
LOOP
UPDATE
TABLE1
SET
TABLE1.STATUS = 'OK'
WHERE
TABLE1.ID_TABLE1 = r_operation.ID_TABLE1;
END LOOP;
COMMIT;
-- Missing conversion from cursor to sys_refcursor
RETURN l_return;
END;
The update is working but Im still missing how to return the updated rows that are in the cursor (c_operations ).
Thank you.
I'm going to make some assumptions:
id_table1 is the primary key of the table, so your RBAR (*) update affects only one row
id_table1 is numeric
If these assumptions are wrong you will need to tweak the following code.
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
l_id table1.id_table1%type;
l_upd_ids sys.odcinumberlist := new sys.odcinumberlist();
CURSOR c_operations IS
SELECT * FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
BEGIN
FOR r_operation IN c_operations LOOP
UPDATE TABLE1
SET TABLE1.STATUS = 'OK'
WHERE TABLE1.ID_TABLE1 = r_operation.ID_TABLE1
returning TABLE1.ID_TABLE1 into l_id;
l_upd_ids.extend();
l_upd_ids(l_upd_ids.count()) := l_id;
END LOOP;
COMMIT;
open l_return for
select * from table(l_upd_ids);
RETURN l_return;
END;
The key points of the solution.
uses Oracle maintained collection (of number) sys.odcinumberlist to store the updated IDs;
uses RETURNING clause to capture the id_table1 value for the updated row;
stores the returned key in the collection;
uses a table() function to casrt the collection into a table which can be queried in the ref cursor.
This last point is why I chose to use sys.odcinumberlist rather than defining a collection in the procedure. It's a SQL type, so we can use it in SELECT statements.
(*) Row-by-agonizing-row. Updating single records in a PL/SQL loop is the slowest way of executing bulk updates, and normally constitutes an anti-pattern. A straightforward set-based UPDATE should suffice. However, you know your own situation so I'm going to leave that as it is.
It looks to me like you don't need the initial cursor, since you're changing the STATUS of every row which is not 'OK' to 'OK', so you can do this is a simple UPDATE statement. Then use an OPEN...FOR statement to return a cursor of all rows where STATUS is not 'OK', which shouldn't return anything because you've already changed all the status values to 'OK'. I suggest that you rewrite your procedure as:
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
BEGIN
UPDATE TABLE1
SET STATUS = 'OK'
WHERE STATUS != 'OK';
COMMIT;
OPEN l_return FOR SELECT *
FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
RETURN l_return;
END;
Instead of a loop to update how about a bulk update collecting the updated ids. Then a table function from those returned ids.
create type t_table1_id is
table of integer;
create or replace function set_table1_status_ok
return sys_refcursor
is
l_results_cursor sys_refcursor;
l_updated_ids t_table1_id;
begin
update table1
set status = 'Ok'
where status != 'Ok'
returning table1.id
bulk collect
into l_updated_ids;
open l_results_cursor for
select *
from table1
where id in (select * from table(l_updated_ids));
return l_results_cursor;
end set_table1_status_ok;
-- test
declare
updated_ids sys_refcursor;
l_this_rec table1%rowtype;
begin
updated_ids := set_table1_status_ok();
loop
fetch updated_ids into l_this_rec;
exit when updated_ids%notfound;
dbms_output.put_line ( l_this_rec.id || ' updated.');
end loop;
close updated_ids;
end ;

Validation of result before executing the query

When running a stored procedure to fetch some rows, First I want to validate if the query will return a row before sending the result, and second if it is possible to validate without running the same query twice.
I am using a cursor to store the yielded result, So I tried the cursor attribute %ROWCOUNT & %NOTFOUND. But the doesnt quite work. Plus I want to do this without running a loop on the cursor.
procedure MODULE_LIST_GK(p_module_Id IN MODULE_LIST.MODULE_ID% TYPE,
p_Error_Code out nvarchar2,
p_Error_Msg out nvarchar2,
p_Cursor out sys_refcursor) IS
BEGIN
OPEN p_Cursor FOR
SELECT A.MODULE_ID,
A.MODULE_NM,
A.AUTH_STATUS_ID
FROM MODULE_LIST A
WHERE A.MODULE_ID=p_module_Id;
SELECT COUNT(MODULE_ID)
INTO v_row_num
FROM MODULE_LIST A
WHERE A.MODULE_ID=p_module_Id;
IF v_row_num=0 THEN
p_Error_Code := SQLCODE;
p_Error_Msg := 'Does not Exists';
Return;
end IF;
EXCEPTION
WHEN OTHERS THEN
p_error_code:= SQLCODE;
p_error_msg := SQLERRM;
END MODULE_LIST_GK;
Your implementation have several points that could be improved.
First if you expect that for lot of parameters the returned cursor will be empty,
than first check the empty cursor and only after this check open the cursor. You do it vice versa.
How to check if the cursor is empty? Unfortunatelly you must fetch the first row to be able to verify it.
open l_cur for
select id, status from tab where id = p_id;
fetch l_cur into l_id, l_status;
if l_cur%NOTFOUND then
p_Error_Msg := 'Does not Exists';
Return;
end if;
This check is far more effective that the often used count(*) as it is considering only the first (few) rows and not counting all rows in the cursor.
If the check fails you are ready, othervise simple open the cursor and return it.
open l_cur for
select id, status from tab where id = p_id;
p_Cursor := l_cur;
Two additional thinks come to mind.
You should rething the generall approach if the database is very dynamic. How would you handle the case when other session deletes some row between the check and the second open of the cursor?
Finally consider returning an exception instead of the return code.
In order to know whether a cursor contains rows, you must open it and fetch the first row. Once you've done this, it makes no sense anymore to return that cursor, for the recipient will not be able to fetch that first row, because the cursor already points beyond it.
So, you must select twice. What you'd do is to use ROWNUM or an EXISTS clause here to show the DBMS that you are not interested in any more rows. This can speed up the query extremely.
PROCEDURE module_list_gk(p_module_id IN MODULE_LIST.MODULE_ID%TYPE,
p_error_code OUT NVARCHAR2,
p_error_msg OUT NVARCHAR2,
p_cursor OUT SYS_REFCURSOR) IS
v_count INTEGER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM module_list
WHERE module_id = p_module_Id
AND ROWNUM = 1;
IF v_count = 0 THEN
p_error_code := 0; -- Or -1403 for NO DATA FOUND if you like
p_error_msg := 'Does not Exists';
RETURN;
END IF;
OPEN p_Cursor FOR
SELECT module_id, module_nm, auth_status_id
FROM module_list
WHERE module_id = p_module_id;
EXCEPTION WHEN OTHERS THEN
p_error_code:= SQLCODE;
p_error_msg := SQLERRM;
END module_list_gk;
SQLCODE for the first query will be 0 by the way (SELECT COUNT(*) returns one row with the number of records found - no error hence). This is why you should decide to either return zero explicitly or some error code, such as -1403.
Here is the same with EXISTS:
BEGIN
SELECT CASE WHEN EXISTS
(
SELECT NULL
FROM module_list
WHERE module_id = p_module_Id
) THEN 1 ELSE 0 END
INTO v_count
FROM DUAL;
IF v_count = 0 THEN

How to display the results of a procedure outside of it in Oracle

I am working on an application and made the decision that all the queries would be procedures. I hope to have gains in performance and ease of maintenance by doing it this way. Our DBA's have also expressed interest in having it done this way.
I have an HR table where operations are performed on it each night, and any changes are recorded in a secondary table. We are not doing auditing, these change records are kept until the next run and show users the changes that have happened.
To keep my question shorter I have reduced the number of columns in HR.
The HR table ID, GROUP_NAME, and GROUP_LEVEL. The Drill table has ID and TYPEVALUE.
CREATE OR REPLACE PROCEDURE DOCSADM.DRILL_RECORD_POSITION (
RECORD_TYPE IN VARCHAR2,
OUT_ID OUT VARCHAR2,
OUT_GROUP_NAME OUT VARCHAR2,
OUT_GROUP_LEVEL OUT VARCHAR2
) AS
BEGIN
SELECT HR.ID, HR.GROUP_NAME, HR.GROUP_LEVEL
INTO OUT_ID, OUT_GROUP_NAME, OUT_GROUP_LEVEL
FROM HR_POSITION HR JOIN DRILL_POSITION DP ON (HR.ID = DP.ID) WHERE DP.TYPEVALUE = RECORD_TYPE;
END DRILL_RECORD_POSITION;
The procedure compiles without issue. Before doing all the work in the application to link to the procedure and extract the values which in this case will eventually be displayed in a view or webpage, I wanted to have a quick little script that would call the procedure and then display the results so I can verify in Oracle.
Loops
BEGIN
for t in (DRILL_RECORD_POSITION('D', V1,V5,V6))
loop
--dbms_output.put_line(t.V1 || t.V5 || t.V6);
dbms_output.put_line(t.OUT_ID);
end loop;
END;
/
CURSORS
DECLARE
V1 HR_POSITION.ID%TYPE;
V5 HR_POSITION.GROUP_NAME%TYPE;
V6 HR_POSITION.GROUP_LEVEL%TYPE;
CURSOR T_CUR IS DRILL_RECORD_POSITION('D', V1,V5,V6);
BEGIN
OPEN T_CUR;
DBMS_OUTPUT.PUTLINE('START');
LOOP
FETCH T_CUR INTO V1,V5,V6;
EXIT WHEN T_CUR%NOTFOUND;
DBMS_OUTPUT.PUTLINE(V1||V5||V6);
END LOOP;
CLOSE T_CUR;
END;
FOR LOOPS
DECLARE
V1 HR_POSITION.POSITION_ID%TYPE;
V5 HR_POSITION.GROUP_NAME%TYPE;
V6 HR_POSITION.GROUP_LEVEL%TYPE;
BEGIN
DBMS_OUTPUT.PUTLINE('START');
FOR INDEX IN (DRILL_RECORD_POSITION('D', V1,V5,V6))
LOOP
--DBMS_OUTPUT.PUTLINE(INDEX.ID);
DBMS_OUTPUT.PUTLINE(INDEX.V1||INDEX.V5||INDEX.V6);
END LOOP;
END;
Note: I edited the column names out and shorted some when transferring here so I might have made a few mistakes.
All the articles I have seen online show me how to display from within the original procedure or by using views, cursors, records. Unless I am wrong, Eclipse wont have any problems using the information in the current form which is why I am passing it that way. So I am not interested in changing the procedure and would like to work with it as is, since thats how the application will be doing it.
As this is the first of the stored procedures I am doing for the application, instead of using adhoc queries from the application, I dont have any existing examples to work from, which is why I believe the results will work fine, because it should be the same format the adhoc ones use.
Update:
In one of the comments, I was pointed to what should have been a solution. This was confirmed by another solution that was under it.
I keep getting the error
ORA-01422: exact fetch returns more than requested number of rows
So Im returning multiple rows, but that is my expectation and what is happening. I just cant seem to figure out how to display the results.
To test the procedure you showed, you would do something like:
declare
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
drill_record_position('D', l_id, l_group_name, l_group_level);
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end;
/
But that - or more specifically, your procedure - only works if there is exactly one row in the query's result set for the passed-in value type. It seems you're expecting multiple rows back (which would get too-many-rows), but there could also be non (which would get no-data-found).
So really it seems like your question should be about how to write your procedure so it works with one of the retrieval/test methods you tried.
If your procedure needs to return multiple rows then it can use a ref cursor, e.g.:
create or replace procedure drill_record_position (
p_record_type in varchar2,
p_ref_cursor out sys_refcursor
)
as
begin
open p_ref_cursor for
select hr.id, hr.group_name, hr.group_level
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type;
end drill_record_position;
/
which you could then test with something like:
declare
l_ref_cursor sys_refcursor;
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
drill_record_position('D', l_ref_cursor);
loop
fetch l_ref_cursor into l_id, l_group_name, l_group_level;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end loop;
close l_ref_cursor;
end;
/
You can also do that as a function, which might be easier to work with from your application:
-- drop procedure drill_record_position;
create or replace function drill_record_position (p_record_type in varchar2)
return sys_refcursor as
l_ref_cursor sys_refcursor;
begin
open l_ref_cursor for
select hr.id, hr.group_name, hr.group_level
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type;
return l_ref_cursor;
end drill_record_position;
/
declare
l_ref_cursor sys_refcursor;
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
l_ref_cursor := drill_record_position('D');
loop
fetch l_ref_cursor into l_id, l_group_name, l_group_level;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end loop;
close l_ref_cursor;
end;
/
You coudl also do this with collections and a pipelined function, which is more work to set up:
create type t_drill_obj as object (
-- use your real data types...
id number,
group_name varchar2(10),
group_level number
)
/
create type t_drill_tab as table of t_drill_obj
/
create or replace function drill_record_position (p_record_type in varchar2)
return t_drill_tab pipelined as
begin
for l_row in (
select t_drill_obj(hr.id, hr.group_name, hr.group_level) as obj
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type
)
loop
pipe row (l_row.obj);
end loop;
return;
end drill_record_position;
/
but you could call it as part of another query, and even join tot he result if you needed to:
select * from table(drill_record_position('D'));

Pagination in Oracle/PLSQL error

Hey I am trying to add paging to my dynamic sql block in PLSQL but for some reason when I run the test script it errors out:
ORA-00932: inconsistent datatypes: expected - got -
Here is my procedure:
create or replace
procedure spm_search_patientmedrecs (
p_columnsort_in in varchar2,
p_column1_in in varchar2,
p_column2_in in varchar2,
p_column3_in in varchar2,
p_column4_in in varchar2,
p_ascdesc_in in varchar2,
p_return_cur_out out sys_refcursor
is
lv_sql varchar2(32767);
lv_startnum number:= 1;
lv_incrementby number:= 20;
begin
lv_sql := '';
lv_sql := 'select * from (
select /*+ first_rows(20) */
'||p_column1_in||',
'||p_column2_in||',
'||p_column3_in||',
'||p_column4_in||',
row_number() over
(order by '||p_columnsort_in||' '||p_ascdesc_in||') rn
from membermedicalreconcilationhdr h,
membermedicalreconcilationdet d
where h.membermedreconciliationhdrskey =
d.membermedreconciliationhdrskey)
where rn between :lv_startnum and :lv_incrementby
order by rn';
open p_return_cur_out for lv_sql;
end spm_search_patientmedrecs;
Here is my test script:
set serveroutput on
declare
type tempcursor is ref cursor;
v_cur_result tempcursor;
p_columnsort_in varchar2(50);
p_column1_in varchar2(50);
p_column2_in varchar2(50);
p_column3_in varchar2(50);
p_column4_in varchar2(50);
p_ascdesc_in varchar2(50);
begin
spm_search_patientmedrecs
('h.PRIMARYMEMBERPLANID',
'h.PRIMARYMEMBERPLANID',
'h.ASSIGNEDUSERID',
'd.MEMBERMEDRECONCILIATIONDETSKEY',
'd.GENERICNM',
'ASC',
v_cur_result
);
loop
fetch v_cur_result into
p_column1_in,p_column2_in,p_column3_in,p_column4_in;
dbms_output.put_line('column 1: '||p_column1_in||' column 2: '||p_column2_in||
' column 3: '||p_column3_in||' column 4: '||p_column4_in);
exit when v_cur_result%notfound;
end loop;
end;
The error I posted above doesnt make sense to me, but I've been looking for the cause for awhile. If anyone can point me in the right direction it would be much appreciated, thanks in advance.
A couple of issues jump out at me.
The query that you are using to return the cursor returns 5 columns (the 4 you pass in plus the computed rn) while your fetch fetches the data into only 4 variables. You would either need to modify your query to return only 4 columns or modify your test script to fetch the data into 5 variables.
In your procedure, you have bind variables in your SQL statement but you don't pass in any bind variables when you open the cursor. My guess is that you want something like this
Passing the bind variables with the USING clause
open p_return_cur_out
for lv_sql
using lv_startnum, lv_incrementby;
There may well be more errors-- if there are, it would be helpful to post the full stack trace including the line number of the error.
A couple of other things to be aware of.
Unless p_columnsort_in happens to specify a column that is unique, your paging code may well miss rows and/or show rows in multiple pages because the sort order isn't fully specified. If rows 20 and 21 have the same p_columnsort_in value, it would be perfectly legal to sort them one way on the first query and another way on the second query so row 20 might show up on the first and second page and row 21 might not show up anywhere.
If efficiency is a concern, using rownum will probably end up being more efficient than using the analytic function like this because the optimizer can generally do a better job of optimizing a rownum predicate.
create or replace
procedure spm_search_patientmedrecs (
p_columnsort_in in varchar2,
p_column1_in in varchar2,
p_column2_in in varchar2,
p_column3_in in varchar2,
p_column4_in in varchar2,
p_ascdesc_in in varchar2,
p_return_cur_out out sys_refcursor
is
lv_sql varchar2(32767);
lv_startnum number:= 1;
lv_incrementby number:= 20;
begin
lv_sql := 'select * from (
select /*+ first_rows(20) */
'||p_column1_in||',
'||p_column2_in||',
'||p_column3_in||',
'||p_column4_in||',
row_number() over
(order by '||p_columnsort_in||' '||p_ascdesc_in||') rn
from membermedicalreconcilationhdr h,
membermedicalreconcilationdet d
where h.membermedreconciliationhdrskey =
d.membermedreconciliationhdrskey)
where rn between :1 and :2
order by rn';
open p_return_cur_out for lv_sql using lv_startnum, lv_incrementby;
end spm_search_patientmedrecs;

dbms_sql.to_cursor_number - Getting Invalid Cursor error for SYS_REFCURSOR

I have the following code for table and view creation.
create table test_company
(
comp_id number
, comp_name varchar2(500)
)
;
insert into test_company values(1, 'CompanyA');
insert into test_company values(2, 'CompanyB');
insert into test_company values(3, 'CompanyC');
create or replace view test_company_view as select * from test_company;
And I have the following code for my cursor testing. But dbms_sql.to_cursor_number got error ORA-01001: invalid cursor
set serveroutput on;
declare
reader test_company_view%ROWTYPE;
datacursor SYS_REFCURSOR;
v_cursor_id number;
begin
open datacursor for select * from test_company_view;
v_cursor_id := dbms_sql.to_cursor_number(datacursor); -- ERROR: invalid cursor
loop fetch datacursor into reader;
exit when datacursor%NOTFOUND;
dbms_output.put_line(reader.comp_id);
end loop;
close datacursor;
end;
What did I do wrong? Thank you for your helps!
I have tried strongly-typed REF CURSOR and weakly-typed REF CURSOR but they got the same error.
From the documentation:
After you convert a REF CURSOR variable to a SQL cursor number, native dynamic SQL operations cannot access it.
It is not dbms_sql.to_cursor_number that is raising the error, it is fetch datacursor into reader since datacursor can no longer be accessed.

Resources