Unable to create table function in oracle, type mismatch found between FETCH cursor and INTO variable - oracle

I am trying to create a table function to use in tableau's custom SQL, but I am getting an error, type mismatch found between FETCH cursor and INTO variable. Below is the code I am trying, I have created a type object and table of that type object. Function my_fct should return the table with a select statement output.
CREATE
OR replace type DATA_OBJ AS OBJECT (
id varchar2(10)
);
CREATE
OR replace type
DATA_OBJ_TAB AS TABLE OF DATA_OBJ;
CREATE OR REPLACE FUNCTION my_fct()
RETURN DATA_OBJ_TAB PIPELINED
AS
TYPE CurTyp IS REF CURSOR RETURN DATA_OBJ_TAB%ROWTYPE;
rc CurTyp;
CURSOR data IS SELECT ID from alumni_data;
BEGIN
FOR rc IN data LOOP
PIPE ROW (rc);
END LOOP;
END;

This can be implemented with a packaged PTF without using the SQL data types at all.
Something like this:
create table alumni_data (id, memo) as
select rownum id, 'memo '||rownum from dual connect by level<=3
/
create or replace package pack as
type arrT is table of alumni_data%rowtype;
function get (c varchar2) return arrT pipelined;
end;
/
create or replace package body pack as
function get (c varchar2) return arrT pipelined is
arr arrT;
begin
select * bulk collect into arr
from alumni_data
where memo like c||'%';
for i in 1..arr.count loop
pipe row (arr(i));
end loop;
return;
end;
end;
/
Result:
select * from pack.get ('mem');
ID MEMO
---------- ---------------------------------------------
1 memo 1
2 memo 2
3 memo 3

Have a look at the following example:
SQL> create or replace type data_obj as object
2 (id varchar2(10));
3 /
Type created.
SQL> create or replace type
2 data_obj_tab as table of data_obj;
3 /
Type created.
SQL> create or replace function my_fct
2 return data_obj_tab pipelined
3 as
4 l_vc data_obj := data_obj(null);
5 begin
6 for cur_r in (select id from alumni_data) loop
7 l_vc.id := cur_r.id;
8 pipe row (l_vc);
9 end loop;
10 return;
11 end;
12 /
Function created.
SQL> select * from table(my_fct);
ID
----------
CLARK
KING
MILLER
SQL>

Related

how to look up another field in Oracle Function

Table 1
ID
----------
1
2
3
4
5
Table 2
ID Desc
------------------------------
A1 Apple
A2 Pear
A3 Orange
I am trying to create a Function in Oracle, so that it add the prefix 'A' in Table 1, and after that I want to look up in Table 2 to get the DESC returned. It has to be a function.
Thank you!!!
You may use the following for creation of such a function :
Create or Replace Function Get_Fruit( i_id table2.description%type )
Return table2.description%type Is
o_desc table2.description%type;
Begin
for c in ( select description from table2 where id = 'A'||to_char(i_id) )
loop
o_desc := c.description;
end loop;
return o_desc;
End;
where
no need to include exception handling, because of using cursor
instead of select into clause.
using table_name.col_name%type for declaration of data types for
arguments or variables makes the related data type of the columns
dynamic. i.e. those would be able to depend on the data type of the
related columns.
the reserved keywords such as desc can not be used as column names
of tables, unless they're expressed in double quotes ("desc")
To call that function, the following might be preferred :
SQL> set serveroutput on
SQL> declare
2 i_id pls_integer := 1;
3 o_fruit varchar2(55);
4 begin
5 o_fruit := get_fruit( i_id );
6 dbms_output.put_line( o_fruit );
7 end;
8 /
Apple
PL/SQL procedure successfully completed
I am not sure with your question- Are you trying to achieve something like this:-
CREATE OR REPLACE FUNCTION Replace_Value
(
input_ID IN VARCHAR2
) RETURN VARCHAR2
AS
v_ID varchar(2);
BEGIN
begin
SELECT distinct a.ID into v_id from Table 2 a where a.ID in (select 'A'||b.id from table1 b where b.id=input_ID);
exception
when others then
dbms_output.put_line(sqlcode);
end;
RETURN v_id;
END Replace_Value;
Are you trying for something like this?
CREATE OR replace FUNCTION replace_value (table_name IN VARCHAR2,
input_id IN INTEGER)
RETURN VARCHAR2
AS
v_desc VARCHAR(20);
BEGIN
SELECT descr
INTO v_desc
FROM table2
WHERE id = 'A' || input_id
AND ROWNUM = 1; -- only needed if there are multiple rows for each id.
RETURN v_desc;
END replace_value;
You may also add an exception handling for NO_DATA_FOUND or INVALID_NUMBER

Oracle PL/SQL - Show results of declared table

I am using Toad. I have a declaration of a table in a package as follows:
TYPE MyRecordType IS RECORD
(ID MyTable.ID%TYPE
,FIELD1 MyTable.FIELD1%TYPE
,FIELD2 MyTable.FIELD2%TYPE
,FIELD3 MyTable.FIELD3%TYPE
,ANOTHERFIELD VARCHAR2(80)
);
TYPE MyTableType IS TABLE OF MyRecordType INDEX BY BINARY_INTEGER;
There is a procedure (lets say MyProcedure), that is using an object of this table type as input/output. I want to run the procedure and see the results (how the table is filled). So I am thinking I will select the results from the table:
declare
IO_table MyPackage.MyTableType;
begin
MyPackage.MyProcedure (IO_table
,parameter1
,parameter2
,parameter3);
select * from IO_table;
end;
I get the message:
Table or view does not exist (for IO_table). If I remove the select line, the procedure runs successfully, but I cannot see its results. How can I see the contents of IO_table after I call the procedure?
You cannot see the results for a PL/SQL table by using Select * from IO_table
You will need to loop through the collection in the annonymous block.
do something like, given in pseudo code below...
declare
IO_table MyPackage.MyTableType;
l_index BINARY_INTEGER;
begin
MyPackage.MyProcedure (IO_table
,parameter1
,parameter2
,parameter3);
l_index := IO_table.first;
While l_index is not null
loop
dbms_output.put_line (IO_table(l_index).id);
.
.
.
.
l_index :=IO_table.next(l_index_id);
end loop;
end;
You have to do it like this:
select * from TABLE(IO_table);
and, of course you missed the INTO or BULK COLLECT INTO clause
1) You can not use associated arrays in SELECT statement, Just nested tables or varrays declared globally.
2) You should use TABLE() expression in SELECT statement
3) You can't simply use SELECT in PL/SQL code - cursor FOR LOOP or REF CURSOR or BULK COLLECT INTO or INTO must be used.
4) The last but not least - please study the manual:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28371/adobjcol.htm#ADOBJ00204
Just an example:
SQL> create type t_obj as object( id int, name varchar2(10));
2 /
SQL> create type t_obj_tab as table of t_obj;
2 /
SQL> var rc refcursor
SQL> declare
2 t_var t_obj_tab := t_obj_tab();
3 begin
4 t_var.extend(2);
5 t_var(1) := t_obj(1,'A');
6 t_var(2) := t_obj(2,'B');
7 open :rc for select * from table(t_var);
8 end;
9 /
SQL> print rc
ID NAME
---------- ----------
1 A
2 B

How to retrieve multiple rows from a stored function with oracle

I'm trying to create a stored function in oracle that returns multiple rows.
My question is very similar to this one except that I want to fetch a select * query
In a nutshell, I want to create a function which returns the result of this query
select * from t_email_queue
What I've tried is this :
create or replace
PACKAGE email_queue AS
type t_email_queue_type is table of T_EMAIL_QUEUE%ROWTYPE;
FUNCTION lock_and_get return t_email_queue_type;
END email_queue;
create or replace
PACKAGE BODY email_queue AS
FUNCTION lock_and_get RETURN t_email_queue_type AS
queue_obj t_email_queue_type;
cursor c (lockid in varchar2) is select * from t_email_queue where lockedby = lockid;
lockid varchar2(100) := 'alf';
BEGIN
OPEN c(lockid);
FETCH c bulk collect INTO queue_obj;
return queue_obj;
END lock_and_get;
END email_queue;
The package compiles just fine but when I try to call it with this query
select * from table(email_queue.lock_and_get);
Oracle throws the following error
ORA-00902: invalid datatype
00902. 00000 - "invalid datatype"
*Cause:
*Action:
Error at Line: 1 Column: 20
I think Oracle want me to create my return type at the schema level but when I try to do
create type t_email_queue_type is table of T_EMAIL_QUEUE%ROWTYPE;
Oracle complains
Type IMAIL.T_EMAIL_QUEUE_TYPE#imail dev
Error(1): PL/SQL: Compilation unit analysis terminated
Error(2,37): PLS-00329: schema-level type has illegal reference to IMAIL.T_EMAIL_QUEUE
Can someone point me to the right direction? What am I missing here?
Thanks for reading!
with SQL types you cant do %ROWTYPE, you'd have to type each column to match the table*.
*sys.anydataset aside. but going down that route is a lot more complex coding.
e.g. if your table was
create table foo (id number, cola varchar2(1));
then
create type email_queue_type is object (id number, cola varchar2(1));
/
create type t_email_queue_type as table of email_queue_type;
/
and use that table email_queue_type_tab as the output from your function.
but i'd recommend a pipelined function, as your current code isn't scalable.
eg:
SQL> create table foo (id number, cola varchar2(1));
Table created.
SQL>
SQL> create type email_queue_type is object (id number, cola varchar2(1));
2 /
Type created.
SQL> create type t_email_queue_type as table of email_queue_type;
2 /
Type created.
SQL> insert into foo select rownum, 'a' from dual connect by level <= 10;
10 rows created.
SQL>
SQL> create or replace PACKAGE email_queue AS
2
3
4 FUNCTION lock_and_get return t_email_queue_type pipelined;
5
6 END email_queue;
7 /
Package created.
SQL> create or replace PACKAGE BODY email_queue AS
2
3 FUNCTION lock_and_get RETURN t_email_queue_type pipelined AS
4 queue_obj t_email_queue_type;
5
6 BEGIN
7
8 for r_row in (select * from foo)
9 loop
10 pipe row(email_queue_type(r_row.id, r_row.cola));
11 end loop;
12
13 END lock_and_get;
14
15 END email_queue;
16 /
Package body created.
SQL> select * from table(email_queue.lock_and_get());
ID C
---------- -
1 a
2 a
3 a
4 a
5 a
6 a
7 a
8 a
9 a
10 a
10 rows selected.
SQL>
If you aren't particularly keen on having the SQL type, you could do this with a sys_refcursor instead:
create or replace package email_queue as
function lock_and_get return sys_refcursor;
end email_queue;
/
create or replace package body email_queue as
function lock_and_get return sys_refcursor AS
c sys_refcursor;
lockid varchar2(100) := 'alf';
begin
open c for
select * from t_email_queue
where lockedby = lockid;
return c;
end lock_and_get;
end email_queue;
/
From SQL*Plus you could call it something like:
var cur refcursor;
exec :cur := email_queue.lock_and_get;
print cur
and as exec is shorthand for a simple anonymous block you can call if from other PL/SQL objects too. What you can't do, though, is:
select * from table(email_queue.lock_and_get);
I'm not familiar with calling functions from PHP, but from Java you can use this directly as the return from a callable statement, so you don't need the select * from table() construct at all. I have no idea if you can execute an anonymous block in a PHP call, something like begin $cur = email_queue.lock_and_get; end;, and have $cur as your result set that you can then iterate through?
I realise this isn't a complete answer as the PHP side is way too vague, but might give you some ideas.
If you are using PHP and you want to access a oracle stored function. You can make use of something like this
//Your connection details
$conn = oci_connect($username, $password, '(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 127.0.0.1)(PORT = 1521))(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = XE)))' );
/* Your query string; you can use oci_bind_by_name to bind parameters or just pass the variable in it*/
$query = "begin :cur := functionName('".$param1."','".$param2."','".$param3."'); end;";
$stid = oci_parse($conn, $query);
$OUTPUT_CUR = oci_new_cursor($conn);
oci_bind_by_name($stid, ':cur', $OUTPUT_CUR, -1, OCI_B_CURSOR);
oci_execute($stid);
oci_execute($OUTPUT_CUR);
oci_fetch_all($OUTPUT_CUR, $res);
// To get your result
var_dump($res);
I hope this helps.

Return collection from packaged function for use in select

I'm currently using this block of code to return a collection of rows from my function.
--Source: http://www.adp-gmbh.ch/ora/plsql/coll/return_table.html
create or replace type t_col as object (
i number,
n varchar2(30)
);
/
create or replace type t_nested_table as table of t_col;
/
create or replace function return_table return t_nested_table as
v_ret t_nested_table;
begin
v_ret := t_nested_table();
v_ret.extend;
v_ret(v_ret.count) := t_col(1, 'one');
v_ret.extend;
v_ret(v_ret.count) := t_col(2, 'two');
v_ret.extend;
v_ret(v_ret.count) := t_col(3, 'three');
return v_ret;
end return_table;
/
Which I call by issuing SQL
select * from table(return_table);
Object types can not be defined in a package, I tried using the record type which worked (in PL/SQL) but I couldn't select from it in the same way as I can here.
How do I achieve this result using a function inside a package?
You could either use SQL objects inside your package or use pipelined functions (tested with 10gr2). Using SQL objects is straightforward, your actual function could be used as is inside a package.
Here's how you could use a pipelined function with a RECORD type:
SQL> CREATE OR REPLACE PACKAGE my_pkg IS
2 TYPE t_col IS RECORD(
3 i NUMBER,
4 n VARCHAR2(30));
5 TYPE t_nested_table IS TABLE OF t_col;
6 FUNCTION return_table RETURN t_nested_table PIPELINED;
7 END my_pkg;
8 /
Package created
SQL> CREATE OR REPLACE PACKAGE BODY my_pkg IS
2 FUNCTION return_table RETURN t_nested_table PIPELINED IS
3 l_row t_col;
4 BEGIN
5 l_row.i := 1;
6 l_row.n := 'one';
7 PIPE ROW(l_row);
8 l_row.i := 2;
9 l_row.n := 'two';
10 PIPE ROW(l_row);
11 RETURN;
12 END;
13 END my_pkg;
14 /
Package body created
SQL> select * from table(my_pkg.return_table);
I N
---------- ------------------------------
1 one
2 two
What happens behind the scene is that Oracle understands that since you want to use your function in a query (because of the PIPELINED keyword), you will need SQL objects, so those objects are created behind the scene for you:
SQL> select object_name
2 from user_objects o
3 where o.created > sysdate - 1
4 and object_type = 'TYPE';
OBJECT_NAME
--------------------------------------------------------------------------------
SYS_PLSQL_798806_24_1
SYS_PLSQL_798806_DUMMY_1
SYS_PLSQL_798806_9_1
SQL> select text from user_source where name='SYS_PLSQL_798806_9_1';
TEXT
--------------------------------------------------------------------------------
type SYS_PLSQL_798806_9_1 as object (I NUMBER,
N VARCHAR2(30));
create or replace type t_col as object (
i number,
n varchar2(30)
);
/
create or replace package foo as
type t_nested_table is table of t_col;
function return_table return t_nested_table pipelined;
end;
/
show errors
create or replace package body foo as
data t_nested_table := t_nested_table(t_col(1, 'one'),
t_col(2, 'two'),
t_col(3, 'three'));
function return_table return t_nested_table pipelined as
begin
for i in data.first .. data.last loop
pipe row(data(i));
end loop;
return;
end;
end;
/
show errors
column n format a10
select * from table(foo.return_table);
I N
---------- ----------
1 one
2 two
3 three

Oracle: Select From Record Datatype

I have a function that returns a record datatype (2 fields: ID and Name). How can I get at the data from a select statement?
Specifically, I am trying using an OracleCommand object attempting to get the object into my C# code. I initially tried ...
CALL FUNCTION_NAME() INTO :loRetVal
... but I get a data type error for whatever type I use. I have also tried ...
SELECT * FROM FUNCTION_NAME()
... and ...
SELECT * FROM TABLE ( FUNCTION_NAME() )
... to no avail. I guess I am looking for ...
SELECT * FROM RECORD ( FUNCTION_NAME() )
... which, of course, doesn't exist.
The only solution I have been able to come up with is to wrap this function call in another function call in which the outer function returns a TABLE of records containing this sole record. This, however, seems cumbersome and I am looking for a simpler method. Any help would be appreciated.
EDIT: Sorry, I have also tried SELECT FUNCTION_NAME() FROM DUAL.
A record datatype is a PL/SQL datatype. SQL doesn't know about it. That's probably why you are getting an error. See this example:
SQL> create package mypkg
2 as
3 type myrec is record
4 ( id int
5 , name varchar2(10)
6 );
7 function f return myrec;
8 end mypkg;
9 /
Package created.
SQL> create package body mypkg
2 as
3 function f return myrec
4 is
5 r myrec;
6 begin
7 r.id := 1;
8 r.name := 'test';
9 return r;
10 end f;
11 end mypkg;
12 /
Package body created.
SQL> desc mypkg
FUNCTION F RETURNS RECORD
ID NUMBER(38) OUT
NAME VARCHAR2(10) OUT
SQL> select mypkg.f from dual
2 /
select mypkg.f from dual
*
ERROR at line 1:
ORA-00902: invalid datatype
The error in SQL I was referring to.
You can call it from PL/SQL though:
SQL> declare
2 r mypkg.myrec;
3 begin
4 r := mypkg.f;
5 dbms_output.put_line(r.id);
6 dbms_output.put_line(r.name);
7 end;
8 /
1
test
PL/SQL procedure successfully completed.
If you want to use the function in SQL, then you can create a SQL objecttype. Note that calling your function directly from C# looks way more preferable than insisting on using SQL to do this. But just for the record:
SQL> drop package mypkg
2 /
Package dropped.
SQL> create type myobj is object
2 ( id int
3 , name varchar2(10)
4 );
5 /
Type created.
SQL> create package mypkg
2 as
3 function f return myobj;
4 end mypkg;
5 /
Package created.
SQL> create package body mypkg
2 as
3 function f return myobj
4 is
5 begin
6 return myobj(1,'test');
7 end f;
8 end mypkg;
9 /
Package body created.
SQL> select mypkg.f from dual
2 /
F(ID, NAME)
--------------------------------------------------------------
MYOBJ(1, 'test')
1 row selected.
Regards,
Rob.
I think this what you are looking for; to get the values out in a select statement:
select result.id as id, result.name
from ( select function() as result from dual);
Because your function returns a record an not a native type you can't use the standard methods. The if you want to get the actual record as an object into C# then you have do some reading on user defined types in the ODP .net documentation.
You could also wrap the function in another function that returns a ref cursor and that is used in C# in a more standard fashion.
Can you
CREATE TYPE <object name> AS TABLE OF <record type>
and use that directly in a SQL statement? I ask because I have a stored proc that I can not edit. The stored proc has an output variable that is record type that I have to reference in a SQL statement. I have already created a function to call the proc, but if I don't have to convert the record to type object that would be nice.
I would later call it like:
SELECT *
FROM TABLE( CAST( <function name>() as <object name>));
The formatting of my comment for Rob van Wijk is bad. To continue his thought.
-- create a collection type
CREATE TYPE myobj_tab AS TABLE OF myobj;
-- have the function return a collection type
CREATE OR REPLACE function f return myobj_tab
IS
objtab myobj_tab;
BEGIN
objtab := myobj_tab(myobj(1,'test'));
return objtab;
end f;
-- CAST it as a table and straight up select from it.
SELECT id, name FROM TABLE(CAST(f() AS myobj_tab));
I think you are looking for PIPELINED functionality:
CREATE TABLE test_table(tt_id INTEGER,tt_text VARCHAR2(40));
CREATE PACKAGE test_pkg IS
TYPE tp_rec IS RECORD(tt_id INTEGER,tt_text VARCHAR2(40));
TYPE tp_recs IS TABLE OF tp_rec;
FUNCTION test_func RETURN tp_recs PIPELINED;
FUNCTION test_func1 RETURN tp_recs PIPELINED;
FUNCTION test_func2(ivar INTEGER) RETURN tp_recs PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY test_pkg IS
FUNCTION test_func RETURN tp_recs PIPELINED
AS
currec tp_rec;
BEGIN
currec.tt_id := 1;
currec.tt_text := 'test1';
PIPE ROW(currec);
END;
FUNCTION test_func1 RETURN tp_recs PIPELINED
AS
currec tp_rec;
CURSOR t_cursor IS
SELECT * FROM test_table;
BEGIN
OPEN t_cursor;
LOOP
FETCH t_cursor INTO currec;
EXIT WHEN t_cursor%NOTFOUND;
PIPE ROW(currec);
END LOOP;
CLOSE t_cursor;
END;
FUNCTION test_func2(ivar INTEGER) RETURN tp_recs PIPELINED
AS
currec tp_rec;
BEGIN
SELECT * INTO currec FROM test_table WHERE tt_id = ivar;
PIPE ROW(currec);
END;
END;
/
BEGIN
INSERT INTO test_table VALUES(1,'test1');
INSERT INTO test_table VALUES(2,'test2');
INSERT INTO test_table VALUES(3,'test3');
COMMIT;
END;
/
SELECT * FROM TABLE(test_pkg.test_func());
SELECT * FROM TABLE(test_pkg.test_func1());
SELECT * FROM TABLE(test_pkg.test_func2(2));
The above code is tested, and should give you a good start on this. Just look up the PIPELINED keyword in Oracle for more information (assuming you are working with Oracle...)
Why do you need to use SQL at all? You might be able to just use a System.Data.CommandType.StoredProcedure to call the function.
call-an-oracle-function-from-c#
Call Oracle Function in package with C#

Resources