use oracle cursor within a sys_refcursor - oracle

I've got a PL/SQL package that returns a sys_refcursor based on the id that you pass it. I'd like to iterate through some ids and create a new ref cursor with one column from the original result set repeated for each id. (Sort of a cross tab.) A very simplified version of the PL/SQL block looks like:
create or replace package body dashboard_package is
procedure visits(RC in out sys_refcursor, IdNumber varchar2) as
BEGIN
OPEN RC FOR
select *
from (
select cat, cat_order, subcat, label_text
, trim(to_char(sum(v.current_month),'9,999,999,999')) current_month
, trim(to_char(sum(v.ly_month),'9,999,999,999')) ly_month
, trim(to_char(sum(v.ytd_tot),'9,999,999,999')) ytd_tot
, trim(to_char(sum(v.lytd_tot),'9,999,999,999')) lytd_tot
, trim(to_char(sum(v.ly_tot),'9,999,999,999')) ly_tot
from dashboard v
where v.id_number = IdNumber
group by cat_order, subcat, cat, label_text
union all
...
) a
order by cat_order, subcat;
END;
END;
I think if I had something like this
create or replace procedure test_refcur is
refCursorValue SYS_REFCURSOR;
begin
dashboard_package.visits(refCursorValue,12345);
for cursrow in refCursorValue loop
dbms_output.put_line(cursrow.ytd_tot);
end loop;
end test_refcur;
working, I could take it from there... any thoughts? Or perhaps clarification on the question that I should be asking.

If you're coming in with a number of IDs, then first prize would be to run only one SQL query to fetch the lot in one go, using a bulk in-bind for the IDs. This would probably require a modification to dashboard_package.visits, or writing a new version of the visits procedure to accept a PL/SQL table of IDs instead of a single ID.
If your hands are tied WRT modifying dashboard_package, then you could write a pipelined function to return the rows for a set of IDs:
-- create some helper types for the pipelined function
create type visitobj as object
(id number
,cat dashboard.cat%type
,cat_order dashboard.cat_order%type
,subcat dashboard.subcat%type
,label_text dashboard.label_text%type
,current_month varchar2(13)
,ly_month varchar2(13)
,ytd_tot varchar2(13)
,lytd_tot varchar2(13)
,ly_tot varchar2(13));
create type visittable as table of visitobj;
create or replace function test_refcur
return visittable deterministic pipelined is
refCursorValue SYS_REFCURSOR;
cat dashboard.cat%type;
cat_order dashboard.cat_order%type;
subcat dashboard.subcat%type;
label_text dashboard.label_text%type;
current_month varchar2(13);
ly_month varchar2(13);
ytd_tot varchar2(13);
lytd_tot varchar2(13);
ly_tot varchar2(13);
begin
for id in (/*iterate through the IDs*/) loop
dashboard_package.visits(refCursorValue, id);
loop
fetch refCursorValue into cat, cat_order, subcat, label_text,
current_month, ly_month, ytd_tot,
lytd_tot, ly_tot;
exit when refCursorValue%NOTFOUND;
pipe row (visitobj (id, cat, cat_order, subcat, label_text,
current_month, ly_month, ytd_tot,
lytd_tot, ly_tot));
end loop;
end loop;
return;
end test_refcur;
-- now you can simply do this:
SELECT * FROM TABLE(test_refcur);
(Of course, "/*iterate through the IDs*/" would be whatever method you want to use to gather the IDs for which the function should be called - e.g. could be a PL/SQL table of IDs, or perhaps another query).
Again I'd stress that "first prize" is to not do any of this extra work at all - just have a dashboard_package.visits that does it all in one SQL.
On a side note, trim(to_char(sum(v.ly_tot),'9,999,999,999')) can be simplified to to_char(sum(v.ly_tot),'FM9,999,999,999'). Also, if you use the format 'FM9G999G999G999' instead, it will be non-locale-specific.

Related

How to insert records into variables from cte in oracle?

I have a procedure in which I want to fetch all records from cte into Names variable. But this code is not writing into names from CTE. How can I fetch records into names so that I can later loop through names and get content of field_name?
CREATE OR REPLACE PROCEDURE sp_market
IS
Names VARCHAR2(32767);
BEGIN
WITH CTE(sqql) As
(
SELECT field_name sqql FROM pld_medicare_config
)
SELECT sqql into Names from CTE;
END sp_market;
SELECT sqql into Names from CTE;
You are assigning multiple rows returned from table to a variable, which will fail.
You could simply use a CURSOR FOR LOOP which will create an implicit cursor and you can loop through the names:
CREATE OR REPLACE PROCEDURE sp_market IS
BEGIN
FOR i IN (
SELECT field_name
FROM pld_medicare_config
)
LOOP
-- Your logic goes here
dbms_output.put_line(i.field_name);
END LOOP;
END;
/
I think your best bet is to create a associative array and use BULK COLLECT to populate the table. In its simplest form, the code would look like this:
CREATE OR REPLACE PROCEDURE sp_market IS
TYPE lt_names IS TABLE OF VARCHAR2(32767) INDEX BY PLS_INTEGER;
l_tNames lt_names;
BEGIN
SELECT field_name
BULK COLLECT INTO l_tNames
FROM pld_medicare_config
IF l_tNames.COUNT() > 0 THEN
FOR i IN l_tNames.FIRST..l_tNames.LAST LOOP
NULL; --REPLACE WITH YOUR LOGIC
END LOOP;
END IF;
END;
/
A few notes:
I'm assuming that you've set MAX_STRING_SIZE to EXTENDED. Otherwise, you'll have an issue with VARCHAR2 that big.
As I said, that is the simplest way to do this. If you're expecting a huge result set, you'll want to look into chunking it up. This Oracle blog post is very helpful in giving you multiple options for how to perform bulk processing. (Blog Post)

How to display the results of a procedure outside of it in Oracle

I am working on an application and made the decision that all the queries would be procedures. I hope to have gains in performance and ease of maintenance by doing it this way. Our DBA's have also expressed interest in having it done this way.
I have an HR table where operations are performed on it each night, and any changes are recorded in a secondary table. We are not doing auditing, these change records are kept until the next run and show users the changes that have happened.
To keep my question shorter I have reduced the number of columns in HR.
The HR table ID, GROUP_NAME, and GROUP_LEVEL. The Drill table has ID and TYPEVALUE.
CREATE OR REPLACE PROCEDURE DOCSADM.DRILL_RECORD_POSITION (
RECORD_TYPE IN VARCHAR2,
OUT_ID OUT VARCHAR2,
OUT_GROUP_NAME OUT VARCHAR2,
OUT_GROUP_LEVEL OUT VARCHAR2
) AS
BEGIN
SELECT HR.ID, HR.GROUP_NAME, HR.GROUP_LEVEL
INTO OUT_ID, OUT_GROUP_NAME, OUT_GROUP_LEVEL
FROM HR_POSITION HR JOIN DRILL_POSITION DP ON (HR.ID = DP.ID) WHERE DP.TYPEVALUE = RECORD_TYPE;
END DRILL_RECORD_POSITION;
The procedure compiles without issue. Before doing all the work in the application to link to the procedure and extract the values which in this case will eventually be displayed in a view or webpage, I wanted to have a quick little script that would call the procedure and then display the results so I can verify in Oracle.
Loops
BEGIN
for t in (DRILL_RECORD_POSITION('D', V1,V5,V6))
loop
--dbms_output.put_line(t.V1 || t.V5 || t.V6);
dbms_output.put_line(t.OUT_ID);
end loop;
END;
/
CURSORS
DECLARE
V1 HR_POSITION.ID%TYPE;
V5 HR_POSITION.GROUP_NAME%TYPE;
V6 HR_POSITION.GROUP_LEVEL%TYPE;
CURSOR T_CUR IS DRILL_RECORD_POSITION('D', V1,V5,V6);
BEGIN
OPEN T_CUR;
DBMS_OUTPUT.PUTLINE('START');
LOOP
FETCH T_CUR INTO V1,V5,V6;
EXIT WHEN T_CUR%NOTFOUND;
DBMS_OUTPUT.PUTLINE(V1||V5||V6);
END LOOP;
CLOSE T_CUR;
END;
FOR LOOPS
DECLARE
V1 HR_POSITION.POSITION_ID%TYPE;
V5 HR_POSITION.GROUP_NAME%TYPE;
V6 HR_POSITION.GROUP_LEVEL%TYPE;
BEGIN
DBMS_OUTPUT.PUTLINE('START');
FOR INDEX IN (DRILL_RECORD_POSITION('D', V1,V5,V6))
LOOP
--DBMS_OUTPUT.PUTLINE(INDEX.ID);
DBMS_OUTPUT.PUTLINE(INDEX.V1||INDEX.V5||INDEX.V6);
END LOOP;
END;
Note: I edited the column names out and shorted some when transferring here so I might have made a few mistakes.
All the articles I have seen online show me how to display from within the original procedure or by using views, cursors, records. Unless I am wrong, Eclipse wont have any problems using the information in the current form which is why I am passing it that way. So I am not interested in changing the procedure and would like to work with it as is, since thats how the application will be doing it.
As this is the first of the stored procedures I am doing for the application, instead of using adhoc queries from the application, I dont have any existing examples to work from, which is why I believe the results will work fine, because it should be the same format the adhoc ones use.
Update:
In one of the comments, I was pointed to what should have been a solution. This was confirmed by another solution that was under it.
I keep getting the error
ORA-01422: exact fetch returns more than requested number of rows
So Im returning multiple rows, but that is my expectation and what is happening. I just cant seem to figure out how to display the results.
To test the procedure you showed, you would do something like:
declare
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
drill_record_position('D', l_id, l_group_name, l_group_level);
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end;
/
But that - or more specifically, your procedure - only works if there is exactly one row in the query's result set for the passed-in value type. It seems you're expecting multiple rows back (which would get too-many-rows), but there could also be non (which would get no-data-found).
So really it seems like your question should be about how to write your procedure so it works with one of the retrieval/test methods you tried.
If your procedure needs to return multiple rows then it can use a ref cursor, e.g.:
create or replace procedure drill_record_position (
p_record_type in varchar2,
p_ref_cursor out sys_refcursor
)
as
begin
open p_ref_cursor for
select hr.id, hr.group_name, hr.group_level
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type;
end drill_record_position;
/
which you could then test with something like:
declare
l_ref_cursor sys_refcursor;
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
drill_record_position('D', l_ref_cursor);
loop
fetch l_ref_cursor into l_id, l_group_name, l_group_level;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end loop;
close l_ref_cursor;
end;
/
You can also do that as a function, which might be easier to work with from your application:
-- drop procedure drill_record_position;
create or replace function drill_record_position (p_record_type in varchar2)
return sys_refcursor as
l_ref_cursor sys_refcursor;
begin
open l_ref_cursor for
select hr.id, hr.group_name, hr.group_level
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type;
return l_ref_cursor;
end drill_record_position;
/
declare
l_ref_cursor sys_refcursor;
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
l_ref_cursor := drill_record_position('D');
loop
fetch l_ref_cursor into l_id, l_group_name, l_group_level;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end loop;
close l_ref_cursor;
end;
/
You coudl also do this with collections and a pipelined function, which is more work to set up:
create type t_drill_obj as object (
-- use your real data types...
id number,
group_name varchar2(10),
group_level number
)
/
create type t_drill_tab as table of t_drill_obj
/
create or replace function drill_record_position (p_record_type in varchar2)
return t_drill_tab pipelined as
begin
for l_row in (
select t_drill_obj(hr.id, hr.group_name, hr.group_level) as obj
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type
)
loop
pipe row (l_row.obj);
end loop;
return;
end drill_record_position;
/
but you could call it as part of another query, and even join tot he result if you needed to:
select * from table(drill_record_position('D'));

PL/SQL Creating a procedure that contains result set joins

I want to create a procedure in PL/SQL that has 5 steps. Step 1 and 2 execute first and return an ID. In step 3, we have a SELECT statement that has a condition with that returned ID. I want then to take all of the results of that SELECT statement and use them in a JOIN in another SELECT statement and use THOSE results in a 3rd SELECT statement again using JOIN. From what I've seen, I can't use CURSOR in JOIN statements. Some of my co-workers have suggested that I save the results in a CURSOR and then use a loop to iterate through each row and use that data for the next SELECT. However since I'm going to do 2 selects this will create a huge fork of inside loops and that's exactly what I'm trying to avoid.
Another suggestion was to use Temprary Tables to store the data. However this procedure could be executed at the same time by many users and the table's data would conflict with each other. Right now I'm looking at LOCAL Temporary tables that supposedly filter the data according the the session but I'm not really sure I want to create dummy tables for my procedures since I want to avoid leaving trash in the schema (this procedure is for a custom part of the application). Is there a standard way of doing this? Any ideas?
Sample:
DECLARE
USERID INT := 1000000;
TEXT1 VARCHAR(100);
TEXT_INDEX INT;
CURSOR NODES IS SELECT * FROM NODE_TABLE WHERE DESCRIPTION LIKE TEXT || '%';
CURSOR USERS IS SELECT * FROM USERGROUPS JOIN NODES ON NODES.ID = USERGROUPS.ID;
BEGIN
SELECT TEXT INTO TEXT1 FROM TABLE_1 WHERE ID = USERID;
TEXT_INDEX = INSTR(TEXT, '-');
TEXT = SUBSTR(TEXT, 0, TEXT_INDEX);
OPEN NODES;
OPEN USERS;
END;
NOTE: This does NOT work. Oracle doesn't support joins between cursors.
NOTE2: This CAN be done in a single query but for the sake of argument (and in my real use case) I want to break those steps down in a procedure. The sample code is a depiction of what I'm trying to achieve IF joins between cursors worked. But they don't and I'm looking for an alternative.
I ended up using a function (although a procedure could be used as well) along with tables. Things I've learned and one should pay attention to:
PL/SQL functions can only return types that have been declared in the schema in advance and are clear. You can't create a function that returns something like MY_TABLE%ROWTYPE, even though it seems the type information is available it is not acceptable. You have to instead create a custom type of MY_TABLE%ROWTYPE is you want to return it.
Oracle treats tables of declared types differently from tables of %ROWTYPE. This confused the hell out of me at first but from what I've gathered this is how it works.
DECLARE TYPE MY_CUSTOM_TABLE IS TABLE OF MY_TABLE%ROWTYPE;
Declares a collection of types of MY_TABLE row. In order to add to this we must use BULK COLLECT INTO from an SQL statement that queries MY_TABLE. The resulting collection CANNOT be used in JOIN statements is not queryable and CANNOT be returned by a function.
DECLARE
CREATE TYPE MY_CUSTOM_TYPE AS OBJECT (COL_A NUMBER, COL_B NUMBER);
CREATE TYPE MY_CUSTOM_TABLE AS TABLE OF MY_CUSTOM_TYPE;
my_custom_tab MY_CUSTOM_TABLE;
This create my_custom_tab which is a table (not a collection) and if populated can be queried at using TABLE(my_custmo_tab) in the FROM statement. As a table which is declared in advance in the schema this CAN be returned from a function. However it CANNOT be populated using BULK COLLECT INTO since it is not a collection. We must instead use the normal SELECT INTO statement. However, if we want to populate it with data from an existing table that has 2 number columns we cannot simply do SELECT * INTO my_custom_tab FROM DOUBLE_NUMBER_TABLE since my_custom_tab hasn't been initialized and doesn't contain enough rows to receive the data. And if we don't know how many rows a query returns we can't initialize it. The trick into populating the table is to use the CAST command and cast our select result set as a MY_CUSTOM_TABLE and THEN add it.
SELECT CAST(MULTISET(SELECT COL_A, COL_B FROM DOUBLE_NUMBER_TABLE) AS MY_CUSTOM_TABLE) INTO my_custom_tab FROM DUAL
Now we can easily use my_custom_tab in queries etc through the use of the TABLE() function.
SELECT * FROM TABLE(my_custom_tab)
is valid.
You can do such decomposition in many ways, but all of them have a significant performance penalty in comaration with single SQL statement.
Maintainability improvement are also questionable and depends on specific situation.
To review all possibilities please look through documentation.
Below is some possible variants based on simple logic:
calculate Oracle user name prefix based on given Id;
get all users whose name starts with this prefix;
find all tables owned by users from step 2;
count a total number of found tables.
1. pipelined
Prepare types to be used by functions:
create or replace type TUserRow as object (
username varchar2(30),
user_id number,
created date
)
/
create or replace type TTableRow as object (
owner varchar2(30),
table_name varchar2(30),
status varchar2(8),
logging varchar2(3)
-- some other useful fields here
)
/
create or replace type TUserList as table of TUserRow
/
create or replace type TTableList as table of TTableRow
/
Simple function to find prefix by user id:
create or replace function GetUserPrefix(piUserId in number) return varchar2
is
vUserPrefix varchar2(30);
begin
select substr(username,1,3) into vUserPrefix
from all_users
where user_id = piUserId;
return vUserPrefix;
end;
/
Function searching for users:
create or replace function GetUsersPipe(
piNameStart in varchar2
)
return TUserList pipelined
as
vUserList TUserList;
begin
for cUsers in (
select *
from
all_users
where
username like piNameStart||'%'
)
loop
pipe row( TUserRow(cUsers.username, cUsers.user_id, cUsers.created) ) ;
end loop;
return;
end;
Function searching for tables:
create or replace function GetUserTablesPipe(
piUserNameStart in varchar2
)
return TTableList pipelined
as
vTableList TTableList;
begin
for cTables in (
select *
from
all_tables tab_list,
table(GetUsersPipe(piUserNameStart)) user_list
where
tab_list.owner = user_list.username
)
loop
pipe row ( TTableRow(cTables.owner, cTables.table_name, cTables.status, cTables.logging) );
end loop;
return;
end;
Usage in code:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from table(GetUserTablesPipe(GetUserPrefix(vUserId)));
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
2. Simple table functions
This solution use same types as a variant with pipelined functions above.
Function searching for users:
create or replace function GetUsers(piNameStart in varchar2) return TUserList
as
vUserList TUserList;
begin
select TUserRow(username, user_id, created)
bulk collect into vUserList
from
all_users
where
username like piNameStart||'%'
;
return vUserList;
end;
/
Function searching for tables:
create or replace function GetUserTables(piUserNameStart in varchar2) return TTableList
as
vTableList TTableList;
begin
select TTableRow(owner, table_name, status, logging)
bulk collect into vTableList
from
all_tables tab_list,
table(GetUsers(piUserNameStart)) user_list
where
tab_list.owner = user_list.username
;
return vTableList;
end;
/
Usage in code:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from table(GetUserTables(GetUserPrefix(vUserId)));
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
3. cursor - xml - cursor
It's is a specific case, which may be implemented without user-defined types but have a big performance penalty, involves unneeded type conversion and have a low maintainability.
Function searching for users:
create or replace function GetUsersRef(
piNameStart in varchar2
)
return sys_refcursor
as
cUserList sys_refcursor;
begin
open cUserList for
select * from all_users
where username like piNameStart||'%'
;
return cUserList;
end;
Function searching for tables:
create or replace function GetUserTablesRef(
piUserNameStart in varchar2
)
return sys_refcursor
as
cTableList sys_refcursor;
begin
open cTableList for
select
tab_list.*
from
(
XMLTable('/ROWSET/ROW'
passing xmltype(GetUsersRef(piUserNameStart))
columns
username varchar2(30) path '/ROW/USERNAME'
)
) user_list,
all_tables tab_list
where
tab_list.owner = user_list.username
;
return cTableList;
end;
Usage in code:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from
XMLTable('/ROWSET/ROW'
passing xmltype(GetUserTablesRef(GetUserPrefix(vUserId)))
columns
table_name varchar2(30) path '/ROW/TABLE_NAME'
)
;
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
Of course, all variants may be mixed, but SQL looks better at least for simple cases:
declare
vUserId number := 5;
vUserPrefix varchar2(100);
vTableCount number;
begin
-- Construct prefix from Id
select max(substr(user_list.username,1,3))
into vUserPrefix
from
all_users user_list
where
user_list.user_id = vUserId
;
-- Count number of tables owned by users with name started with vUserPrefix string
select
count(1) into vTableCount
from
all_users user_list,
all_tables table_list
where
user_list.username like vUserPrefix||'%'
and
table_list.owner = user_list.username
;
dbms_output.put_line('Users with name started with "'||vUserPrefix||'" owns '||vTableCount||' tables');
end;
P.S. All code only for demonstration purposes: no optimizations and so on.

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.

Returning Oracle ref cursor and appending multiple results

I have this problem I'm hoping someone knows the answer to. I have an oracle stored procedure that takes a customer id and returns all the customer's orders in a ref_cursor. Oversimplifying it, this is what I have:
Orders
- orderId
- siteID
Customers
- siteID
- Name
GetOrder(siteID, outCursor) /* returns all orders for a customer */
Now, I need to write another procedure that takes a customer name and does a LIKE query to get all custIds, then I need to reuse the GetOrder method to return all the orders for the custIds found, something like this:
PROCEDURE GetOrderbyCustName(
p_name IN VARCHAR2,
curReturn OUT sys_refcursor
)
IS
siteid number;
BEGIN
FOR rec in SELECT site_id FROM customers WHERE name LIKE p_name
LOOP
-- This will replace curReturn in each iteration
-- how do I append instead?
GetOrder(rec.site_id,
curReturn
);
END LOOP;
END GetOrderbyCustName;
My question is, how do I append the return of GetOrder to curReturn in each iteration? As it's written right now it overwrites it in each cycle of the loop.
Thanks!!
You can't do it like that - cursors cannot be appended or merged. Just do this instead:
PROCEDURE GetOrderbyCustName(
p_name IN VARCHAR2,
curReturn OUT sys_refcursor
)
IS
BEGIN
OPEN curReturn FOR
SELECT o.orderID, o.siteID
FROM Orders o
JOIN Customers c ON c.siteID = o.siteID
WHERE c.name LIKE p_name;
END GetOrderbyCustName;
If the query is simple, I would say go with Tony's answer. This is not only simple but likely to perform better than executing one query for each siteID.
If it is fairly complex then it might be worth some extra effort to reuse the GetOrder procedure so you only have to maintain one query.
To do this, you would need to actually fetch the data from the refcursor on each iteration of the loop, and put it into some other data structure.
One option, if it makes sense for the interface, is to change GetOrderbyCustName to have a PL/SQL index-by table as its output parameter instead of a refcursor. Append to that table on each iteration through the loop.
If you really need to return a refcursor, you can use a nested table type instead and then return a cursor querying that nested table. Something like this (not tested code):
CREATE TYPE number_table_type AS TABLE OF NUMBER;
PROCEDURE GetOrderbyCustName(
p_name IN VARCHAR2,
curReturn OUT sys_refcursor
)
IS
cursor_source_table number_table_type := number_table_type();
single_site_cursor sys_refcursor;
orderID NUMBER;
BEGIN
FOR rec in SELECT site_id FROM customers WHERE name LIKE p_name
LOOP
-- This will replace curReturn in each iteration
-- how do I append instead?
GetOrder(rec.site_id,
single_site_cursor
);
-- Fetch all rows from the refcursor and append them to the nested table in memory
LOOP
FETCH single_site_cursor INTO orderID;
EXIT WHEN single_site_cursor%NOTFOUND;
cursor_source_table.extend();
cursor_source_table( cursor_source_table.COUNT+1) := orderID;
END LOOP;
END LOOP;
OPEN curReturn FOR
SELECT * FROM TABLE( cursor_source_table );
END GetOrderbyCustName;

Resources