How to fetch Oracle reference cursor into table variable? - oracle

I am trying to load data from reference cursor into a table variable (or array), the reference cursor works if the table variable is based on existingtable %Rowtype but my reference cursor gets
loaded by joining multiple tables so let me try to demonstrate an example what i am trying to do and some one can help me
--created table
create table SAM_TEMP(
col1 number null,
col2 varchar(100) null
);
--created procedure which outputs results from that table
CREATE OR REPLACE
PROCEDURE SP_OUT_RefCur_PARAM(
C_RESULT OUT SYS_REFCURSOR
) IS
BEGIN
OPEN C_RESULT FOR
SELECT COL1,COL2
FROM SAM_TEMP;
END SP_OUT_RefCur_PARAM;
--seeing the output works like this
DECLARE
REFCUR SYS_REFCURSOR;
outtable SAM_TEMP%rowtype ;
BEGIN
SP_OUT_RefCur_PARAM(REFCUR);
LOOP
FETCH REFCUR INTO outtable;
EXIT WHEN REFCUR%NOTFOUND;
dbms_output.put_line(outtable.col1);
END LOOP;
CLOSE REFCUR;
END;
--but when i try to run below script it is giving error,i think i am missing something
DECLARE
REFCUR SYS_REFCURSOR;
TYPE REFTABLETYPE IS RECORD (COL1 NUMBER, COL2 VARCHAR(100));
TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
outtable TABLETYPE;
BEGIN
SP_OUT_RefCur_PARAM(REFCUR);
LOOP
FETCH REFCUR INTO outtable;
EXIT WHEN REFCUR%NOTFOUND;
dbms_output.put_line(outtable.col1);
END LOOP;
CLOSE REFCUR;
END;
Error report:
ORA-06550 line 9, column 21:
PLS-00597 expression 'OUTTABLE' in the INTO list is of wrong type
ORA-06550 line 9, column 3:
PL/SQL SQL Statement ignored
ORA-06550 line 11, column 32:
PLS-00302 component 'COL1' must be declared
Not sure what i am missing, Thanks in advance for your help

The name of variable in code above misleaded you. Your variable outtable is in table type. It isn't possible to fetch record data into table of records, but you can fetch it into record itself.
DECLARE
REFCUR SYS_REFCURSOR;
TYPE RECORDTYPE IS RECORD (COL1 NUMBER, COL2 VARCHAR(100));
outtable RECORDTYPE;
BEGIN
SP_OUT_RefCur_PARAM(REFCUR);
LOOP
FETCH REFCUR INTO outtable;
EXIT WHEN REFCUR%NOTFOUND;
dbms_output.put_line(outtable.col1);
END LOOP;
CLOSE REFCUR;
END;
Update: If you want to fetch all data for better performance your application you need to use BULK COLLECT statement:
DECLARE
REFCUR SYS_REFCURSOR;
TYPE RECORDTYPE IS
RECORD (COL1 NUMBER, COL2 VARCHAR(100));
TYPE TABLETYPE IS
TABLE OF REFTABLETYPE
INDEX BY PLS_INTEGER;
outtable TABLETYPE;
BEGIN
SP_OUT_RefCur_PARAM(REFCUR);
LOOP
FETCH REFCUR INTO BULK COLLECT outtable;
EXIT WHEN outtable.COUNT = 0;
FOR indx IN 1 .. outtable.COUNT
LOOP
dbms_output.put_line(outtable(indx).col1);;
END LOOP;
END LOOP;
CLOSE REFCUR;
END;
Note: memory consumption with the BULK statement is much more than without.
The most important thing to remember when you learn about and start to
take advantage of features such as BULK COLLECT is that there is no
free lunch. There is almost always a trade-off to be made somewhere.
The tradeoff with BULK COLLECT, like so many other
performance-enhancing features, is "run faster but consume more
memory." (Oracle Magazine)
But if you are just fetching and processing the rows - a row at a time there is no needs in BULK statement, just use the cursor FOR LOOP. (Ask Tom)

Another way to do it is this one:
DECLARE
REFCUR SYS_REFCURSOR;
TYPE REFTABLETYPE IS RECORD (COL1 NUMBER, COL2 VARCHAR(100));
TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
outtable TABLETYPE;
BEGIN
SP_OUT_RefCur_PARAM(REFCUR);
FETCH REFCUR BULK COLLECT INTO outtable;
FOR i in outtable.First..outtable.Last Loop
dbms_output.put_line(outtable(i).col1);
END LOOP;
CLOSE REFCUR;
END;

Related

provide Select statement in procedure parameter

Hi i'm working on this query in oracle and i need to provide many id to a procedure from a table. how can i provide each id from a table to my procedure. sory i'm kinda new at this im completely lost i dont know what to search.
here's
Procedure
PROCEDURE procedname(in_id in VARCHAR2)
select id from mytable
Here's what i tryed
execute procedname(select id from mytable);
but did no work
Is there a way to achive this?
Hope somone help me out with this
You can pass a collection of numbers. Here is an example on how to pass.
--sys.odcinumberlist is a collection which can hold numbers..
create procedure sp_test(i_id in sys.odcinumberlist)
as
l_cnt int;
begin
select count(*)
into l_cnt
from TABLE(i_id); /* the TABLE keyword is used to unfold the collection of numbers as rows..*/
dbms_output.put_line(l_cnt);
end;
/
--calling the stored procedure
begin
dbms_output.enable;
sp_test(sys.odcinumberlist(1,2,3,4,5,6)); /* here i am passing a list of numbers from 1 to 6*/
--the procedure will count the number of elements in the input collection which is 6
end;
/
You cannot directly use a SQL statement as an argument for a procedure or function. Since that needs an INTO clause in order to return the content of the SELECT statement. Your case suggests a CURSOR as needs to return all the records at a time. For this, a possible sample solution using SYS_REFCURSOR as an IN/OUT(or just OUT) type of parameter would be ;
SQL> CREATE TABLE mytable( id VARCHAR2(1) );
SQL> INSERT INTO mytable VALUES('A');
SQL> INSERT INTO mytable VALUES('B');
SQL> CREATE OR REPLACE PROCEDURE Convert_ID(p_myrecordset IN OUT SYS_REFCURSOR) AS
BEGIN
OPEN p_myrecordset FOR
SELECT id, ASCII( id )
FROM mytable
ORDER BY id;
END;
/
SQL> SET SERVEROUTPUT ON;
SQL> DECLARE
l_cursor SYS_REFCURSOR;
l_value1 mytable.id%TYPE;
l_value2 INT;
BEGIN
Convert_ID(p_myrecordset => l_cursor);
LOOP
FETCH l_cursor
INTO l_value1, l_value2;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_value1 || ' - ' || l_value2 );
END LOOP;
CLOSE l_cursor;
END;
/
A - 65
B - 66
Demo
To take each id from some table and call sometable(id), a PL/SQL loop would be something like this:
begin
for r in (
select id from sometable
)
loop
procedname(r.id);
end loop;
end;

Put Column Name in Variable and use it in output statement

Here is What i actually wanted to do, Fetch Data From a Table without knowing any columns but i.j.Column_Name gives an error, so i wanted to put it in a variable. After reading the comments i think it's not possible
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
CURSOR C2 IS SELECT Table_Name,Column_Name FROM user_tab_columns
WHERE data_type='VARCHAR2';
v_table Varchar2(256);
v_Col varchar2(200);
BEGIN
FOR i in C1 LOOP
FOR j in (SELECT Column_Name FROM user_tab_columns WHERE
Table_Name='Table_Name') LOOP
dbms_output.put_line(i.j.Column_Name);
END LOOP;
END LOOP;
END;
/
No, There is no Column Named v_Col
You can't refer to a field in a record (which is what the cursor loop is giving you) dynamically. If you need to do flexibly then you can use dbms_sql (possibly adapting this approach), but in the scenario you've shown you could use dynamic SQl to only get the column you want in the cursor:
-- dummy data
create table table_name (id number, column_name varchar2(10), other_col date);
insert into table_name values (1, 'Test 1', sysdate);
insert into table_name values (2, 'Test 2', sysdate);
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
v_Cur sys_refcursor;
v_Col varchar2(200);
v_Val varchar2(4000);
BEGIN
v_Col:= 'Column_Name';
OPEN v_Cur for 'SELECT ' || v_Col || ' FROM Table_Name';
LOOP
FETCH v_Cur INTO v_Val;
EXIT WHEN v_Cur%notfound;
dbms_output.put_line(v_val);
END LOOP;
END;
/
Test 1
Test 2
PL/SQL procedure successfully completed.
The downside of this is that whatever the data type of the target column is, you have to implicitly convert it to a string; but you would be doing that in the dbms_output call anyway. So if you change the column you want to print:
v_Col:= 'Other_Col';
then the output from my dummy data would be:
2018-08-23
2018-08-23
PL/SQL procedure successfully completed.
where the date value is being implicitly formatted as a string using my current NLS session settings.
You could get more advanced by checking the data type in user_tab_columns and changing the dynamic query and/or the fetch and handling, but it isn't clear what you really need to do.

BULK COLLECT INTO inside OPEN cursor FOR SELECT... doesn't populate collection

I have a procedure, which returns cursor as an output parameter. Inside SP the output cursor variable is opened as SELECT statement. I was to reuse records from this cursor for the following logic in the SP, and used BULK COLLECT clause to store them in a nested table. But found out, that without any exception this nested table is not populated.
I wrote a simple example to illustrate this behavior:
create table temp_table as
select 1 as col1 from dual
union all
select 2 as col1 from dual;
declare
v_cur sys_refcursor;
v_rec temp_table%rowtype;
procedure get_cursor(v_cur OUT sys_refcursor) is
type typ_temp_tab_tab is table of temp_table%rowtype;
v_tab typ_temp_tab_tab;
begin
v_tab:=typ_temp_tab_tab();
open v_cur for
select *
bulk collect into v_tab
from temp_table;
dbms_output.put_line('nested table''s records num: '||v_tab.count);
end;
begin
get_cursor(v_cur);
dbms_output.put_line('values in cursor: ');
loop
fetch v_cur into v_rec;
exit when v_cur%NOTFOUND;
dbms_output.put_line('>>'||v_rec.col1);
end loop;
end;
/
drop table temp_table;
And output is:
nested table's records num: 0
values in cursor:
>>1
>>2
Do you have any idea why it's not working and what is the best practice to reuse cursor's records inside a SP?
You don't write:
open v_cur for
select *
bulk collect into v_tab
from temp_table;
You just need:
select *
bulk collect into v_tab
from temp_table;
No "open v_cur for".
Unfortunately, I think that means that you cannot both (A) have the data in a nested table and (B) return the open cursor to the caller without running the query twice. Oracle must fetch all the rows in the cursor to perform the BULK COLLECT after which point the cursor would be useless to pass back to the caller.

Oracle: Insert rowtype data into another table

I have one table called event, and created another global temp table tmp_event with the same columns and definition with event. Is it possible to insert records in event to tmp_event using this ?
DECLARE
v_record event%rowtype;
BEGIN
Insert into tmp_event values v_record;
END;
There are too many columns in event table, I want to try this because I don't want to list all the columns.
Forget to mention: I will use this in the trigger, can this v_record be the object :new after insert on EVENT table ?
To insert one row-
DECLARE
v_record event%rowtype;
BEGIN
SELECT * INTO v_record from event where rownum=1; --or whatever where clause
Insert into tmp_event values v_record;
END;
Or a more elaborate version to insert all rows from event-
DECLARE
TYPE t_bulk_collect_test_tab IS TABLE OF event%ROWTYPE;
l_tab t_bulk_collect_test_tab;
CURSOR c_data IS
SELECT *
FROM event;
BEGIN
OPEN c_data;
LOOP
FETCH c_data
BULK COLLECT INTO l_tab LIMIT 10000;
EXIT WHEN l_tab.count = 0;
-- Process contents of collection here.
Insert into tmp_event values v_record;
END LOOP;
CLOSE c_data;
END;
/
In a trigger, yes it is possible but its like the chicken or the egg. You have to initialize every field of the rowtype with the :new column values like-
v_record.col1 := :new.col1;
v_record.col2 := :new.col2;
v_record.col3 := :new.col3;
....
Apparently, the PLSQL examples above cannot be used in a trigger since it would throw a mutating trigger error. And there is no other way for you to get the entire row in the trigger other than accessing each column separately as I explain above, so if you do all this why not directly use :new.col in the INSERT into temp_event itself, will save you a lot of work.
Also since you say it's a lot of work to mention all the columns, (in Oracle 11gR2) here's a quick way of doing that by generating the INSERT statement and executing it dynamically (although not tested for performance).
CREATE OR REPLACE TRIGGER event_air --air stands for "after insert of row"
AFTER INSERT ON EVENT
FOR EACH ROW
L_query varchar2(2000); --size it appropriately
BEGIN
SELECT 'INSERT INTO tmp_event VALUES ('|| listagg (':new.'||column_name, ',')
WITHIN GROUP (ORDER BY column_name) ||')'
INTO l_query
FROM all_tab_columns
WHERE table_name='EVENT';
EXECUTE IMMEDIATE l_query;
EXCEPTION
WHEN OTHERS THEN
--Meaningful exception handling here
END;
There is a way to insert multiple rows into table with %Rowtype.
checkout below example.
DECLARE
TYPE v_test IS TABLE OF TEST_TAB%rowtype;
v_test_tab v_test ;
EXECUTE immediate ' SELECT * FROM TEST_TAB ' bulk collect INTO v_test_tab ;
dbms_output.put_line('v_test_tab.count -->'||v_test_tab.count);
FOR i IN 1..v_test_tab.count
LOOP
INSERT INTO TEST_TAB_1 VALUES v_test_tab
(i
) ;
END LOOP;
END;
sum up to full working excample ...
DECLARE
TYPE t_bulk_collect_test_tab IS TABLE OF event%ROWTYPE;
l_tab t_bulk_collect_test_tab;
CURSOR c_data IS SELECT * FROM event;
BEGIN
OPEN c_data;
LOOP
FETCH c_data
BULK COLLECT INTO l_tab LIMIT 10000;
EXIT WHEN l_tab.count = 0;
FORALL i IN 1..l_tab.count
Insert into tmp_event values l_tab(i);
commit;
END LOOP;
CLOSE c_data;
END;
/

reserved word NUMBER used as column name causing error with Cursor declaration

... at least I think that is the problem.
I am writing a function which contains a cursor declaration that access a table where one of the column is the reserved word NUMBER (yeah, I know..). The function hit a problem at compile time:
Error(16,10): PL/SQL: ORA-06552: PL/SQL: Compilation unit analysis terminated ORA-06553: PLS-488: invalid variable declaration: object 'NUMBER' must be a type or subtype
MY code looks something like:
CURSOR my_cur
IS
SELECT "NUMBER", col2, col3
FROM tb1_x;
To make sure that is the problem, I changed the code to
CURSOR my_cur
IS
SELECT 'NUMBER', 'col2', 'col3'
FROM dual;
and it compiled alright, but obviously that's not what I want.
Unfortunately, I don't have the option to change the column name(sigh), and for the record
SELECT "NUMBER", col2, col3
FROM tb1_x;
works OK in normal SQL execution.
Anyway I can work around this problem? Any help is much appreciated!
We can also create record as well as uses column in cursor.fetching of data from cursor i surprising for me as i dint used this before .
Create table temp2("number" integer,id integer,name varchar2(200));
insert into temp2 values(1,1,'Gaurav Soni');
insert into temp2 values(2,2,'Niharika Saraf');
Commit;
DECLARE
type abc is record(
"number" number,
id NUMBER,
name varchar2(200));
v_rec abc;
TYPE v_cur IS REF CURSOR;
cur v_cur;
v_temp INTEGER;
BEGIN
OPEN cur FOR
SELECT "number", id, name FROM temp2;
LOOP
FETCH cur INTO v_rec;
EXIT when cur%notfound;
DBMS_OUTPUT.put_line('number is ' || v_rec."number");
DBMS_OUTPUT.put_line('id is ' || v_rec.id);
DBMS_OUTPUT.put_line('name is ' || v_rec.name);
end loop;
CLOSE cur;
end;
output
number is 1
id is 1
name is Gaurav Soni
number is 2
id is 2
name is Niharika Saraf
Create table temp2("number" integer);
insert into temp2 values(1);
insert into temp2 values(2);
insert into temp2 values(3);
commit;
DECLARE
TYPE v_cur IS REF CURSOR;
cur v_cur;
v_temp INTEGER;
BEGIN
OPEN cur FOR
SELECT "number"
FROM temp2;
FETCH cur
INTO v_temp;
DBMS_OUTPUT.put_line ('number is ' || v_temp);
CLOSE cur;
END;
output
number is 1
I am not able to replicate this issue ,but this is workign fine for me
Hmm. The structure of your Cursor declaration is a bit different from mine. Can you try this:
drop table temp2;
Create table temp2("NUMBER" integer );
insert into temp2 values(1);
Commit;
CREATE OR REPLACE FUNCTION func1
RETURN VARCHAR2
IS
l_dummy VARCHAR2(10) := '';
CURSOR cur1 IS
SELECT * FROM temp2;
BEGIN
FOR a_rec IN cur1
LOOP
l_dummy := 'dummy';
END LOOP;
RETURN l_dummy;
END func1;
/
SHOW ERRORS;
The above hit compilation error. Just change the 2nd line to get rid of the reserved word I manage to get it compiled. BTW I am using Oracle SQL Developer client connecting to Oracle 10.2 db.
Create table temp2("NUMBERxxx" integer );

Resources