Return table from function in Oracle - oracle

Following function works with SQL Server.
CREATE FUNCTION GET_EMP_PROB_START_ATT_CODE(#EVAL_TYPE VARCHAR(6))
RETURNS #EMP_PROB_START_ATT_CODE TABLE (
PROBATION_START_ATT_CODE varchar(6) NULL
)
AS
BEGIN
IF #EVAL_TYPE='New'
INSERT INTO #EMP_PROB_START_ATT_CODE (PROBATION_START_ATT_CODE)
SELECT NEW_EMP_PROB_START_ATT_CODE FROM HS_HR_PEA_DYNAMIC_ATTRIBUTES;
ELSE
INSERT INTO #EMP_PROB_START_ATT_CODE (PROBATION_START_ATT_CODE)
SELECT EXIS_EMP_PROB_START_ATT_CODE FROM HS_HR_PEA_DYNAMIC_ATTRIBUTES;
RETURN;
END;
I just want to convert this to Oracle equivalent query . Tried but not succeeded.
CREATE FUNCTION GET_EMP_PROB_START_ATT_CODE(EVAL_TYPE VARCHAR(6))
RETURN EMP_PROB_START_ATT_CODE IS TABLE
PROBATION_START_ATT_CODE VARCHAR(6) NULL;
AS
BEGIN
IF EVAL_TYPE='New'
INSERT INTO EMP_PROB_START_ATT_CODE (PROBATION_START_ATT_CODE)
SELECT NEW_EMP_PROB_START_ATT_CODE FROM HS_HR_PEA_DYNAMIC_ATTRIBUTES;
ELSE
INSERT INTO EMP_PROB_START_ATT_CODE (PROBATION_START_ATT_CODE)
SELECT EXIS_EMP_PROB_START_ATT_CODE FROM HS_HR_PEA_DYNAMIC_ATTRIBUTES;
RETURN;
END;
Warning: compiled but with compilation errors
Any help guys..

First of all, create a database-wide type:
create type string_tab6 is table of varchar2(6);
Then you would create your function like so:
create or replace function get_emp_prob_start_att_code(eval_type varchar2)
return string_tab6
is
v_array string_tab6;
begin
if eval_type = 'New' then
select new_emp_prob_start_att_code
bulk collect into v_array
from hs_hr_pea_dynamic_attributes;
else
select exis_emp_prob_start_att_code
bulk collect into v_array
from hs_hr_pea_dynamic_attributes;
end if;
return v_array;
end get_emp_prob_start_att_code;
/
However, I would question why you need such a function. What are you going to use it for?
In Oracle, I'd expect to see that logic embedded directly into whichever SQL statement needs that info, which you could easily do via a case statement.

First define row type. For existing database table you can use %ROWTYPE.
Then define table type.
Finally use the table type as return type of the function.
TYPE tr_row IS my_table%ROWTYPE;
TYPE tab_row IS TABLE OF tr_row;
FUNCTION get_row RETURN tab_row IS
mytab tab_row;
BEGIN
SELECT * FROM my_table
BULK COLLECT INTO mytab;
RETURN mytab;
END;

Related

Return updated rows from a stored function

Im trying to select some rows from a Table in ORACLE and at the same time update the selected rows state. I found a way to do so with a stored function and Cursors but I cant manage to return the rows after using the cursor to update. This is my code:
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
CURSOR c_operations IS
SELECT * FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
BEGIN
FOR r_operation IN c_operations
LOOP
UPDATE
TABLE1
SET
TABLE1.STATUS = 'OK'
WHERE
TABLE1.ID_TABLE1 = r_operation.ID_TABLE1;
END LOOP;
COMMIT;
-- Missing conversion from cursor to sys_refcursor
RETURN l_return;
END;
The update is working but Im still missing how to return the updated rows that are in the cursor (c_operations ).
Thank you.
I'm going to make some assumptions:
id_table1 is the primary key of the table, so your RBAR (*) update affects only one row
id_table1 is numeric
If these assumptions are wrong you will need to tweak the following code.
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
l_id table1.id_table1%type;
l_upd_ids sys.odcinumberlist := new sys.odcinumberlist();
CURSOR c_operations IS
SELECT * FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
BEGIN
FOR r_operation IN c_operations LOOP
UPDATE TABLE1
SET TABLE1.STATUS = 'OK'
WHERE TABLE1.ID_TABLE1 = r_operation.ID_TABLE1
returning TABLE1.ID_TABLE1 into l_id;
l_upd_ids.extend();
l_upd_ids(l_upd_ids.count()) := l_id;
END LOOP;
COMMIT;
open l_return for
select * from table(l_upd_ids);
RETURN l_return;
END;
The key points of the solution.
uses Oracle maintained collection (of number) sys.odcinumberlist to store the updated IDs;
uses RETURNING clause to capture the id_table1 value for the updated row;
stores the returned key in the collection;
uses a table() function to casrt the collection into a table which can be queried in the ref cursor.
This last point is why I chose to use sys.odcinumberlist rather than defining a collection in the procedure. It's a SQL type, so we can use it in SELECT statements.
(*) Row-by-agonizing-row. Updating single records in a PL/SQL loop is the slowest way of executing bulk updates, and normally constitutes an anti-pattern. A straightforward set-based UPDATE should suffice. However, you know your own situation so I'm going to leave that as it is.
It looks to me like you don't need the initial cursor, since you're changing the STATUS of every row which is not 'OK' to 'OK', so you can do this is a simple UPDATE statement. Then use an OPEN...FOR statement to return a cursor of all rows where STATUS is not 'OK', which shouldn't return anything because you've already changed all the status values to 'OK'. I suggest that you rewrite your procedure as:
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
BEGIN
UPDATE TABLE1
SET STATUS = 'OK'
WHERE STATUS != 'OK';
COMMIT;
OPEN l_return FOR SELECT *
FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
RETURN l_return;
END;
Instead of a loop to update how about a bulk update collecting the updated ids. Then a table function from those returned ids.
create type t_table1_id is
table of integer;
create or replace function set_table1_status_ok
return sys_refcursor
is
l_results_cursor sys_refcursor;
l_updated_ids t_table1_id;
begin
update table1
set status = 'Ok'
where status != 'Ok'
returning table1.id
bulk collect
into l_updated_ids;
open l_results_cursor for
select *
from table1
where id in (select * from table(l_updated_ids));
return l_results_cursor;
end set_table1_status_ok;
-- test
declare
updated_ids sys_refcursor;
l_this_rec table1%rowtype;
begin
updated_ids := set_table1_status_ok();
loop
fetch updated_ids into l_this_rec;
exit when updated_ids%notfound;
dbms_output.put_line ( l_this_rec.id || ' updated.');
end loop;
close updated_ids;
end ;

PL/SQL: ORA-00947: not enough values when creating function

I am creating a package with a function that returns a table. Here is the (simplified) code:
DROP TYPE A_TABLE;
/
CREATE OR REPLACE TYPE A_RECORD IS object (
ID varchar(10),
Name varchar(500)
);
/
CREATE TYPE A_TABLE IS TABLE OF A_RECORD;
/
CREATE OR REPLACE PACKAGE My_Package AS
results A_TABLE;
FUNCTION A_Data()
RETURN A_TABLE;
END My_Package;
/
CREATE OR REPLACE PACKAGE BODY My_Package IS
FUNCTION A_Data() RETURN A_TABLE IS results A_TABLE;
BEGIN
SELECT ID,Name
BULK COLLECT INTO results
FROM Customer_Table customer
RETURN results;
END A_Data;
END My_Package;
/
I get this error on compiling the package:
Error(9,13): PL/SQL: ORA-00947: not enough values
The query itself works although there is no data in there at the moment as the database is being worked on at the moment. I can't understand why this is breaking. I think it might be something to do with types?
You select two values into one record. It not work that way. You should create your record type in your select:
CREATE OR REPLACE PACKAGE BODY My_Package IS
FUNCTION A_Data() RETURN A_TABLE IS results A_TABLE;
BEGIN
SELECT A_RECORD(ID,Name)
BULK COLLECT INTO results
FROM Customer_Table customer
RETURN results;
END A_Data;
END My_Package;
/
The issue is
SELECT ID,Name -- here
BULK COLLECT INTO results
FROM Customer_Table customer
The objects of type A_RECORD need to be collected in result. so, call should be
SELECT A_RECORD(ID,Name) BULK ...`
Besides, I think you don't really need to create types externally. This should work:
create or replace PACKAGE My_Package AS
TYPE A_TABLE IS TABLE OF Customr_Table%rowtype;
FUNCTION A_Data
RETURN A_TABLE;
END My_Package;
/
CREATE OR REPLACE PACKAGE BODY My_Package
IS
FUNCTION A_Data
RETURN A_TABLE
IS
results A_TABLE;
BEGIN
SELECT ID, NAME BULK COLLECT INTO results FROM Customr_Table;
RETURN results;
END A_Data;
END My_Package;
/
And you can use it like this:
declare
v My_Package.A_Table;
begin
v := My_Package.A_data();
for i in 1 .. v.count loop
dbms_output.put_line(v(i).id || ' ' || v(i).Name);
end loop;
end;
/

Function should return resultset when executed in select statement

I'm trying to write a pl/sql function that returns a resultset table that I can retrieve using a select statement.
Here is what I got so far:
Type REF CURSOR:
CREATE OR REPLACE PACKAGE types
AS
TYPE ref_cursor IS REF CURSOR;
END;
Function:
CREATE OR REPLACE FUNCTION getitems
RETURN types.ref_cursor
AS
mycursor types.ref_cursor;
BEGIN
OPEN mycursor FOR
SELECT myid, myname, mytype
FROM mycategory;
RETURN mycursor;
END getitems;
Select statement:
SELECT getitems() FROM dual;
This is almost(!) what I wanted. The only problem now is that Selecting From dual resulted in exactly one entry in the resultset, this entry being the table I really wanted.
How do I get the select statement to unwrap the the outer resultset and deliver just the inner table so I can cursor along the inner table?
You can't query a REF CURSOR. A possible solution is to use PIPELINED function and TABLE function (read more here: Tim Hall about PIPELINED functions). Example:
CREATE TABLE emp (
id NUMBER,
name VARCHAR2(20)
);
INSERT INTO emp VALUES (1, 'Smith');
INSERT INTO emp VALUES (2, 'Brown');
COMMIT;
CREATE OR REPLACE TYPE emp_row_t AS OBJECT (
id NUMBER,
name VARCHAR2(20)
);
CREATE OR REPLACE TYPE emp_tab_t IS TABLE OF emp_row_t;
CREATE OR REPLACE FUNCTION piped_function RETURN emp_tab_t PIPELINED AS
BEGIN
FOR v_rec IN (SELECT id, name FROM emp)
LOOP
PIPE ROW (emp_row_t(v_rec.id, v_rec.name));
END LOOP;
END;
-- and now query using that function
SELECT id, name FROM TABLE(piped_function);
Check at SQLFiddle: SQLFiddle PIPELINED function example

How to use session-global variables of type collection in oracle

I have a package which declares a collection of type table of some database table's %rowtype. It also declares a function to populate the package-level variable with some data. I can now print the data with dbms_output, seems fine.
But when I use the package-level variable in some sql I get the following error:
ORA-21700: object does not exist or is marked for delete
ORA-06512: at "TESTDB.SESSIONGLOBALS", line 17
ORA-06512: at line 5
Here is my code:
create some dummy data:
drop table "TESTDATA";
/
CREATE TABLE "TESTDATA"
( "ID" NUMBER NOT NULL ENABLE,
"NAME" VARCHAR2(20 BYTE),
"STATUS" VARCHAR2(20 BYTE)
);
/
insert into "TESTDATA" (id, name, status) values (1, 'Hans Wurst', 'J');
insert into "TESTDATA" (id, name, status) values (2, 'Hans-Werner', 'N');
insert into "TESTDATA" (id, name, status) values (3, 'Hildegard v. Bingen', 'J');
/
now create the package:
CREATE OR REPLACE
PACKAGE SESSIONGLOBALS AS
type t_testdata is table of testdata%rowtype;
v_data t_testdata := t_testdata();
function load_testdata return t_testdata;
END SESSIONGLOBALS;
and the package body:
CREATE OR REPLACE
PACKAGE BODY SESSIONGLOBALS AS
function load_testdata return t_testdata AS
v_sql varchar2(500);
BEGIN
if SESSIONGLOBALS.v_data.count = 0
then
v_sql := 'select * from testdata';
execute immediate v_sql
bulk collect into SESSIONGLOBALS.v_data;
dbms_output.put_line('data count:');
dbms_output.put_line(SESSIONGLOBALS.v_data.count);
end if; -- SESSIONGLOBALS.v_data.count = 0
-- ******************************
-- this line throws the error
insert into testdata select * from table(SESSIONGLOBALS.v_data);
-- ******************************
return SESSIONGLOBALS.v_data;
END load_testdata;
END SESSIONGLOBALS;
execute the sample:
DECLARE
v_Return SESSIONGLOBALS.T_TESTDATA;
BEGIN
v_Return := SESSIONGLOBALS.LOAD_TESTDATA();
dbms_output.put_line('data count (direct access):');
dbms_output.put_line(SESSIONGLOBALS.v_data.count);
dbms_output.put_line('data count (return value of function):');
dbms_output.put_line(v_Return.count);
END;
If the line marked above is commented out i get the expected result.
So can anyone tell me why the exception stated above occurs?
BTW: it is absolutely nessecary for me to execute the statement which populates the collection with data as dynamic sql because the tablename is not known at compiletime. (v_sql := 'select * from testdata';)
the solution is to use pipelined functions in the package
see: http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dcitblfns.htm#CHDJEGHC ( => section Pipelining Between PL/SQL Table Functions does the trick).
my package looks like this now (please take the table script from my question):
create or replace
PACKAGE SESSIONGLOBALS AS
v_force_refresh boolean;
function set_force_refresh return boolean;
type t_testdata is table of testdata%rowtype;
v_data t_testdata;
function load_testdata return t_testdata;
function get_testdata return t_testdata pipelined;
END SESSIONGLOBALS;
/
create or replace
PACKAGE BODY SESSIONGLOBALS AS
function set_force_refresh return boolean as
begin
SESSIONGLOBALS.v_force_refresh := true;
return true;
end set_force_refresh;
function load_testdata return t_testdata AS
v_sql varchar2(500);
v_i number(10);
BEGIN
if SESSIONGLOBALS.v_data is null then
SESSIONGLOBALS.v_data := SESSIONGLOBALS.t_testdata();
end if;
if SESSIONGLOBALS.v_force_refresh = true then
SESSIONGLOBALS.v_data.delete;
end if;
if SESSIONGLOBALS.v_data.count = 0
then
v_sql := 'select * from testdata';
execute immediate v_sql
bulk collect into SESSIONGLOBALS.v_data;
end if; -- SESSIONGLOBALS.v_data.count = 0
return SESSIONGLOBALS.v_data;
END load_testdata;
function get_testdata return t_testdata pipelined AS
v_local_data SESSIONGLOBALS.t_testdata := SESSIONGLOBALS.load_testdata();
begin
if v_local_data.count > 0 then
for i in v_local_data.first .. v_local_data.last
loop
pipe row(v_local_data(i));
end loop;
end if;
end get_testdata;
END SESSIONGLOBALS;
/
now i can do a select in sql like this:
select * from table(SESSIONGLOBALS.get_testdata());
and my data collection is only populated once.
nevertheless it is quite not comparable with a simple
select * from testdata;
from a performace point of view but i'll try out this concept for some more complicated use cases. the goal is to avoid doing some really huge select statements involving lots of tables distributed among several schemas (english plural for schema...?).
The syntax you use does not work:
insert into testdata select * from table(SESSIONGLOBALS.v_data); -- does not work
You have to use something like that:
forall i in 1..v_data.count
INSERT INTO testdata VALUES (SESSIONGLOBALS.v_data(i).id,
SESSIONGLOBALS.v_data(i).name,
SESSIONGLOBALS.v_data(i).status);
(which actually duplicates the rows in the table)
Package-level types cannot be used in SQL. Even if your SQL is called from within a package, it still can't see that package's types.
I'm not sure how you got that error message, when I compiled the package I got this error, which gives a good hint at the problem:
PLS-00642: local collection types not allowed in SQL statements
To fix this problem, create a type and a nested table of that type:
create or replace type t_testdata_rec is object
(
"ID" NUMBER,
"NAME" VARCHAR2(20 BYTE),
"STATUS" VARCHAR2(20 BYTE)
);
create or replace type t_testdata as table of t_testdata_rec;
/
The dynamic SQL to populate the package variable gets more complicated:
execute immediate
'select cast(collect(t_testdata_rec(id, name, status)) as t_testdata)
from testdata ' into SESSIONGLOBALS.v_data;
But now the insert will work as-is.

How to wrap an Oracle stored procedure in a function that gets executed by a standard SELECT query?

I am following these steps, but I continue to get an error and don't see the issue:
1) Create a custom Oracle datatype that represents the database columns that you want to retrieve:
CREATE TYPE my_object AS OBJECT
(COL1 VARCHAR2(50),
COL2 VARCHAR2(50),
COL3 VARCHAR2(50));
2) Create another datatype that is a table of the object you just created:
TYPE MY_OBJ_TABLE AS TABLE OF my_object;
3) Create a function that returns this table. Also use a pipeline clause so that results are pipelined back to the calling SQL, for example:
CREATE OR REPLACE
FUNCTION MY_FUNC (PXOBJCLASS varchar2)
RETURN MY_OBJ_TABLE pipelined IS
TYPE ref1 IS REF CURSOR
Cur1 ref1,
out_rec_my_object := my_object(null,null,null);
myObjClass VARCHAR2(50);
BEGIN
myObjClass := PXOBJCLASS
OPEN Cur1 For ‘select PYID, PXINSNAME, PZINSKEY from PC_WORK where PXOBJCLass = ;1’USING myObjClass,
LOOP
FETCH cur1 INTO out_rec.COL1, out_rec.COL2, out_rec.COL3;
EXIT WHEN Cur1%NOTFOUND;
PIPE ROW (out_rec);
END LOOP;
CLOSE Cur1;
RETURN;
END MY_FUNC;
NOTE: In the example above, you can easily replace the select statement with a call to another stored procedure that returns a cursor variable.
4) In your application, call this function as a table function using the following SQL statement:
select COL1, COL2, COL3 from TABLE(MY_FUNC('SomeSampletask'));
There is no need to use dynamic sql (dynamic sql is always a little bit slower) and there are too many variables declared. Also the for loop is much easier. I renamed the argument of the function from pxobjclass to p_pxobjclass.
Try this:
create or replace function my_func (p_pxobjclass in varchar2)
return my_obj_table pipelined
is
begin
for r_curl in (select pyid,pxinsname,pzinskey
from pc_work
where pxobjclass = p_pxobjclass) loop
pipe row (my_object(r_curl.pyid,r_curl.pxinsname,r_curl.pzinskey));
end loop;
return;
end;
EDIT1:
It is by the way faster to return a ref cursor instead of a pipelined function that returns a nested table:
create or replace function my_func2 (p_pxobjclass in varchar2)
return sys_refcursor
is
l_sys_refcursor sys_refcursor;
begin
open l_sys_refcursor for
select pyid,pxinsname,pzinskey
from pc_work
where pxobjclass = p_pxobjclass;
return l_sys_refcursor;
end;
This is faster because creating objects (my_object) takes some time.
I see two problems:
The dynamic query does not work that way, try this:
'select PYID, PXINSNAME, PZINSKEY from PC_WORK where PXOBJCLass ='''||PXOBJCLASS||''''
You don't need myObjClass, and it seems all your quotes are wrong.
The quoting on 'SomeSampletask'...
select COL1, COL2, COL3 from TABLE(MY_FUNC('SomeSampletask'));
Maybe I'm misunderstanding something here, but it seems like you want to be using a VIEW.

Resources