Are cursors necessary for queries in a procedure? - oracle

I'm rather new to Oracle and I was asked to write a procedure to query some data from a table. I built it with 2 arguments, a cursor and a number. Essentially I have:
PROCEDURE PROC_NAME (
cursor_name IN OUT NOCOPY MY_DEFINED_CURSOR_TYPE,
a_number IN NUMBER);
AS
BEGIN
OPEN CURSOR_NAME FOR
SELECT
column
FROM
table
WHERE
table.dat_value > (SYSDATE - a_number);
END PROC_NAME;
It works like a charm, and I'm able to fetch the column from the cursor. My problem is that the requester doesn't want to pass in a cursor, they just want to pass in the number. I've never created a procedure that doesn't use a cursor to return the values of a query and the examples I have seen only ever do it that way. Is this possible?

You can use a collection:
CREATE PROCEDURE PROC_NAME (
a_number IN NUMBER,
numbers OUT SYS.ODCINUMBERLIST
)
AS
BEGIN
SELECT number_value
BULK COLLECT INTO numbers
FROM table_name
WHERE date_value > (SYSDATE - a_number);
END PROC_NAME;
Also, if you don't want to pass in a cursor then you can just pass one out:
CREATE OR REPLACE PROCEDURE PROC_NAME (
a_number IN NUMBER,
numbers OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN numbers FOR
SELECT number_value
FROM table_name
WHERE date_value > (SYSDATE - a_number);
END PROC_NAME;

Use a function instead ? But it's just a "stylistic" difference compared to procedure out parameter. Anyway the returned value have to be implicitly passed (unlike in SQL Server as noted by #ShannonSeverance).
function f(
p_days in number
) return my_defined_cursor_type is
v_cur my_defined_cursor_type;
begin
open v_cur for
select
column
from
table
where
table.dat_value > (sysdate - p_days);
return v_cur;
end;
/
Usage
declare
v_cur my_defined_cursor_type := f(42);
begin
-- use v_cur as you like
end;

If you want to apply some PL/SQL logic, but remain using select for querying the data (i.e not pass in a cursor - use pipelined functions.
You need to define the TYPEs of the result row and table; FETCH the cursor and PIPE the results in the function.
CREATE or replace type MY_DEFINED_ROW_TYPE as object
(
txt VARCHAR2(30)
);
/
create or replace type MY_DEFINED_TABLE_TYPE as table of MY_DEFINED_ROW_TYPE
/
create or replace function FUN_NAME( a_number IN NUMBER) return
MY_DEFINED_TABLE_TYPE
PIPELINED
as
cur MY_DEFINED_CURSOR_TYPE;
v_txt varchar2(30);
begin
OPEN cur
FOR
SELECT
column
FROM table
WHERE table.dat_value > (SYSDATE - a_number);
LOOP
FETCH cur INTO v_txt;
EXIT WHEN cur%NOTFOUND;
pipe row(v_txt);
END LOOP;
return;
end;
/
The usage:
select * from table (FUN_NAME(2));

Related

How to return thousands of ids from a function accepting input to a select query

Create or replace function f(v_input nvarchar2)
Return t_table
Is
v_table t_table;
BEGIN
Select id bulk collect into v_table from table1 where "USER"=v_input ;
Return v_table;
End;
/
Select * from table2 where id in (select * from table(f(‘abc’)));
How to implement this using pipelined table functions in oracle 19.0 using limit clause with bulk collect to reduce the memory usage and improve performance?
If not pipelined functions what else can we use?
How to implement this using pipelined table functions in oracle 19.0 using limit clause with bulk collect to reduce the memory usage and improve performance?
A simple pipelined function is:
CREATE OR REPLACE FUNCTION f(v_input nvarchar2)
RETURN t_table PIPELINED
IS
BEGIN
FOR n IN ( SELECT id FROM table1 WHERE "USER" = v_input )
LOOP
PIPE ROW (n.id);
END LOOP;
END;
/
or if you want to use a cursor and process the ids in bulk:
CREATE OR REPLACE FUNCTION f(v_input nvarchar2)
RETURN t_table PIPELINED
IS
CURSOR v_cur IS
SELECT id
FROM table1
WHERE "USER" = v_input;
v_table t_table;
BEGIN
OPEN v_cur;
LOOP
FETCH v_cur BULK COLLECT INTO v_table LIMIT 100;
FOR i IN 1 .. v_table.COUNT LOOP
PIPE ROW (v_table(i));
END LOOP;
EXIT WHEN v_cur%NOTFOUND;
END LOOP;
CLOSE v_cur;
END;
/
Then you can use:
SELECT *
FROM table2
WHERE id MEMBER OF f('abc');
db<>fiddle here
Pipeline functions are not so good for your purposes: extra context switches (SQL <-> PL/SQL), row-by-row pipe, extra user-defined type, etc.
Since you are on Oracle 19, I'd suggest you to try SQL Table Macros (19.6+):
Simple example:
DBFiddle
Create or replace function f(v_input nvarchar2)
return varchar2 sql_macro(table)
is
begin
return 'Select id from table1 where "USER"=v_input';
end;
/
then you can use it as a simple table in your queries:
Select *
from f('user1') ff
join table2 t2
on t2.id=ff.id;

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;

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;
/

ORACLE - Return cursor from stored procedure

I need to return a cursor object from a stored procedure, but I need to process data first.
For example, let's consider this simple stored procedure:
create or replace PROCEDURE TEST
(
query_str IN VARCHAR2,
CURSOR_ OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN CURSOR_ FOR query_str;
END;
This procedure returns data as is, with no postprocessing.
The improvement I need is the following:
process data coming from the execution of query_str;
return the processed data in the form of a cursor.
Anyone could suggest me a way to accomplish this?
Thanks
It is difficult to do what you are suggesting with dynamic SQL (unless all the statements you are passing to the procedure all have a similar output format). If you can know what the SELECT will be then you can store it in a collection and process it:
CREATE TABLE TEST_DATA ( id, name, dt ) AS
SELECT 1, 'A', SYSDATE FROM DUAL UNION ALL
SELECT 2, 'B', DATE '2017-01-01' FROM DUAL;
CREATE TYPE processed_data_obj AS OBJECT(
id INTEGER,
etag VARCHAR2(20)
);
/
CREATE OR REPLACE TYPE processed_data_table AS TABLE OF processed_data_obj;
/
CREATE OR REPLACE PROCEDURE TEST
(
CURSOR_ OUT SYS_REFCURSOR
)
AS
processed PROCESSED_DATA_TABLE;
BEGIN
-- Process it in the select statement
SELECT processed_data_obj(
id,
name || '_' || ROUND( ( dt - DATE '1970-01-01' ) * 24*60*60 )
)
BULK COLLECT INTO processed
FROM test_data;
-- Process it more in PL/SQL
FOR i IN 1 .. processed.COUNT LOOP
processed[i].etag := processed[i].etag || '_' || i;
END LOOP;
OPEN cursor_out FOR
SELECT *
FROM TABLE( processed );
END;
/

Oracle pipelined function with record type

I have a situation like the following:
CREATE OR REPLACE FUNCTION GET_CURSOR_PIPELINE(placeholder IN NUMBER)
RETURN MY_RECORD_TYPE PIPELINED IS
TYPE CURSOR_TYPE IS REF CURSOR;
myCursor CURSOR_TYPE;
TYPE RECORD_TYPE IS RECORD(
record_id NUMBER,
firstname VARCHAR(50)
);
resultSet RECORD_TYPE;
BEGIN
OPEN myCursor FOR
SELECT 1, 'Scott' FROM DUAL
UNION
SELECT 2, 'Tiger' FROM DUAL;
IF (myCursor IS NOT NULL) THEN
LOOP
FETCH myCursor INTO resultSet;
EXIT WHEN myCursor%NOTFOUND;
PIPE ROW (MY_RECORD_OBJ(
resultSet.record_id,
resultSet.firstname
));
END LOOP;
CLOSE myCursor;
END IF;
END GET_CURSOR_PIPELINE;
the only difference between my production code and the sample above is that I need to fetch about 20 fields from a real table, and not just 2 fields from DUAL.
I'd like to avoid the boiler code where I have to list esplicitally all the involved fields. The function above works fine, but I have to define ALL the involved fields 3 times. The fist time when I define the return type.
CREATE OR REPLACE TYPE MY_RECORD_OBJ AS OBJECT (
RECORD_ID NUMBER,
FIRSTNAME VARCHAR2(50)
);
/
CREATE OR REPLACE TYPE MY_RECORD_TYPE AS TABLE OF MY_RECORD_OBJ;
/
the second time when I define the RECORD type (declare section of the function), and the third time when I push the object in the pipeline (PIPE ROW).
Here it is the question: is there a way to avoid this and writing simply something like this?
PIPE ROW (MY_RECORD_OBJ(resultSet))
Moreover, if the answer was "yes, it is possible", how would the code look when applying to a real table? Should I put a %rowtype label somewhere?
How about this:
CREATE OR REPLACE FUNCTION GET_CURSOR_PIPELINE(placeholder IN NUMBER)
RETURN MY_RECORD_TYPE PIPELINED IS
myCursor SYS_REFCURSOR;
resultSet MY_RECORD_OBJ;
BEGIN
OPEN myCursor FOR
SELECT MY_RECORD_OBJ(1, 'Scott') FROM DUAL
UNION ALL
SELECT MY_RECORD_OBJ(2, 'Tiger') FROM DUAL;
LOOP
FETCH myCursor INTO resultSet;
EXIT WHEN myCursor%NOTFOUND;
PIPE ROW (resultSet);
END LOOP;
CLOSE myCursor;
END GET_CURSOR_PIPELINE;
You can also shrink it further with a cursor FOR LOOP:
CREATE OR REPLACE FUNCTION GET_CURSOR_PIPELINE(placeholder IN NUMBER)
RETURN MY_RECORD_TYPE PIPELINED IS
BEGIN
FOR myCursor IN
(
SELECT MY_RECORD_OBJ(1, 'Scott') my_record FROM DUAL
UNION ALL
SELECT MY_RECORD_OBJ(2, 'Tiger') my_record FROM DUAL
) LOOP
PIPE ROW(myCursor.my_record);
END LOOP;
END GET_CURSOR_PIPELINE;

Resources