Oracle PL/SQL: Function Cursor - sql

I get Error:
PLS-00231: function 'GET_NUM' may not be used in SQL
when the following code is executed;
CREATE OR REPLACE PACKAGE BODY TESTJNSABC IS
-- FUNCTION IMPLEMENTATIONS
FUNCTION get_num(num IN NUMBER)
RETURN SYS_REFCURSOR AS
my_cursor SYS_REFCURSOR;
BEGIN
--
OPEN my_cursor FOR
WITH ntable AS (
SELECT 1 ID, 111 AGT, 'ABC' DESCRIP FROM DUAL
UNION ALL
SELECT 2 ID, 222 AGT, 'ABC' DESCRIP FROM DUAL
UNION ALL
SELECT 1 ID, 333 AGT, 'ABC' DESCRIP FROM DUAL
)
SELECT AGT FROM ntable WHERE ID = num;
RETURN my_cursor;
END;
-- PROCEDURE IMPLEMENTATIONS
PROCEDURE testingabc AS
BEGIN
WITH xtable AS (
SELECT 111 AGT, 'A' DESCRIP FROM DUAL
UNION ALL
SELECT 222 AGT, 'B' DESCRIP FROM DUAL
UNION ALL
SELECT 333 AGT, 'C' DESCRIP FROM DUAL
)
SELECT DESCRIP FROM xtable WHERE COD_AGT IN get_num(1);
END testingabc;
END TESTJNSABC;
Even if I call the function as TESTJNSABC.get_num(1) I still get the same error.
--UPDATE. So in real life scenario I would like to call a Function from a WHERE CLAUSE; the function should return a set of NUMBER values (that's why I use the IN clause).

So is it possible then to create a variable on the Procedure and
assign the Function values to the variable? Let's say
It sould not be the question whether it is possible or not rather it should had been if this is the right way. Ofcourse you can do it in the way you are doing but as experts suggested, that's not the right and efficient way. See how you can do it. PS: Not tested.
CREATE OR REPLACE PACKAGE BODY TESTJNSABC IS
-- FUNCTION IMPLEMENTATIONS
FUNCTION get_num(num IN NUMBER)
RETURN SYS_REFCURSOR AS
my_cursor SYS_REFCURSOR;
BEGIN
--
OPEN my_cursor FOR
WITH ntable AS (
SELECT 1 ID, 111 AGT, 'ABC' DESCRIP FROM DUAL
UNION ALL
SELECT 2 ID, 222 AGT, 'ABC' DESCRIP FROM DUAL
UNION ALL
SELECT 1 ID, 333 AGT, 'ABC' DESCRIP FROM DUAL
)
SELECT AGT FROM ntable WHERE ID = num;
RETURN my_cursor;
END;
-- PROCEDURE IMPLEMENTATIONS
PROCEDURE testingabc AS
--Creating a collection to hold return of the function
type y is table of varchar2(1000) index by pls_integer;
var_z y;
var_1 varchar2(100);
BEGIN
Select get_num(1)
bulk collect into var_z
from dual;
For i in 1..var_z.count
loop
WITH xtable AS (
SELECT 111 AGT, 'A' DESCRIP FROM DUAL
UNION ALL
SELECT 222 AGT, 'B' DESCRIP FROM DUAL
UNION ALL
SELECT 333 AGT, 'C' DESCRIP FROM DUAL
)
SELECT DESCRIP
into var_1
FROM xtable
WHERE AGT = var_z(i) ; ---Check each record
dbms_output.put_line(var_1);
end loop;
END testingabc;
END TESTJNSABC;

in () requires either a subquery or a comma-separated list of values, so no, you can't substitute a function that returns a collection.
Assuming the function is in scope for SQL queries (it's either a standalone function or declared in a package specification), you could use it in a table() construction (this needs a table function, i.e. it needs to return a collection, not a cursor):
where somecol in (select column_value from table(get_num(1)) )
(or the equivalent inner join etc.)
Demo at livesql.oracle.com/apex/livesql/file/content_EF2M0F1LV9LTP6PEII3BDFKAI.html
Edit: I've just noticed the example in the question tried to use a ref cursor. Note that the table() operator works on collections, not ref cursors. Therefore the function has to return a collection type (nested table or varray).

As per #William Robertson answer, I tried to implement the solution but getting issue :
[Error] ORA-22905 (18: 26): PL/SQL: ORA-22905: cannot access rows from
a non-nested table item
-- FUNCTION IMPLEMENTATIONS
create or replace FUNCTION get_num(num IN NUMBER)
RETURN SYS_REFCURSOR AS
my_cursor SYS_REFCURSOR;
BEGIN
--
OPEN my_cursor FOR
SELECT AGT
FROM ntable
WHERE ID = num;
RETURN my_cursor;
END;
-- PROCEDURE IMPLEMENTATIONS
CREATE OR REPLACE PROCEDURE testingabc
AS
type var is table of xtable.DESCRIP%type;
v_var var;
BEGIN
SELECT DESCRIP
bulk collect into var
FROM xtable
WHERE AGT IN (SELECT COLUMN_VALUE
FROM TABLE (get_num (1)));
END testingabc;

Related

With statement select and function into cursor

it is possible to use the with structure with a function inside a cursor, I don't know if I am declaring it inappropriately, I am getting the following error using with function inside procedure pl sql statement is not supported
CURSOR c_detail IS
WITH
FUNCTION CALC_NUMBER(FOB_ITEM NUMBER DEFAULT 0,
FOB_TOTAL NUMBER DEFAULT 0,
WEIGHT NUMBER DEFAULT 0) RETURN NUMBER
IS
PESO_BRUTO_ITEM NUMBER :=0;
BEGIN
IF( (FOB_ITEM > 0) AND (FOB_TOTAL > 0 AND WEIGHT> 0 )) THEN
PESO_BRUTO_ITEM := (FOB_ITEM * WEIGHT) / FOB_TOTAL;
END IF;
RETURN PESO_BRUTO_ITEM;
END CALC_NUMBER;
test_data AS
(
SELECT 36.25 AS FOB_I, 12536.36 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 15.36 AS FOB_I, 3678.65 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 878.77 AS FOB_I, 89653.13 AS FOB_TOTAL, 362 AS W FROM dual
)
SELECT TD.FOB_I,
TD.FOB_TOTAL,
CALC_NUMBER(TD.FOB_I, TD.FOB_TOTAL, TD.W) WEIGHT
FROM test_data TD
[TL;DR] You can declare a function in a sub-query factoring clause but (as Justin Cave points out) it only works when you are executing the query as dynamic SQL and support for using functions in static SQL within a cursor may be available in future database versions.
This sub-query factoring clause with a function works outside of a cursor:
WITH
FUNCTION with_function(
p_id IN NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN 42 + p_id;
END;
test_data ( id ) AS (
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3
)
SELECT id,
with_function( id )
FROM test_data;
Trying to put it into a cursor (in Oracle 18c):
DECLARE
p_id NUMBER;
p_fn NUMBER;
CURSOR c_detail IS
WITH
FUNCTION with_function(
p_id IN NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN 42 + p_id;
END;
test_data ( id ) AS (
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3
)
SELECT id,
with_function( id )
FROM test_data;
BEGIN
OPEN c_detail;
LOOP
FETCH c_detail INTO p_id, p_fn;
EXIT WHEN c_detail%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( p_id || ', ' || p_fn );
END LOOP;
CLOSE c_detail;
END;
/
Outputs the error:
ORA-06550: line 7, column 14:
PL/SQL: ORA-00905: missing keyword
ORA-06550: line 6, column 3:
PL/SQL: SQL Statement ignored
ORA-06550: line 13, column 5:
PLS-00103: Encountered the symbol "END" when expecting one of the following:
begin function pragma procedure subtype type <an identifier>
<a double-quoted delimited-identifier> current cursor delete
exists prior
Removing the function then the cursor works:
DECLARE
p_id NUMBER;
CURSOR c_detail IS
WITH
test_data ( id ) AS (
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3
)
SELECT id
FROM test_data;
BEGIN
OPEN c_detail;
LOOP
FETCH c_detail INTO p_id;
EXIT WHEN c_detail%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( p_id );
END LOOP;
CLOSE c_detail;
END;
/
So it is not an issue with using a sub-query factoring clause in a cursor.
Executing the cursor as dynamic SQL query:
DECLARE
p_id NUMBER;
p_fn NUMBER;
c_detail SYS_REFCURSOR;
p_sql VARCHAR2(4000) := 'WITH
FUNCTION with_function(
p_id IN NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN 42 + p_id;
END;
test_data ( id ) AS (
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3
)
SELECT id,
with_function( id )
FROM test_data';
BEGIN
OPEN c_detail FOR p_sql;
LOOP
FETCH c_detail INTO p_id, p_fn;
EXIT WHEN c_detail%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( p_id || ', ' || p_fn );
END LOOP;
CLOSE c_detail;
END;
/
Works and outputs:
1, 43
2, 44
3, 45
So it appears that, yes, you can declare a function in a sub-query factoring clause but it only works when you are executing the query as dynamic SQL.
db<>fiddle here
The error message clearly says you can't do what you are after, but then you will not have to if your cursor looks like following
CURSOR c_detail IS
WITH test_data AS(
SELECT 36.25 AS FOB_I, 12536.36 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 15.36 AS FOB_I, 3678.65 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 878.77 AS FOB_I, 89653.13 AS FOB_TOTAL, 362 AS W FROM dual
)
SELECT TD.FOB_I,
TD.FOB_TOTAL,
CASE WHEN TD.FOB_I > 0 AND TD.FOB_TOTAL > 0 AND TD.W > 0
THEN (TD.FOB_I * TD.W) / TD.FOB_TOTAL
ELSE 0
END WEIGHT
FROM test_data TD
;

Return Multiple Values from Oracle Function

I want to create a function that returns multiple rows into a table that is of object type.
I have created an object and a nested table object and now when I run the function there is an error which says
PL/SQL: SQL Statement ignored
PL/SQL: ORA-00947: not enough values
-- Object type creation
create or replace type test_object_sn as object
(
column_1 varchar2(30),
column_2 varchar2(30),
column_3 number
);
-- Table of object
create or replace type test_otable_sn as table of test_object_sn;
-- function (where I get an error)
create or replace function load_test_object_sn
return test_otable_sn
as
details test_otable_sn;
begin
with ad as (select 'a', 'b', 4 from dual
union all
select 'r', '5', 3 from dual
union all
select 'g', 's', 3 from dual)
select * into details from ad;
return details;
end;
I want to have the test_otable_sn table object loaded with the data and then query it using the table() function via my load_test_object_sn function
e.g. select * from table(load_test_object_sn);
Update:
do you know how to modify this for scenario whereby I have an sql
statement contained in a string variable to execute?
Yes, we can use a cursor reference (SYS_REFCURSOR) and OPEN/FETCH/CLOSE instead of a CURSOR and CURSOR FOR LOOP.
The syntax is OPEN <cursor-reference> FOR <string-containing-sql-statement> . See below.
CREATE OR REPLACE FUNCTION load_test_object_sn
RETURN test_otable_sn
AS
details test_otable_sn := test_otable_sn();
-- Variable stores SQL statement for cursor
l_sql CLOB :=
q'[with ad as (
select 'a' column_1, 'b' column_2, 4 column_3 from dual union all
select 'r', '5', 3 from dual union all
select 'g', 's', 3 from dual
)
select *
from ad]';
-- Cursor reference allows us to open cursor for SQL statement above
rc SYS_REFCURSOR;
-- Define object instance to store each row fetched from the cursor
l_obj test_object_sn := test_object_sn(NULL, NULL, NULL);
i PLS_INTEGER := 1;
BEGIN
-- Explicitly open, fetch from, and close the cursor
OPEN rc FOR l_sql;
LOOP
FETCH rc INTO l_obj.column_1, l_obj.column_2, l_obj.column_3;
EXIT WHEN rc%NOTFOUND;
details.extend();
details(i) := test_object_sn(l_obj.column_1, l_obj.column_2, l_obj.column_3);
i := i + 1;
END LOOP;
CLOSE rc;
RETURN details;
END;
Original answer:
Unfortunately, one can't use SELECT * INTO with a collection in this manner, so here's an alternative way to populate the table:
create or replace function load_test_object_sn
return test_otable_sn
as
details test_otable_sn := test_otable_sn();
cursor c_ad is
with ad as (select 'a' column_1, 'b' column_2, 4 column_3 from dual
union all
select 'r', '5', 3 from dual
union all
select 'g', 's', 3 from dual)
select * from ad;
i pls_integer := 1;
begin
for ad_rec in c_ad loop
details.extend();
details(i) := test_object_sn(ad_rec.column_1, ad_rec.column_2, ad_rec.column_3);
i := i + 1;
end loop;
return details;
end;
/
Output:
SQL> SELECT * FROM TABLE(load_test_object_sn);
COLUMN_1 COLUMN_2 COLUMN_3
---------- ---------- ----------
a b 4
r 5 3
g s 3

Use an array in a SELECT statement?

I want to use this array in 'select from..where..in(YYY)' statement.
I don't want to iterate through array values, I want to use it whole in my select statement.
Unfortunately, I found only how to iterate it:
1 declare
2 type array is table of varchar2(30) index by binary_integer;
3 a array;
4 procedure p( array_in array )
5 is
6 begin
7 for i in 1..array_in.count loop
8 dbms_output.put_line( array_in(i) );
9 end loop;
10 end;
11 begin
12 a(1) := 'Apple';
13 a(2) := 'Banana';
14 a(3) := 'Pear';
15 p( a );
16 end;
17 /
You can do this by creating a function returning your array. Then you can use it into a select:
Create external types and function
create or replace type t_array is table of varchar2(30);
create or replace function list_of_fruits
return t_array
is
l_ t_array:=t_array();
begin
l_.extend(); l_(l_.COUNT) := 'Apple';
l_.extend(); l_(l_.COUNT) := 'Banana';
l_.extend(); l_(l_.COUNT) := 'Pear';
return l_;
end list_of_fruits;
/
And here is how to use it:
select * from (
select 'Peter' this_and_that from dual
union all select 'Joy' from dual
union all select 'God' from dual
union all select 'Pear' from dual
union all select 'Man' from dual
)
where this_and_that in (
select column_value from (table( list_of_fruits() ))
);
The trick here is to use the table() function to make a SQL usable list for your select; also difficult for me was to discover the name of that column_value... which is some built-in constant from Oracle: how do you guess that?
You can use oracle defined collection to achieve this as well. Please see below and example.
declare
a sys.odcivarchar2list;
begin
a := sys.odcivarchar2list('Apple','Banana','Pear');
for r in ( SELECT m.column_value m_value
FROM table(a) m )
loop
dbms_output.put_line (r.m_value);
end loop;
end;

Oracle Package return table

I am trying to return a table via a function in an Oracle package:
CREATE OR REPLACE PACKAGE test AS
TYPE rec IS RECORD(
col1 VARCHAR(10));
TYPE rec_table IS TABLE OF rec;
FUNCTION get_table(input VARCHAR2)
RETURN rec_table
PIPELINED;
END;
CREATE OR REPLACE PACKAGE BODY test AS
FUNCTION get_table(input VARCHAR2)
RETURN rec_table
PIPELINED IS
rec1 rec;
BEGIN
SELECT * INTO rec1
FROM
(
SELECT '1' from dual
UNION ALL
SELECT '2' from dual
);
PIPE ROW (rec1)
RETURN;
END get_table;
END;
but when I try to run
select * from table(test.get_table('blah'))
I get an error: exact fetch returns more then requested number of rows
I've read a bit about BULK COLLECT INTO, but I am not understanding the syntax...
The following piece of code:
SELECT '1' from dual
UNION ALL
SELECT '2' from dual
Returns two, not one record, and you are trying to put those two records in one rec variable. You should instead loop through the results of the UNION:
FOR v_rec IN (
SELECT *
FROM (
SELECT '1' from dual
UNION ALL
SELECT '2' from dual
)
)
LOOP
PIPE ROW (v_rec);
END LOOP;

Oracle stored procedure help

HI am new to stored procedure.
Am using a dynamic sql with
select * from table into var
var is a variable.the var contains more than one value ,when i try to run the proc with inputs i get an error:
ORA-01422: exact fetch returns more than requested number of rows
Is there a way such that the variable can hold more than one row using dynamic sql.
Use collection variables:
DECLARE
TYPE tt_int IS TABLE OF INTEGER;
var tt_int;
BEGIN
SELECT id
BULK COLLECT
INTO var
FROM table;
END;
You have to bulk collect the result set into a table of type if you're going to select multiple rows.
declare
type record_type is table of <table_name>;
var_records record_type;
begin
select *
bulk collect into var_records
from <table_name>;
end;
/
See also my answer to PL SQL how to select all columns
you can also return the results to a ref cursor
set serveroutput on
DECLARE
REFEXAMPLE SYS_REFCURSOR;
VAR NUMBER ;
col varchar2(50);
BEGIN
OPEN REFEXAMPLE FOR --Here you open the cursor and fill it
SELECT *
FROM (
SELECT 1 VAR, 'a' COL FROM DUAL
UNION ALL
SELECT 2 VAR, 'b' COL FROM DUAL
UNION ALL
SELECT 3 VAR, 'c' COL FROM DUAL
UNION ALL
SELECT 4 VAR, 'd' COL FROM DUAL
UNION ALL
SELECT 5 VAR, 'e' COL FROM DUAL
) EXAMPLETABLE ;
DBMS_OUTPUT.PUT_LINE('var ' || 'col');
DBMS_OUTPUT.PUT_LINE('---------');
LOOP
FETCH REFEXAMPLE INTO VAR, col; --now loop through
EXIT WHEN REFEXAMPLE%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(var || ' ' || col || ' ');
END LOOP;
CLOSE REFEXAMPLE;
end ;
/
var col
---------
1 a
2 b
3 c
4 D
5 e
You can use a table variable, which provides you with an in-memory table of your results. You declare a table variable similar to a standard variable:
declare #MyVar table (col1 col1type, col2 col2type, etc.)