How to populate nested object table in pl/sql block? - oracle

I struggle a problem, which, i think, is rather simple.
I have a type T_OPERATION_TAG in a database which is created as:
CREATE OR REPLACE TYPE t_operation_tag AS OBJECT(
tag_name VARCHAR2(30),
tag_value VARCHAR2(30),
CONSTRUCTOR FUNCTION t_operation_tag RETURN SELF AS RESULT
)
I also have another type T_OPERATION_TAGS, which is defined as follows
CREATE OR REPLACE TYPE t_operation_tags AS TABLE OF t_operation_tag;
Then in my pl/sql block i have the following code
DECLARE
p_op_tags t_operation_tags;
BEGIN
p_op_tags := t_operation_tags();
FOR i IN (SELECT tag_name, tag_value
FROM op_tags_table
WHERE some_condition)
LOOP
--How to append new lines to p_op_tags ?
END LOOP;
END;
So, if the SELECT-query in the FOR LOOP returns,e.g., five lines then how can I populate my P_OP_TAGS object table with these five lines?

Like this:
DECLARE
p_op_tags t_operation_tags;
p_cursor sys_refcursor;
p_limit number := 5;
BEGIN
open p_cursor for
SELECT t_operation_tag(tag_name, tag_value)
FROM op_tags_table
;
fetch p_cursor bulk collect into p_op_tags limit p_limit;
DBMS_OUTPUT.put_line(p_op_tags(4).tag_name);
close p_cursor;
END;
Or if you prefer the loop clause:
DECLARE
p_op_tag t_operation_tag;
p_op_tags t_operation_tags;
p_limit number := 5;
BEGIN
p_op_tags := t_operation_tags();
for i in (SELECT tag_name, tag_value
FROM op_tags_table
WHERE some_condition
and rownum < p_limit + 1)
loop
p_op_tag := t_operation_tag(i.tag_name, i.tag_value);
p_op_tags.extend();
p_op_tags(p_op_tags.COUNT) := p_op_tag;
end loop;
DBMS_OUTPUT.put_line(p_op_tags(4).tag_name);
END;
/

You don't really need a cursor or loop at all, if you're populating the collection entirely from your query; you can bulk collect straight into it:
DECLARE
p_op_tags t_operation_tags;
BEGIN
SELECT t_operation_tag(tag_name, tag_value)
BULK COLLECT INTO p_op_tags
FROM op_tags_table
WHERE some_condition;
...
END;
/

Related

PL/SQL Record populate

I have a record as following and I want to populate this with a for loop.
declare
type ch_type is table of record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab;
begin
for i in (select * from emp) loop
rec_typr.id := i.emp_id;
rec_typr.name := i.first_name;
end loop;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
but I get the error:
PLS:0302 component first must be declared.
Can you help me with this?
Two things are problematic in your code.
1) type ch_type is table of record is syntactically incorrect. You must first declare a record and then define its collection type.
2) Using implicit cursor loop is not an efficient method to load a collection and definitely can't be done the way you're trying to do. Use much simpler BULK COLLECT method instead.
declare
type ch_type is record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab;
begin
select emp_id,first_name bulk collect into
rec_typr from emp;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
/
Output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PL/SQL procedure successfully completed.
EDIT
I need to populate the record through a loop not through bulk collect.
Is it any way?
Yes, there is. But, it is less efficient than the method described above.
declare
type ch_type is record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab := ch_type_tab();
i INTEGER := 1;
begin
rec_typr.extend;
for rec in
(
select emp_id,first_name bulk collect into
rec_typr from emp
)
loop
rec_typr(i).id := rec.emp_id;
rec_typr(i).name := rec.first_name;
rec_typr.extend;
i := i + 1;
end loop;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
/

PLSQL : FORALL insert when data is NOT from the type

I want to use FORALL to insert data into a table. But, in my below code I will not be able to
get l_final_amt and l_reference_number variables outside the FOR loop of l_tbl_table_test_retrieve.
How to use FORALL to insert data into a table when values are not in the given type?
CREATE OR REPLACE PACKAGE test_FORALL AS
PROCEDURE pr_test_FORALL;
END test_FORALL;
CREATE OR REPLACE PACKAGE BODY test_FORALL AS
PROCEDURE pr_test_FORALL IS
TYPE ty_tbl_table_test IS TABLE OF table_test%ROWTYPE INDEX BY BINARY_INTEGER;
l_tbl_table_test_retrieve ty_tbl_table_test;
l_tbl_table_test ty_tbl_table_test;
l_final_amt INTEGER;
l_reference_number VARCHAR2(100);
BEGIN
SELECT * BULK COLLECT
INTO l_tbl_table_test_retrieve
FROM table_test t1;
FOR i IN 1 .. l_tbl_table_test_retrieve.COUNT
LOOP
l_tbl_table_test(l_tbl_table_test.COUNT + 1) := l_tbl_table_test_retrieve(i);
l_final_amt := l_final_amt + 10;
l_reference_number := SYSDATE + l_tbl_table_test_retrieve(i).ID;
insert into some_other_table(fname, address,final_amt,ref_number)
values(l_tbl_table_test_retrieve(i).fname, l_tbl_table_test_retrieve(i).address,l_final_amt,l_reference_number);
END LOOP;
--I want to insert into some_other_table using FORALL. But,l_final_amt and l_reference_number variables
-- are not available in l_tbl_table_test_retrieve.
EXCEPTION
DBMS_OUTPUT.put_line('EXCEPTION occurred');
END;
END pr_test_FORALL;
END test_FORALL;
Use a cursor and add the fields into the rows returned by the cursor:
PROCEDURE pr_test_FORALL IS
DECLARE csrData AS CURSOR FOR
SELECT t1.*,
NULL AS COUNT_VAL,
NULL AS FINAL_AMT,
NULL AS REFERENCE_NUMBER
FROM TABLE_TEST t1;
TYPE ty_tbl_table_test IS
TABLE OF csrData%ROWTYPE -- Note: csrData%ROWTYPE
INDEX BY BINARY_INTEGER;
l_tbl ty_tbl_table_test;
l_final_amt INTEGER := 0;
l_reference_number VARCHAR2(100);
BEGIN
OPEN csrData
FETCH csrData
BULK COLLECT INTO l_tbl;
CLOSE csrData;
FOR i IN 1 .. l_tbl.COUNT LOOP
l_final_amt := l_final_amt + 10;
l_tbl(i).FINAL_AMT := l_final_amt;
l_tbl(i).REFERENCE_NUMBER := SYSDATE + l_tbl(i).ID;
END LOOP;
FORALL i IN l_tbl.FIRST..l_tbl.LAST
INSERT INTO SOME_OTHER_TABLE
(FNAME, ADDRESS, FINAL_AMT, REF_NUMBER)
VALUES
(l_tbl(i).FNAME,
l_tbl(i).ADDRESS,
l_tbl(i).FINAL_AMT,
l_tbl(i).REFERENCE_NUMBER);
EXCEPTION
DBMS_OUTPUT.put_line('EXCEPTION occurred');
END pr_test_FORALL;
You could convert the whole thing into two inserts of the below form into the required tables.
I see that in your code l_reference_number is defined as a VARCHAR2 variable but it sounds like a number. ( SYSDATE + some_number ) will yield a date type. It will be implicitly converted into a string based on your NLS_ settings when you assign it to a varchar2. I'm not sure what do you want to store in there as a "REFERENCE_NUMBER".
INSERT INTO some_other_table (
fname,
address,
final_amt,
ref_number
)
SELECT fname,
address,
10 * ROWNUM AS final_amt,
SYSDATE + id as reference_number
FROM table_test;

How to pass array as input parameter in Oracle Function

I have a function to BULK insert data using FORALL.
create or replace type l_array_tab as table of number;
create or replace FUNCTION fn_insert_using_array(
L_TAB VARCHAR2,
L_COL_NAME VARCHAR2,
L_ARRAY L_ARRAY_TAB)
RETURN NUMBER
AS
SQL_STMT VARCHAR2(32767);
sql_count NUMBER;
BEGIN
FORALL i IN L_ARRAY.first .. L_ARRAY.LAST
EXECUTE immediate 'INSERT INTO my_table
Select * from '||L_TAB
||' where '||L_COL_NAME||' := :1' using L_ARRAY(i);
sql_count:= SQL%ROWCOUNT;
RETURN SQL_COUNT;
end;
I need to call this function from another stored procedure or plsql block in this example. While calling this function, I am getting error as wrong number or type of inputs.
This is how I am calling the function:
create or replace type l_array_orig_tab as table of number;
Declare
l_array_orig l_array_orig_tab :=l_array_orig_tab();
l_tab varchar2(30): ='my_tab_orig';
l_col_name varchar2(30) :='insert_id';
V_COUNT NUMBER;
cursor c1 is select * from my_tab_orig;
begin
open c1;
LOOP
FETCH c1 BULK COLLECT INTO l_array_orig limit 1000;
EXIT WHEN L_ARRAY_orig.COUNT =0;
V_COUNT:= fn_insert_using_array(L_TAB, L_COL_NAME,l_array_orig);
END LOOP;
END ;
Please suggest how to call the function.
I am getting error as wrong number or type of inputs
You are getting the error because l_array_orig_tab is a different type from l_array_tab. It doesn't matter that they have the same structure, as far as Oracle knows they are different types. Oracle is a database engine and it strongly enforces type safety. There is no duck typing here.
So the simplest solution is to use the correct type when calling the function:
Declare
l_array_orig l_array_tab :=l_array_tab(); -- change this declaration
l_tab varchar2(30): ='my_tab_orig';
l_col_name varchar2(30) :='insert_id';
V_COUNT NUMBER;
cursor c1 is select * from my_tab_orig;
begin
open c1;
LOOP
FETCH c1 BULK COLLECT INTO l_array_orig limit 1000;
EXIT WHEN L_ARRAY_orig.COUNT =0;
V_COUNT:= fn_insert_using_array(L_TAB, L_COL_NAME,l_array_orig);
END LOOP;
END ;
"The function fn_insert_using_array is in a different schema and also the Type."
So the schema which owns the function has granted you EXECUTE privilege on the function. But they also need to grant you EXECUTE on the type. This is their responsibility: they defined the function with a UDT in its signature so they have to give you all the privileges necessary to call it.
I don't don't whether this is a toy example just for posting on SO, but if it isn't there is no need to create a type like this. Instead use the documented Oracle built-in table of numbers, sys.odcinumberlist.
Is l_array_orig_tab != l_array_tab
you have to use the same type or do the cast between type.
Declare
l_array_orig l_array_orig_tab;
new_array l_array_tab;
l_tab varchar2(30): ='my_tab_orig';
l_col_name varchar2(30) :='insert_id';
V_COUNT NUMBER;
cursor c1 is select * from my_tab_orig;
begin
open c1;
LOOP
FETCH c1 BULK COLLECT INTO l_array_orig limit 1000;
select cast( l_array_orig as l_array_tab) into new_array from dual;
EXIT WHEN L_ARRAY_orig.COUNT =0;
V_COUNT:= fn_insert_using_array(L_TAB, L_COL_NAME,new_array);
END LOOP;
END ;
How cast works.
select cast( variable as destination_type) into var_destination_type from dual

Oracle table type to nested table cast error

I declared table type and set a value in it with using loop. I am having an error while I was casting this t_table
DECLARE
TYPE t_row IS RECORD
(
id NUMBER,
description VARCHAR2(50)
);
TYPE t_table IS TABLE OF t_row;
l_tab t_table := t_table();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.extend();
l_tab(l_tab.last).id := i;
l_tab(l_tab.last).description := 'Description for ' || i;
END LOOP;
SELECT * from TABLE(CAST(l_tab AS t_table));
END
Best regards
Why do you want to do a select onto the the type? You would use the the TABLE() and the CAST rather if you have a collection in a column stored in a table.
You could just loop through the table in your code. Example:
for i in l_tab.first .. l_tab.last
loop
dbms_output.put_line(l_tab(i).id||' '||l_tab(i).description);
end loop;
Since l_tab is of type t_table, there's no need for the cast. But that's not your problem.
Your problem is that you're trying to reference a PL/SQL type in SQL, which you simply can't do. You can either remove the select as #hol suggested or make the type a database object (which will allow SQL to access it):
CREATE OR REPLACE TYPE t_row AS OBJECT
(
id NUMBER,
description VARCHAR2 (50)
);
CREATE OR REPLACE TYPE t_table AS TABLE OF t_row;
DECLARE
l_tab t_table := t_table ();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.EXTEND ();
l_tab (l_tab.LAST) := t_row (i, 'Description for ' || i);
END LOOP;
FOR r IN (SELECT * FROM TABLE (l_tab)) LOOP
DBMS_OUTPUT.put_line (r.id);
END LOOP;
END;
There is a second problem with the initial code, in that you are running a select without telling the code what to do with it. Unlike some other procedural SQL extensions, PL/SQL does not allow you to implicitly return a handle to a resultset (prior to 12c). You must either handle it directly or explicitly return a ref_cursor that points to it. The code above has been update to primitively handle the result of the query.

Inserting a RefCursor into a table

I have a simple function where I run a stored procedure that returns a RefCursor and I try to use that RefCursor to insert data to a temporary table. I get the following error when trying to do so:
SQL Error: ORA-00947: not enough values
I know for a fact that the refcursor returns exactly the same number of values as the temporary table has, correct column names, their order and their type. I ran print RefCursor and I can see all of the data. Here's the code:
var r refcursor;
EXEC SCHEMA.PACKAGE.SPROC(:r);
insert into SCHEMA.TEMP_TABLE
values
(r);
I have to add that the stored procedure has a refcursor defined as a OUT parameter so it returns a correct type. Using print r; prints the correct data.
What am I doing wrong?
EDIT:
Based on a suggestion I tried to use a fetch to a rowtype variable, but getting Invalid Number exception whenever I attempt to fetch a row:
DECLARE
cur SYS_refcursor;
rec SCHEMA.TEMP_TABLE%rowtype;
begin
SCHEMA.PACKAGE.SPROC( cur );
LOOP
FETCH cur INTO rec;
EXIT WHEN cur%NOTFOUND;
INSERT INTO SCHEMA.TEMP_TABLE
VALUES rec;
END LOOP;
EXCEPTION
WHEN INVALID_NUMBER THEN
DBMS_output.put_line(rec.move_id);
end;
I added the exception block to see which row is failing and needless to say it is the first one. The stored procedure I run returns a refcursor of a select query from multiple tables. The temporary table defined as the exact copy of the refcursor columns and their types. Not sure what could be causing the exception.
You can't insert into a table from a refcursor. You could write a procedure that fetches from the cursor and inserts into the table. If schema.package.sproc returns a ref cursor of temp_table%rowtype, you could do something like
DECLARE
cur sys_refcursor;
rec schema.temp_table%rowtype;
BEGIN
schema.package.sproc( cur );
LOOP
FETCH cur INTO rec;
EXIT WHEN cur%NOTFOUND;
INSERT INTO schema.temp_table
VALUES rec;
END LOOP;
END;
You can use LOOP + FETCH to filter the row in SYS_REFCURSOR.
Exit the LOOP using "EXIT WHEN ref_%notfound;"
Example: Have 2 functions.
FUNCTION get_data
RETURN SYS_REFCURSOR
is
s varchar2(2000);
ref_ SYS_REFCURSOR;
begin
s := 'select username, password, email from user_info where id < 100';
OPEN ref_ FOR s;
return ref_;
end;
FUNCTION load_data_in_table
RETURN varchar2
is
s varchar2(2000);
puser_name varchar2(2000);
ppassword varchar2(2000);
pemail varchar2(2000);
ref_ SYS_REFCURSOR;
begin
ref_ := get_data();
LOOP
--process_record_statements;
FETCH ref_ into puser_name, ppassword, pemail;
s := 'INSERT INTO TEST_USER_EXP VALUES(:user_name, :password, :email)';
EXECUTE IMMEDIATE s USING puser_name, ppassword, pemail;
EXIT WHEN ref_%notfound;
END LOOP;
commit;
return 'OK';
end;

Resources