PLS-00221: 'C1'(cursor) is not a procedure or is undefined - oracle

I am creating a package to use with Jasper reports where I learnt that I need SYS_REFCURSOR but I cannot seem to be able to Loop my cursors:eg
create or replace PACKAGE BODY fin_statement_spool
AS
PROCEDURE fin_main_spool(vacid in VARCHAR2, vfromdate in date, vtodate in date,c1 out SYS_REFCURSOR,c2 out SYS_REFCURSOR)
AS
cramount NUMBER;
dramount NUMBER;
countcr NUMBER;
countdr NUMBER;
BEGIN
OPEN c1 FOR
SELECT
.......;
OPEN c2 FOR
SELECT ........;
BEGIN
FOR i IN c1--Error is here
LOOP
rnum := 0;
cramount := 0;
dramount := 0;
countdr := 0;
countcr := 0;
..........
Isn't this the right way?

You appear to have confused explicit cursors, e.g.:
declare
cursor cur is
select dummy from dual;
begin
for rec in cur
loop
dbms_output.put_line(rec.dummy);
end loop;
end;
/
with a ref cursor - which is a pointer to an open cursor.
You would typically use a ref cursor to open a cursor in the db and pass it back to the calling app for it to loop through.
The way you have declared the ref cursors as out parameters and then tried to loop through them in the same procedure does not make sense - once you have fetched a record from a cursor, you cannot re-fetch it.
If you absolutely must loop through a ref cursor, you'd use this sort of syntax:
declare
cur sys_refcursor;
rec dual%rowtype;
begin
open cur for select dummy from dual;
loop
fetch cur into rec;
exit when cur%notfound;
dbms_output.put_line(rec.dummy);
end loop;
end;
/
but as I said, in general, you wouldn't be looping through ref cursors in the db, you'd be doing that in the calling code.
Perhaps if you updated your question with the requirements you're trying to fulfil, we could suggest a better way of doing it.

Related

Oracle REF Cursors

Is there a way to perform any processing after a ref cursor is opened for an application to read from?
So, I have a ref cursor: OPEN p_data FOR v_select. The application reads from that. Afterwards, can my procedure continue processing? Or is it done?
Please advise
Once the cursor is open, you can FETCH from it until you reach until no more data is retrieved. At that point or sooner if you're done processing the data sooner you can CLOSE the cursor.
declare
p_data sys_refcursor;
v_select varchar2(1000) := 'select * from dual';
type data_rec is record (dummy dual.dummy%type);
type data_tab is table of data_rec;
l_data data_tab;
begin
OPEN p_data FOR v_select;
LOOP
FETCH p_data BULK COLLECT INTO l_data LIMIT 100;
FOR r in 1 .. l_data.count LOOP
dbms_output.put_line(l_data(r).dummy);
END LOOP;
EXIT WHEN p_data%notfound;
END LOOP;
CLOSE p_data;
end;

How to pass array as input parameter in Oracle Function

I have a function to BULK insert data using FORALL.
create or replace type l_array_tab as table of number;
create or replace FUNCTION fn_insert_using_array(
L_TAB VARCHAR2,
L_COL_NAME VARCHAR2,
L_ARRAY L_ARRAY_TAB)
RETURN NUMBER
AS
SQL_STMT VARCHAR2(32767);
sql_count NUMBER;
BEGIN
FORALL i IN L_ARRAY.first .. L_ARRAY.LAST
EXECUTE immediate 'INSERT INTO my_table
Select * from '||L_TAB
||' where '||L_COL_NAME||' := :1' using L_ARRAY(i);
sql_count:= SQL%ROWCOUNT;
RETURN SQL_COUNT;
end;
I need to call this function from another stored procedure or plsql block in this example. While calling this function, I am getting error as wrong number or type of inputs.
This is how I am calling the function:
create or replace type l_array_orig_tab as table of number;
Declare
l_array_orig l_array_orig_tab :=l_array_orig_tab();
l_tab varchar2(30): ='my_tab_orig';
l_col_name varchar2(30) :='insert_id';
V_COUNT NUMBER;
cursor c1 is select * from my_tab_orig;
begin
open c1;
LOOP
FETCH c1 BULK COLLECT INTO l_array_orig limit 1000;
EXIT WHEN L_ARRAY_orig.COUNT =0;
V_COUNT:= fn_insert_using_array(L_TAB, L_COL_NAME,l_array_orig);
END LOOP;
END ;
Please suggest how to call the function.
I am getting error as wrong number or type of inputs
You are getting the error because l_array_orig_tab is a different type from l_array_tab. It doesn't matter that they have the same structure, as far as Oracle knows they are different types. Oracle is a database engine and it strongly enforces type safety. There is no duck typing here.
So the simplest solution is to use the correct type when calling the function:
Declare
l_array_orig l_array_tab :=l_array_tab(); -- change this declaration
l_tab varchar2(30): ='my_tab_orig';
l_col_name varchar2(30) :='insert_id';
V_COUNT NUMBER;
cursor c1 is select * from my_tab_orig;
begin
open c1;
LOOP
FETCH c1 BULK COLLECT INTO l_array_orig limit 1000;
EXIT WHEN L_ARRAY_orig.COUNT =0;
V_COUNT:= fn_insert_using_array(L_TAB, L_COL_NAME,l_array_orig);
END LOOP;
END ;
"The function fn_insert_using_array is in a different schema and also the Type."
So the schema which owns the function has granted you EXECUTE privilege on the function. But they also need to grant you EXECUTE on the type. This is their responsibility: they defined the function with a UDT in its signature so they have to give you all the privileges necessary to call it.
I don't don't whether this is a toy example just for posting on SO, but if it isn't there is no need to create a type like this. Instead use the documented Oracle built-in table of numbers, sys.odcinumberlist.
Is l_array_orig_tab != l_array_tab
you have to use the same type or do the cast between type.
Declare
l_array_orig l_array_orig_tab;
new_array l_array_tab;
l_tab varchar2(30): ='my_tab_orig';
l_col_name varchar2(30) :='insert_id';
V_COUNT NUMBER;
cursor c1 is select * from my_tab_orig;
begin
open c1;
LOOP
FETCH c1 BULK COLLECT INTO l_array_orig limit 1000;
select cast( l_array_orig as l_array_tab) into new_array from dual;
EXIT WHEN L_ARRAY_orig.COUNT =0;
V_COUNT:= fn_insert_using_array(L_TAB, L_COL_NAME,new_array);
END LOOP;
END ;
How cast works.
select cast( variable as destination_type) into var_destination_type from dual

How to build a dynamic PLSQL query to fetch records?

I am trying to create a stored procedure in Oracle and make a dynamic query work to get a bunch of records. I have read many examples but so far I can't get this to work unless I do this:
CREATE OR REPLACE PROCEDURE GiveMeResultSet(
v_par1 IN CHAR,
v_par2 IN CHAR,
v_par3 IN CHAR,
v_par4 IN VARCHAR2,
v_par5 IN VARCHAR2,
v_par6 IN VARCHAR2,
cur_typ OUT SYS_REFCURSOR)
IS
BEGIN
OPEN cur_typ FOR 'select * from complex_query';
--CLOSE cur_typ;
END;
And I am executing it this way:
var c refcursor;
execute GiveMeResultSet(null,null,null,null,null,null,:c);
print c;
This way I get the header names and the records from the query, but I am not closing the cursor that is fetching the results. If I close it then I get nothing at all. I guess leaving it open could cause some kind of memory leak problem at some point.
I have seen similar cases in Oracle documentation where they do something like this:
sql_stmt := 'SELECT * FROM emp';
OPEN emp_cv FOR sql_stmt;
LOOP
FETCH emp_cv INTO emp_rec;
EXIT WHEN emp_cv%NOTFOUND;
-- process record
END LOOP;
CLOSE emp_cv;
But I have no clue what goes on the "process record" part of the code which would allow to get the whole set of records at the end, plus that my record has a complex structure that doesn't fit with a fixed set of fields as in a table.
Can you please show me the proper way to do this?.
Thanks a lot.
ok CodeRoller, here is my sample code for a unspecified ref cursor:
Code of the Function which returns the ref cursor:
create or replace function test_ref_cursor(pi_sql_statement in varchar2) return SYS_REFCURSOR is
result_cursor SYS_REFCURSOR;
begin
open result_cursor for pi_sql_statement;
return result_cursor;
end;
Now, in the next step I use this function to get data from v$parameter:
declare
type t_my_cursor is ref cursor;
my_cursor t_my_cursor;
l_rec v$parameter%rowtype;
begin
my_cursor := test_ref_cursor('select * from v$parameter');
loop
fetch my_cursor into l_rec;
exit when my_cursor%notfound;
dbms_output.put_line(l_rec.name || ' = ' || l_rec.value);
end loop;
close my_cursor;
end;
Take care, to close the ref-cursor!

How to populate nested object table in pl/sql block?

I struggle a problem, which, i think, is rather simple.
I have a type T_OPERATION_TAG in a database which is created as:
CREATE OR REPLACE TYPE t_operation_tag AS OBJECT(
tag_name VARCHAR2(30),
tag_value VARCHAR2(30),
CONSTRUCTOR FUNCTION t_operation_tag RETURN SELF AS RESULT
)
I also have another type T_OPERATION_TAGS, which is defined as follows
CREATE OR REPLACE TYPE t_operation_tags AS TABLE OF t_operation_tag;
Then in my pl/sql block i have the following code
DECLARE
p_op_tags t_operation_tags;
BEGIN
p_op_tags := t_operation_tags();
FOR i IN (SELECT tag_name, tag_value
FROM op_tags_table
WHERE some_condition)
LOOP
--How to append new lines to p_op_tags ?
END LOOP;
END;
So, if the SELECT-query in the FOR LOOP returns,e.g., five lines then how can I populate my P_OP_TAGS object table with these five lines?
Like this:
DECLARE
p_op_tags t_operation_tags;
p_cursor sys_refcursor;
p_limit number := 5;
BEGIN
open p_cursor for
SELECT t_operation_tag(tag_name, tag_value)
FROM op_tags_table
;
fetch p_cursor bulk collect into p_op_tags limit p_limit;
DBMS_OUTPUT.put_line(p_op_tags(4).tag_name);
close p_cursor;
END;
Or if you prefer the loop clause:
DECLARE
p_op_tag t_operation_tag;
p_op_tags t_operation_tags;
p_limit number := 5;
BEGIN
p_op_tags := t_operation_tags();
for i in (SELECT tag_name, tag_value
FROM op_tags_table
WHERE some_condition
and rownum < p_limit + 1)
loop
p_op_tag := t_operation_tag(i.tag_name, i.tag_value);
p_op_tags.extend();
p_op_tags(p_op_tags.COUNT) := p_op_tag;
end loop;
DBMS_OUTPUT.put_line(p_op_tags(4).tag_name);
END;
/
You don't really need a cursor or loop at all, if you're populating the collection entirely from your query; you can bulk collect straight into it:
DECLARE
p_op_tags t_operation_tags;
BEGIN
SELECT t_operation_tag(tag_name, tag_value)
BULK COLLECT INTO p_op_tags
FROM op_tags_table
WHERE some_condition;
...
END;
/

Inserting a RefCursor into a table

I have a simple function where I run a stored procedure that returns a RefCursor and I try to use that RefCursor to insert data to a temporary table. I get the following error when trying to do so:
SQL Error: ORA-00947: not enough values
I know for a fact that the refcursor returns exactly the same number of values as the temporary table has, correct column names, their order and their type. I ran print RefCursor and I can see all of the data. Here's the code:
var r refcursor;
EXEC SCHEMA.PACKAGE.SPROC(:r);
insert into SCHEMA.TEMP_TABLE
values
(r);
I have to add that the stored procedure has a refcursor defined as a OUT parameter so it returns a correct type. Using print r; prints the correct data.
What am I doing wrong?
EDIT:
Based on a suggestion I tried to use a fetch to a rowtype variable, but getting Invalid Number exception whenever I attempt to fetch a row:
DECLARE
cur SYS_refcursor;
rec SCHEMA.TEMP_TABLE%rowtype;
begin
SCHEMA.PACKAGE.SPROC( cur );
LOOP
FETCH cur INTO rec;
EXIT WHEN cur%NOTFOUND;
INSERT INTO SCHEMA.TEMP_TABLE
VALUES rec;
END LOOP;
EXCEPTION
WHEN INVALID_NUMBER THEN
DBMS_output.put_line(rec.move_id);
end;
I added the exception block to see which row is failing and needless to say it is the first one. The stored procedure I run returns a refcursor of a select query from multiple tables. The temporary table defined as the exact copy of the refcursor columns and their types. Not sure what could be causing the exception.
You can't insert into a table from a refcursor. You could write a procedure that fetches from the cursor and inserts into the table. If schema.package.sproc returns a ref cursor of temp_table%rowtype, you could do something like
DECLARE
cur sys_refcursor;
rec schema.temp_table%rowtype;
BEGIN
schema.package.sproc( cur );
LOOP
FETCH cur INTO rec;
EXIT WHEN cur%NOTFOUND;
INSERT INTO schema.temp_table
VALUES rec;
END LOOP;
END;
You can use LOOP + FETCH to filter the row in SYS_REFCURSOR.
Exit the LOOP using "EXIT WHEN ref_%notfound;"
Example: Have 2 functions.
FUNCTION get_data
RETURN SYS_REFCURSOR
is
s varchar2(2000);
ref_ SYS_REFCURSOR;
begin
s := 'select username, password, email from user_info where id < 100';
OPEN ref_ FOR s;
return ref_;
end;
FUNCTION load_data_in_table
RETURN varchar2
is
s varchar2(2000);
puser_name varchar2(2000);
ppassword varchar2(2000);
pemail varchar2(2000);
ref_ SYS_REFCURSOR;
begin
ref_ := get_data();
LOOP
--process_record_statements;
FETCH ref_ into puser_name, ppassword, pemail;
s := 'INSERT INTO TEST_USER_EXP VALUES(:user_name, :password, :email)';
EXECUTE IMMEDIATE s USING puser_name, ppassword, pemail;
EXIT WHEN ref_%notfound;
END LOOP;
commit;
return 'OK';
end;

Resources