dbms_sql.to_cursor_number - Getting Invalid Cursor error for SYS_REFCURSOR - oracle

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.

Related

ORA-01001: invalid cursor . Passing cursor to a procedure

When I execute the code below I got ORA-01001: invalid cursor and ORA-06512 error. It was not always the case.I thought I pass the cursor to MY_READ procedure by reference and can close it there. But as I started to get the error above suddenly I concluded, that maybe I'm not passing a reference but the copy of the cursor. Am I right? Is it possible to read close the cursor in MY_READ function?
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE REP_HELPER1 (myIdx IN BINARY_INTEGER, from_d IN DATE, rep_table IN OUT rep_table_T) IS
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
myCUR1 SYS_REFCURSOR;
BEGIN
OPEN myCUR1 FOR SELECT myField1,
myField2,
myField3,
myField4,
myField5,
myField6,
myField7,
myField8,
myField9,
myField10,
myField11,
myField12,
myField13,
myField14,
myField15,
myField16,
myField17,
myField18,
myField19,
myField20,
myField21,
myField22,
myField23,
myField24,
myField25,
myField26,
myField27,
myField28,
myField29,
myField30,
myField31
FROM myTable;
MY_READ(myIdx , myCUR1, rep_table)
END REP_HELPER1;
--Am I passing here a copy of a cursor and not a reference?
PROCEDURE MY_READ(myIdx IN BINARY_INTEGER, cur IN SYS_REFCURSOR, rep_table IN OUT rep_table_T) IS
BEGIN
FETCH cur INTO rep_table(myIdx).day1, rep_table(myIdx).day2, rep_table(myIdx).day3, rep_table(myIdx).day4, rep_table(myIdx).day5,
rep_table(myIdx).day6, rep_table(myIdx).day7, rep_table(myIdx).day8, rep_table(myIdx).day9, rep_table(myIdx).day10,
rep_table(myIdx).day11, rep_table(myIdx).day12, rep_table(myIdx).day13, rep_table(myIdx).day14, rep_table(myIdx).day15,
rep_table(myIdx).day16, rep_table(myIdx).day17, rep_table(myIdx).day18, rep_table(myIdx).day19, rep_table(myIdx).day20,
rep_table(myIdx).day21, rep_table(myIdx).day22, rep_table(myIdx).day23, rep_table(myIdx).day24, rep_table(myIdx).day25,
rep_table(myIdx).day26, rep_table(myIdx).day27, rep_table(myIdx).day28, rep_table(myIdx).day29, rep_table(myIdx).day30,
rep_table(myIdx).day31;
IF cur%NOTFOUND THEN -- here comes ORA-06512 in a stack
dbms_output.put_line('ERROR' || nIndex);
END IF;
CLOSE cur;
END MY_READ;
When I do FETCH and close in REP_HELPER1 I'm not getting the error.
This simple example seems equivalent to yours and works fine:
declare
mycur sys_refcursor;
procedure my_read (cur sys_refcursor) is
job long;
sal number;
begin
fetch cur into job, sal;
dbms_output.put_Line(job||' '||sal);
close cur;
end;
begin
open mycur for select job, sal from emp where ename = 'KING';
my_read (mycur);
end;
So I don't think that is the issue.
According to https://www.techonthenet.com/oracle/errors/ora01001.php:
You tried to reference a cursor that does not yet exist. This may have
happened because:
You've executed a FETCH cursor before OPENING the cursor.
You've executed a CLOSE cursor before OPENING the cursor.
You've executed a FETCH cursor after CLOSING the cursor.

ORA-01002: fetch out of sequence in procedure

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

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: FORALL SAVE EXCEPTIONS not working

I'm trying to handle a FORALL exceptions with the SAVE EXCEPTIONS clause for the INSERT statement. The process contains two simple tables as per the below;
CREATE TABLE TBL1
("COL1" VARCHAR2(**40** BYTE) NOT NULL);
CREATE TABLE TBL2
("COL1" VARCHAR2(**20** BYTE) NOT NULL);
The only difference here is the size of a column type. Any attempt made to insert a string value of more than 20 characters would result in handling errors.
Here's the script;
DECLARE
TYPE REFCURTYPE IS REF CURSOR;
REFCUR REFCURTYPE;
TYPE ASSARRTYPE IS TABLE OF TBL2%ROWTYPE
INDEX BY PLS_INTEGER;
ASSARR ASSARRTYPE;
DML_ERRORS EXCEPTION;
PRAGMA EXCEPTION_INIT(DML_ERRORS, -24381);
BEGIN
OPEN REFCUR FOR
SELECT
COL1
FROM TBL1;
FETCH REFCUR BULK COLLECT INTO ASSARR;
CLOSE REFCUR;
FORALL i IN ASSARR.FIRST..ASSARR.LAST SAVE EXCEPTIONS
INSERT INTO TBL2
VALUES ASSARR(i);
COMMIT;
EXCEPTION
WHEN DML_ERRORS THEN
FOR j IN 1..SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(SQLERRM(-(SQL%BULK_EXCEPTIONS(j).ERROR_CODE)));
END LOOP;
COMMIT;
END;
Somehow, when executing the below block for a data set of more than just one record of 20+ chars, I only receive this single error message;
"ORA-06502: PL/SQL: numeric or value error: Bulk Bind: Truncated Bind"
Thanks in advance for any help.

Oracle procedure input is comma delimited, not returning any values

the procedure Im working has an input variable that is comma delimited. As of right now when I go to run a test script, I dont get any values back. Here is what I have so far.
procedure get_patient(
p_statusmnemonic_in in membermedicalreconcilationhdr.reconciliationstatusmnemonic%type,
p_return_cur_out out sys_refcursor,
p_err_code_out out number,
p_err_mesg_out out varchar2)
is
begin
open p_return_cur_out for
select h.primarymemberplanid,
h.assigneduserid,
h.accountorgid,
h.reconciliationstatusmnemonic,
h.estimatedenddt,
h.actualenddt,
h.inserteddt,
h.insertedby,
h.updateddt,
h.updatedby
from membermedicalreconcilationhdr h
where h.reconciliationstatusmnemonic in (p_statusmnemonic_in);
p_err_code_out := 0;
exception
when others then
p_err_code_out := -1;
p_err_mesg_out := 'error in get_patient=> ' || sqlerrm;
end get_patient;
Here is the test script:
set serveroutput on
declare
type tempcursor is ref cursor;
v_cur_result tempcursor;
errcode number;
errmesg varchar2(1000);
p_primarymemberplanid_in membermedicalreconcilationhdr.primarymemberplanid%type;
p_assigneduserid_in membermedicalreconcilationhdr.assigneduserid%type;
p_accountorgid_in membermedicalreconcilationhdr.accountorgid%type;
p_reconstatusmnemonic_in membermedicalreconcilationhdr.reconciliationstatusmnemonic%type;
p_estimatedenddt_in membermedicalreconcilationhdr.estimatedenddt%type;
p_actualenddt_in membermedicalreconcilationhdr.actualenddt%type;
p_inserteddate_in membermedicalreconcilationhdr.inserteddt%type;
p_insertedby_in membermedicalreconcilationhdr.insertedby%type;
p_updateddate_in membermedicalreconcilationhdr.updateddt%type;
p_updatedby_in membermedicalreconcilationhdr.updatedby%type;
begin
get_patient
('COMPLETE,SUSPENDED_PRIOR_TO_COMPARE',v_cur_result, errcode, errmesg);
--('COMPLETE',v_cur_result, errcode, errmesg);
loop
fetch v_cur_result into p_primarymemberplanid_in,p_assigneduserid_in,p_accountorgid_in,p_reconstatusmnemonic_in,
p_estimatedenddt_in,p_actualenddt_in,p_inserteddate_in,p_insertedby_in,
p_updateddate_in,p_updatedby_in;
dbms_output.put_line(' planid '||p_primarymemberplanid_in||' userid '||p_assigneduserid_in);
exit when v_cur_result%notfound;
end loop;
dbms_output.put_line(' error code '||errcode||' message '||errmesg);
end;
As of right now I get values back when I just have one input value, but when I try to do two I dont get anything. Ive done research and it looks like my select statement is correct so Im at a loss as to what Im doing wrong. Any help is appreciated, thanks.
If you can change the definition of the procedure, you are better served passing in a proper collection.
CREATE TYPE status_tbl IS TABLE OF VARCHAR2(100);
procedure get_patient(
p_statusmnemonic_in in status_tbl,
p_return_cur_out out sys_refcursor,
p_err_code_out out number,
p_err_mesg_out out varchar2)
is
begin
open p_return_cur_out for
select h.primarymemberplanid,
h.assigneduserid,
h.accountorgid,
h.reconciliationstatusmnemonic,
h.estimatedenddt,
h.actualenddt,
h.inserteddt,
h.insertedby,
h.updateddt,
h.updatedby
from membermedicalreconcilationhdr h
where h.reconciliationstatusmnemonic in (SELECT *
FROM TABLE(p_statusmnemonic_in));
...
Otherwise, you would either have to resort to using dynamic SQL (which would have security and performance implications) or you would need to write code to parse the comma-separated string into a collection and then use the TABLE operator to use that collection in the query.
Assuming you modify the signature of the procedure, the call will also have to change so that you are passing in a collection.
get_patient
(status_tbl('COMPLETE','SUSPENDED_PRIOR_TO_COMPARE'),
v_cur_result,
errcode,
errmesg);
And just to point it out, writing procedures that have error code and error message OUT parameters rather than throwing exceptions is generally highly frowned upon. It makes far more sense to eliminate those parameters and to just throw exceptions when you encounter an error. Otherwise, you are relying on every caller to every procedure to correctly check the returned status code and message (which your sample code does not do). And you are losing a ton of valuable information about things like exactly what line an error occurred on, what the error stack was, etc.
Since you don't post your table definitions or your sample data, it is impossible for us to test this code. Here is a quick demonstration, though, of how it would work
SQL> create table patient (
2 patient_id number primary key,
3 status varchar2(10),
4 name varchar2(100)
5 );
Table created.
SQL> insert into patient values( 1, 'COMPLETE', 'Justin' );
1 row created.
SQL> insert into patient values( 2, 'SUSPENDED', 'Bob' );
1 row created.
SQL> insert into patient values( 3, 'NEW', 'Kerry' );
1 row created.
SQL> commit;
Commit complete.
SQL> CREATE TYPE status_tbl IS TABLE OF VARCHAR2(100);
2 /
Type created.
SQL> ed
Wrote file afiedt.buf
1 create or replace procedure get_patients( p_statuses in status_tbl,
2 p_cursor out sys_refcursor )
3 as
4 begin
5 open p_cursor
6 for select *
7 from patient
8 where status in (select *
9 from table( p_statuses ));
10* end;
SQL> /
Procedure created.
SQL> variable rc refcursor;
SQL> exec get_patients( status_tbl('COMPLETE', 'SUSPENDED'), :rc );
PL/SQL procedure successfully completed.
SQL> print rc;
PATIENT_ID STATUS
---------- ----------
NAME
--------------------------------------------------------------------------------
1 COMPLETE
Justin
2 SUSPENDED
Bob

Resources