How to use a nested table in a cursor - oracle

CREATE PROCEDURE( p_cur OUT a_cur)
IS
type rec is record( a varchar2(2), b number, c number);
type tab is table of rec;
tab1 tab:=tab();
begin
tab1.extend;
tab1(tab1.last).a:='as';
tab1(tab1.last).b:=2;
tab1(tab1.last).c:=3;
tab1.extend;
tab1(tab1.last).a:='jj';
tab1(tab1.last).b:=2;
tab1(tab1.last).c:=3;
--??---
end;
I have created a nested table here tab1 ,but my issue is that i want to use this nested table in a cursor and want to return whole records using this nested table ,limitation is that i dont want to use any temporary table .
I am using RDBMS as ORACLE

If you want to use a collection as if it were a table then you'll nedd to look a the TABLE() function:
There is an example here: http://www.dobosz.at/oracle/select-from-plsql-table/
And another good resource here: http://www.databasejournal.com/features/oracle/article.php/2222781/Returning-Rows-Through-a-Table-Function-in-Oracle.htm
You'll need to declare the collection type in the database before then populating it in your procedure and then selecting from it.
I answered a question using this method here: Can a table variable be used in a select statement where clause?
Take a look as it should help you with what you are trying to achieve.
Hope it helps...
EDIT: In response to your question this code should do what you want it to. I haven't tested it but it should be very close to what you need and you can debug it if needed.
-- Create the relevent Object
CREATE TYPE data_obj_type AS OBJECT (
a VARCHAR2(2),
b NUMBER,
c NUMBER
);
Type Created
-- Create the collection to hold the objects
CREATE TYPE table_obj_type IS TABLE OF data_obj_type;
Type Created
CREATE OR REPLACE
PROCEDURE cursor_values(
p_cur OUT sys_refcursor
)
IS
-- Create a variable and initialise it
tab1 table_obj_type := table_obj_type();
BEGIN
-- Populate the tab1 collection
tab1.extend;
tab1(tab1.last) := data_obj_type('as', 2, 3);
tab1.extend;
tab1(tab1.last) := data_obj_type('jj', 2, 3);
--
-- Open ref_cursor for output
OPEN p_cur FOR
SELECT a,
b,
c
FROM TABLE(CAST(tab1 AS table_obj_type));
END cursor_values;
N.B.: This is code amended from this page:
http://www.akadia.com/services/ora_return_result_set.html

Related

Create insert record dynamically by changing pk of existing record for passed in table

I want to pass a table name and schema into a procedure, and have it generate insert, update and delete statements for the particular table. This is part of an automated testing solution (in a development environment) in which I need to test some change data capture. I want to make this dynamic as it is going to be need to be done for lots of different tables over a long period of time, and I need to call it via a REST request through ORDS, so don't want to have to make an endpoint for every table.
Update and delete are fairly easy, however I am struggling with the insert statement. Some of the tables being passed in have hundreds of columns with various constraints, fks etc. so I think it makes sense to just manipulate an existing record by changing only the primary key. I need to be able to modify the primary key to a new value known to me beforehand (e.g. '-1').
Ideally I would create a dynamic rowtype, and select into where rownum = 1, then loop round the primary keys found from all_constraints, and update the rowtype.pk with my new value, before inserting this into the table. Essentially the same as this but without knowing the table in advance.
e.g. rough idea
PROCEDURE manipulate_records(p_owner in varchar2, p_table in varchar2)
IS
cursor c_pk is
select column_name
from all_cons_columns
where owner = p_owner
and constraint_name in (select constraint_name
from all_constraints
where table_name = p_table
and constraint_type = 'P');
l_row tbl_passed_in%ROWTYPE --(I know this isn't possible but ideally)
BEGIN
-- dynamic sql or refcursor to collect a record
select * into tbl_passed_in from tablename where rownum = 1;
-- now loop through pks and reassign their values to my known value
for i in c_pk loop
...if matches then reassign;
...
end loop;
-- now insert the record into the table passed in
END manipulate_records;
I have searched around but haven't found any examples which fit this exact use case, where an unknown column needs to be modified and insert into a table.
Depending on how complex your procedure is, you might be able to store it as a template in a CLOB. Then pull it in, replace table and owner, then compile it.
DECLARE
prc_Template VARCHAR2(4000);
vc_Owner VARCHAR2(0008);
vc_Table VARCHAR2(0008);
BEGIN
vc_Table := 'DUAL';
vc_Owner := 'SYS';
-- Pull code into prc_Template from CLOB, but this demonstrates the concept
prc_Template := 'CREATE OR REPLACE PROCEDURE xyz AS r_Dual <Owner>.<Table>%ROWTYPE; BEGIN NULL; END;';
prc_Template := REPLACE(prc_Template,'<Owner>',vc_Owner);
prc_Template := REPLACE(prc_Template,'<Table>',vc_Table);
-- Create the procedure
EXECUTE IMMEDIATE prc_Template;
END;
Then you have the appropriate ROWTYPE available:
CREATE OR REPLACE PROCEDURE xyz AS r_Dual SYS.DUAL%ROWTYPE; BEGIN NULL; END;
But you can't create the procedure and run it in the same code block.

how to create record type using dynamic sql in oracle?

I have a table named a with column x.
create table A (x varchar2(4000));
insert into A values ('select p,q,r from o');
commit;
Now I want to create a record type dynamically on the basis of above sql i.e.
TYPE rc IS RECORD ( p o.p%type,
q o.q%type,
r o.r%type);
Please let me know how to create above record on runtime.
If its not possible, please suggest any work around.
Thank you.
if you just want to use a record of this table in your code you don't need to create a type, you can declare a rowtype variable as bellow
CREATE TABLE xx_emp (emp_id NUMBER, emp_name VARCHAR2(100));
DECLARE
l_emp_type xx_emp%ROWTYPE;
BEGIN
l_emp_type.emp_id := 10;
l_emp_type.emp_name := 'JOE';
END;
As you commented on a previous answer, I will assume that with "dinamically" you want to create it based on a query that may contain joins,
you can use cursor as bellow
DECLARE
-- here you declare a cursor named c_cursor_name
CURSOR c_cursor_name IS SELECT a.emp_name
, b.dept_name
FROM xx_emp a
, xx_Dept b
WHERE a.dept_id = b.dept_id;
-- here you can declare your record based on your cursor
r_recors_type c_cursor_name%ROWTYPE;
BEGIN
-- now you can fill your cursor as you want
r_recors_type.emp_name := 'JOE';
r_recors_type.dept_name := 'Marketing';
END;

how to insert into nested table collection using cursor plsql

I'm writing a function that needs to manipulate multiple rows at the same time and they need to be indexed. After several hours of reading about Oracle pl/sql I figured I could create a nested table kind of collection. Since I couldn't find a definitive answer and trial/error method takes way to long.
Here is question part:
QUESTION: What is the best practice to populate a nested table collection? Oracle PL/SQL
type partsTable is table of Parts_north_wing%rowtype;
pt PartsTable;
index number;
cursor pCursor is select * from Parts_north_wing;
begin
index := 1;
open pCursor;
loop
fetch pCursor into tempRow;
pt(index) := tempRow;
index := index + 1;
exit when pCursor%notfound;
end loop;
close pCursor;
A cursor FOR LOOP is almost always the best way to process rows in PL/SQL. It's simpler than the OPEN/FETCH/CLOSE method - no need to declare variables and manipulate cursors. It's also faster since it automatically bulk collects the results.
begin
for pt in
(
select parts_north_wing.*, rownum row_index
from parts_north_wing
) loop
--Do something here
null;
end loop;
end;
/
Try this. Hope this helps you to clear some of your concepts.
--Create a dummy object tyep
CREATE OR REPLACE TYPE av_obj
IS
OBJECT
(
ADD1 VARCHAR2(100),
ADD2 VARCHAR2(100) );
--Create a nested tale type
CREATE OR REPLACE TYPE AV_TT
IS
TABLE OF AV_OBJ;
--Bulk collect into nested table type
DECLARE
av_nested_tab AVROY.AV_TT;
BEGIN
SELECT avroy.av_obj(LEVEL
||'add1',LEVEL
||'add2') BULK COLLECT
INTO av_nested_tab
FROM DUAL
CONNECT BY LEVEL < 10;
END;

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.

Returning Nested table from a stored procedure

I would like to know how can I return a nested table from a stored procedure in oracle to consume in my .net client.
Basically I used the following format for creating nested table :
TYPE changes IS RECORD(
col1 VARCHAR2(20),
col2 VARCHAR2(20),
col3 VARCHAR2(20)
);
TYPE collection is table of changes;
I'm populating this with values in stored procedure logic.
Now I want to return these values for my .net client.
Can we try to dump nested tables values to cursor and return back. If yes then how ?
First off, if using ODP.net then know that you can directly pass collection types between a procedure and your UI. It is finicky, but it works.
If you want to simply dump a collection into a cursor to be returned, then look at the TABLE() function. In your example you could pass back a ref_cursor for the UI to traverse using something like (forgive any minor syntax glitches - I'm away from my database at the moment):
FUNCTION collection_to_cursor
return sys_refcursor
IS
p_cursor sys_refcursor;
p_change changes;
BEGIN
Open p_cursor for (
SELECT col1, col2, col3
FROM TABLE(p_change));
RETURN p_cursor;
end;
You could use a ref cursor, something along the lines of
DECLARE
lt_collection collection := collection();
lrc_collection SYS_REFCURSOR;
BEGIN
lt_collection := f_populate_collection_somehow;
OPEN lrc_collection
SELECT col1
,col2
,col3
FROM TABLE(CAST(lt_collection AS collection));
END;
.NET can then retrieve the data from the ref cursor, I'm not sure of the details of how this is done.
There is some good further information on using ref cursors or associative arrays
here
Please note, I am not conversant with .NET, so I am writing this literally to your question "how can I return a nested table from a stored procedure in oracle".
I am not sure about the "to consume in my .net client" portion, as I am not sure if a returned record type/table type can directly be used in .net code.
I did some research and what I learnt is that there is no direct support of record type in any oracle client interface. What people generally do is to create a wrapper procedure around the returned table type from function to convert it into a ref cursor and use the ref cursor in custom code.
CREATE OR REPLACE TYPE changes AS OBJECT(
col1 VARCHAR2(20),
col2 VARCHAR2(20),
col3 VARCHAR2(20)
);
CREATE OR REPLACE TYPE collection is table of changes;
CREATE OR REPLACE PACKAGE test_pkg AS
FUNCTION test_fn RETURN collection;
END test_pkg;
CREATE OR REPLACE PACKAGE BODY test_pkg
AS
FUNCTION test_fn RETURN collection AS
l_collection collection;
BEGIN
l_collection := collection();
l_collection.EXTEND;
l_collection(l_collection.LAST) := changes('Subhasis','Mukherjee','Male');
RETURN l_collection;
END test_fn;
END test_pkg;
SELECT * FROM table(test_pkg.test_fn)
col1 col2 col3
----------- ------------- --------------
Subhasis Mukherjee Male

Resources