How to fetch a ref_cursor into Oracle's WITH CLAUSE - oracle

Is it possible to use a ref_cursor with ORACLE WITH CLAUSE. For example, I have the following scenario. First I have a procedure which returns a ref_cursor
PROCEDURE p_return_cursor(p_id IN NUMBER, io_cursor OUT t_cursor)
AS
BEGIN
OPEN io_cursor FOR
SELECT col1, col2
FROM Table1 t
WHERE t.id = p_id;
END;
Second, I have another procedure on which I make a call to p_return_cursor:
PROCEDURE p_test(p_cid IN NUMBER)
AS
l_cursor t_cursor;
l_rec Table1%ROWTYPE;
BEGIN
p_return_cursor(p_id => p_cid, io_cursor => l_cursor);
-- CODE GOES HERE
...
Now, my question is, can I make a temp table using the Oracle's WITH CLAUSE using the cursor; something like:
...
WITH data AS (
LOOP
FETCH l_cursor INTO l_rec;
EXIT WHEN l_cursor%NOTFOUND;
SELECT l_rec.col1, l_rec.col2 FROM DUAL;
END LOOP;
CLOSE l_cursor;
)

You cannot do it directly. You can, however, BULK COLLECT your cursor into a PL/SQL table variable and use that in a WITH clause.
Be careful of memory usage if the cursor contains many rows.
Full example:
CREATE TABLE table1 ( col1 NUMBER, col2 NUMBER );
INSERT INTO table1 ( col1, col2 ) SELECT rownum, 100+rownum FROM DUAL CONNECT BY ROWNUM <= 15;
COMMIT;
CREATE OR REPLACE PACKAGE se_example AS
TYPE t_cursor IS REF CURSOR
RETURN table1%ROWTYPE;
TYPE l_rec_tab IS TABLE OF table1%ROWTYPE;
PROCEDURE p_test (p_cid IN NUMBER);
END se_example;
CREATE OR REPLACE PACKAGE BODY se_example AS
-- private
PROCEDURE p_return_cursor (p_id IN NUMBER, io_cursor OUT t_cursor) AS
BEGIN
OPEN io_cursor FOR
SELECT col1,
col2
FROM table1 t;
--WHERE t.id = p_id; -- I didn't put "id" column in my sample table, sorry...
END p_return_cursor;
PROCEDURE p_test (p_cid IN NUMBER) IS
l_cursor t_cursor;
l_tab l_rec_tab;
l_dummy NUMBER;
BEGIN
p_return_cursor (p_id => p_cid, io_cursor => l_cursor);
FETCH l_cursor BULK COLLECT INTO l_tab;
-- *** instead of this
-- WITH data AS (
-- LOOP
-- FETCH l_cursor INTO l_rec;
-- EXIT WHEN l_cursor%NOTFOUND;
-- SELECT l_rec.col1, l_rec.col2 FROM DUAL;
-- END LOOP;
-- CLOSE l_cursor;
-- )
-- '
--
-- *** do this
WITH data AS
( SELECT col1, col2 FROM TABLE(l_tab) )
SELECT sum(col1 * col2) INTO l_dummy
FROM data;
dbms_output.put_line('result is ' || l_dummy);
END p_test;
END se_example;
begin
se_example.p_test(100);
end;

Related

Oracle PL/SQL - procedure with array parameter

I need to write an oracle procedure which will have an array of ID's as parameter.
Then I will return a cursor which contains result of select(1).
(1) - select * from table where id in(ID's)
As an option we can pass a string param and then convert string to array.
DECLARE
info sys_refcursor ;
error varchar(255);
BEGIN
package.test_function('1,2,3',info ,error);// info will contain a result cursor for select(1)
END;
Do you have other ideas?
You can create a user-defined collection type:
CREATE TYPE int8_list IS TABLE OF NUMBER(8,0);
Then your package:
CREATE PACKAGE pkg_name AS
PROCEDURE proc_name (
i_ids IN int8_list,
o_cursor OUT SYS_REFCURSOR
);
END;
/
CREATE PACKAGE BODY pkg_name AS
PROCEDURE proc_name (
i_ids IN int8_list,
o_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN o_cursor FOR
SELECT * FROM table_name WHERE id MEMBER OF i_ids;
END;
END;
/
Then you can call the procedure:
DECLARE
v_info sys_refcursor ;
v_id TABLE_NAME.ID%TYPE;
v_value TABLE_NAME.VALUE%TYPE;
BEGIN
pkg_name.proc_name(int8_list(1,2,3), v_info);
LOOP
FETCH v_info INTO v_id, v_value;
EXIT WHEN v_info%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_id || ' ' || v_value);
END LOOP;
END;
/
Which, for the sample data:
CREATE TABLE table_name (id, value) AS
SELECT LEVEL, CHR(64+LEVEL) FROM DUAL CONNECT BY LEVEL <= 5;
Outputs:
1 A
2 B
3 C
db<>fiddle here

How to pass string of comma-separated numbers to stored procedure in condition for numeric field?

I have a stored procedure like below where multiple employee IDs will be passed as comma-separated value (multiple IDs). It is throwing error as "ORA-01722: invalid number". I know it's because of passing varchar2 variable for the numeric ID column. But is there any way we can achieve this simply?
create or replace PROCEDURE Fetch_Emp_Name(Emp_id in varchar2)
IS
BEGIN
select Name from EMP where id in (emp_id);
END;
You can use dynamic sql.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
execute immediate
'select Name from EMP where id in (' || 'emp_id' || ')'
into
v_result;
end;
Also you can use package dbms_sql for dynamic sql.
Update
Another approach. I think may be better.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
select
Name
from
EMP
where
id in
(
select
to_number(regexp_substr(emp_id, '[^,]+', 1, level))
from
dual
connect by regexp_substr(emp_id, '[^,]+', 1, level) is not null
);
exception
when no_data_found then
-- error1;
when too_many_rows then
-- error2;
end;
Sorry for before, I did not get the question in the right way. If you get a lot of IDs as different parameters, you could retrieve the list of names as an string split by comma as well. I put this code where I handled by regexp_substr the name of different emp_ids you might enter in the input parameter.
Example ( I am assuming that the IDs are split by comma )
create or replace PROCEDURE Fetch_Emp_Name(p_empid in varchar2) IS
v_result varchar2(4000);
v_append emp.name%type;
v_emp emp.emp_id%type;
counter pls_integer;
i pls_integer;
begin
-- loop over the ids
counter := REGEXP_COUNT(p_empid ,'[,]') ;
--dbms_output.put_line(p_empid);
if counter > 0
then
i := 0;
for r in ( SELECT to_number(regexp_substr(p_empid,'[^,]+',1,level)) as mycol FROM dual CONNECT BY LEVEL <= REGEXP_COUNT(p_empid ,'[,]')+1 )
loop
--dbms_output.put_line(r.mycol);
v_emp := r.mycol ;
select name into v_append from emp where emp_id = v_emp;
if i < 1
then
v_result := v_append ;
else
v_result := v_result ||','|| v_append ;
end if;
i := i + 1;
end loop;
else
v_emp := to_number(p_empid);
select name into v_result from emp where emp_id = v_emp;
end if;
dbms_output.put_line(v_result);
exception
when no_data_found
then
raise_application_error(-20001,'Not Employee found for '||v_emp||' ');
when too_many_rows
then
raise_application_error(-20002,'Too many employees for id '||v_emp||' ');
end;
Test
SQL> create table emp ( emp_id number, name varchar2(2) ) ;
Table created.
SQL> insert into emp values ( 1 , 'AA' );
1 row created.
SQL> insert into emp values ( 2 , 'BB' ) ;
1 row created.
SQL> commit;
SQL> insert into emp values ( 3 , 'CC' ) ;
1 row created.
SQL> select * from emp ;
EMP_ID NA
---------- --
1 AA
2 BB
3 CC
SQL> exec Fetch_Emp_Name('1') ;
AA
PL/SQL procedure successfully completed.
SQL> exec Fetch_Emp_Name('1,2,3') ;
AA,BB,CC
PL/SQL procedure successfully completed.
SQL>

How to extract value inside with statement?

I want to initialize variable in pl/sql In the following way. But the value of the variable is null. how can I modify this code?
CREATE OR REPLACE PROCEDURE P_TEMP
(
pList0 OUT SYS_REFCURSOR
)
AS
TEMP_COL1 VARCHAR2(100);
TEMP_COL2 VARCHAR2(100);
BEGIN
OPEN cv_1 FOR
WITH TEMP_TABLE(COLUMN_1, COLUMN_2 ,...., COLUMN_N)
AS (SELECT COL_1, COL_2, ... , COL_N)
SELECT COL_1, COL_2
INTO TEMP_COL1, TEMP_COL2
FROM TEMP_TABLE;
DBMS_OUTPUT.PUT_LINE(TEMP_COL1); -- null!!
DBMS_OUTPUT.PUT_LINE(TEMP_COL2); -- null!!
END;
Don't open a cursor; just use the SELECT:
CREATE OR REPLACE PROCEDURE P_TEMP
(
pList0 OUT SYS_REFCURSOR
)
AS
TEMP_COL1 VARCHAR2(100);
TEMP_COL2 VARCHAR2(100);
BEGIN
WITH TEMP_TABLE(COLUMN_1, COLUMN_2 ,...., COLUMN_N) AS (
SELECT COL_1, COL_2, ... , COL_N
FROM your_table -- Need to select from a table.
)
SELECT COLUMN_1, COLUMN_2 -- Use the aliases you set in the sub-query factoring clause.
INTO TEMP_COL1, TEMP_COL2
FROM TEMP_TABLE
WHERE ROWNUM = 1 -- Only want a single row returned into the variables;
-- Or simpler
SELECT COL_1, COL_2
INTO TEMP_COL1, TEMP_COL2
FROM your_table
WHERE ROWNUM = 1 -- Only want a single row returned into the variables;
DBMS_OUTPUT.PUT_LINE(TEMP_COL1);
DBMS_OUTPUT.PUT_LINE(TEMP_COL2);
END;
/
You are missing the FETCH loop from the cursor. The easiest way to do this to use an implicit cursor:
CREATE OR REPLACE PROCEDURE P_TEMP AS
BEGIN
FOR rec IN
(
WITH temp_table(column_1, column_2 ,...., column_n) AS
(SELECT col_1, col_2, ... , col_n)
SELECT col_1, col_2
FROM temp_table
) LOOP
DBMS_OUTPUT.PUT_LINE(rec.col1);
DBMS_OUTPUT.PUT_LINE(rec.col2);
END LOOP;
END;
If, on the other hand, you only want to return the opened cursor, then you must not fetch from it yourself.
If you want to do both, i.e. fetch data yourself and return the cursor, then you must open the cursor twice for the same query. Once to process it yourself, once to give it to the caller to process it.
CREATE OR REPLACE PROCEDURE P_TEMP
(
out_cursor OUT SYS_REFCURSOR
)
AS
v_col1 VARCHAR2(100);
v_col2 VARCHAR2(100);
v_sql VARCHAR2(1000) := 'WITH temp_table ... SELECT col_1, col_2 FROM temp_table';
BEGIN
OPEN out_cursor FOR v_sql;
LOOP
FETCH out_cursor INTO v_col1, v_col2;
EXIT WHEN out_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('col_1: ' || v_col1);
DBMS_OUTPUT.PUT_LINE('col_2: ' || v_col2);
END LOOP;
OPEN out_cursor FOR v_sql;
END;

oracle out ref cursor to a loop

How to pass the 'values' of the output cursor o_cur to a further loop?
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
-- the question is related to this block:
for i in o_cur
loop
DBMS_OUTPUT.PUT_LINE(i.notnow);
end loop;
-----------------------
END;
FETCH is usually handled outside the procedure when the refcursor is an OUT variable
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
END;
/
SET SERVEROUTPUT ON
DECLARE
v_cur SYS_REFCURSOR;
v_notnow DATE;
v_today DATE;
BEGIN
dyn_cursor(v_cur);
LOOP
FETCH v_cur INTO
v_notnow,
v_today;
EXIT WHEN v_cur%notfound;
dbms_output.put_line(v_notnow);
END LOOP;
END;
/
02-10-18
04-10-18
PL/SQL procedure successfully completed.
This seems a strange construct. I would create a view you can reuse at different places.
CREATE OR REPLACE VIEW VW_Times AS
SELECT sysdate-1 notnow, sysdate today FROM dual
UNION ALL
SELECT sysdate+1 notnow, sysdate today FROM dual;
and
CREATE OR REPLACE PROCEDURE OutputTime ()
IS
CURSOR o_cur IS
SELECT * FROM VW_Times;
BEGIN
FOR i IN o_cur
LOOP
DBMS_OUTPUT.PUT_LINE(i.notnow);
END LOOP;
END;
Tanks to post of Kaushik Nayak, I found the acceptable solution:
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
type array is table of VARCHAR2(20) index by binary_integer;
l_datapoint1 array;
l_datapoint2 array;
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
LOOP
FETCH o_cur bulk collect into l_datapoint1, l_datapoint2;
for i in 1 .. l_datapoint1.count
loop
DBMS_OUTPUT.PUT_LINE( l_datapoint1(i) || ', ' || l_datapoint2(i) );
end loop;
exit when o_cur%notfound;
end loop;
close o_cur;
END;

ORA-06504: PL/SQL: Return types of Result Set variables while execution

I created an Object and procedure as below and while execution i get the below error.
ORA-06504: PL/SQL: Return types of Result Set variables or query do
not match ORA-06512: at line 8
CREATE OR REPLACE TYPE OBJ_TST AS OBJECT
(
COl_ID NUMBER (30, 0),
Col_DATE TIMESTAMP (6)
);
/
create or replace TYPE OBJ_TBL AS TABLE OF OBJ_TST;
/
CREATE OR REPLACE PROCEDURE TST_OBJ (input_date IN DATE,
out_cur OUT SYS_REFCURSOR )
AS
l_tab OBJ_TBL := OBJ_TBL ();
BEGIN
SELECT OBJ_TST (ti.col_id, ti.col_date)
BULK COLLECT INTO l_tab
FROM MY_TBL ti
WHERE ti.create_date BETWEEN input_date AND input_date + 1;
Open o_cur for select col_id,col_date from table(l_tab);
END TST_OBJ;
/
Execution brings me the above mentioned error. MY_TBL has column data type of (col_id and col_date) same as of my object.
DECLARE
a SYS_REFCURSOR;
var1 OBJ_TBL;
BEGIN
TST_OBJ (input_date => '21-Aug-2017', out_cur => a);
FETCH a bulk collect INTO var1;
For rec in 1..var1.count
LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/
ORA-06504: PL/SQL: Return types of Result Set variables or query do
not match ORA-06512: at line 8
However when i excute like this it works fine:
DECLARE
a SYS_REFCURSOR;
var1 NUMBER;
var2 TIMESTAMP (6);
BEGIN
TST_OBJ (i_date => '21-Aug-2017', out_cur => a);
LOOP
FETCH a INTO var1, var2;
EXIT WHEN a%NOTFOUND;
DBMS_OUTPUT.put_line (var1 ||' '|| var2);
END LOOP;
END;
Can anyone please suggest whats wrong here ?
You're using a table collection expression to unnest your table collection:
Open out_cur for select col_id,col_date from table(l_tab);
The query is returning two relational columns, not a single object, so your cursor has two columns too. Trying to bulk collect two relational columns into a single object in your anonymous block is throwing the exception.
You could, I suppose, recombine them as objects:
Open out_cur for select OBJ_TST(col_id,col_date) from table(l_tab);
or if you don't want to explicitly list the column/field names:
Open out_cur for select cast(multiset(select * from table(l_tab)) as obj_tbl) from dual;
But then in your example having the table type is a bit pointless, and you can just do:
CREATE OR REPLACE PROCEDURE TST_OBJ (input_date IN DATE,
out_cur OUT SYS_REFCURSOR )
AS
BEGIN
Open out_cur for
SELECT OBJ_TST (ti.col_id, ti.col_date)
FROM MY_TBL ti
WHERE ti.create_date BETWEEN input_date AND input_date + 1;
END TST_OBJ;
/
But I image you have some other use for the collection inside the function - modifying it before querying and returning it. Or you could make the second argument of OBJ_TBL type instead of a ref cursor, so the caller doesn't have to bulk collect that into its own local collection itself.
DECLARE
a SYS_REFCURSOR;
var1 OBJ_TBL;
BEGIN
TST_OBJ (input_date => '21-Aug-2017', out_cur => a);
FETCH a bulk collect INTO var1;
For rec in 1..var1.count
LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/
The cursor a has two columns and you are trying to bulk collect them into a single variable. Oracle will not wrap them in a OBJ_TST object and it can't match them.
Why use cursors at all:
CREATE OR REPLACE PROCEDURE TST_OBJ (
input_date IN DATE,
out_objs OUT OBJ_TBL
)
AS
BEGIN
SELECT OBJ_TST( col_id, col_date)
BULK COLLECT INTO out_objs
FROM MY_TBL
WHERE create_date BETWEEN input_date AND input_date + 1;
END TST_OBJ;
/
Then you can just do:
DECLARE
var1 OBJ_TBL;
BEGIN
TST_OBJ (
input_date => DATE '2017-08-21',
out_objs => var1
);
For rec in 1..var1.count LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/

Resources