Oracle table type to nested table cast error - oracle

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.

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;
/

How to access and query objects passed as parameter to a procedure while converting from Oracle to postgresql

I have a procedure in Oracle that I need to convert to Postgresql and need help on it. It paases a collection of objects in a procedure.The procedure then checks if each object is present in a database table or not and if present it gives a message that , that specific element is found/present. if some element that is paassed to the procedure is not present in the table, the procedure just doesnt do anything. I have to write equivalent of that in postgresql. I think the heart of the issue is this statement:
SELECT COUNT (*)
INTO v_cnt
FROM **TABLE (p_cust_tab_type_i)** pt
WHERE pt.ssn = cc.ssn;
In Oracle a collection can be treated as a table and one can query it but I dont know how to do that in postgresql. The code to create the table, add data, create the procedure, call the procedure by passing the collection (3 objects) and output of that is posted below. Can someone suggest how this can be done in postgresql?
Following the oracle related code and details:
--create table
create table temp_n_tab1
(ssn number,
fname varchar2(20),
lname varchar2(20),
items varchar2(100));
/
--add data
insert into temp_n_tab1 values (1,'f1','l1','i1');
--SKIP no. ssn no. 2 intentionally..
insert into temp_n_tab1 values (3,'f3','l3','i3');
insert into temp_n_tab1 values (4,'f4','l4','i4');
insert into temp_n_tab1 values (5,'f5','l5','i5');
insert into temp_n_tab1 values (6,'f6','l6','i6');
commit;
--create procedure
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE temp_n_proc (
p_cust_tab_type_i IN temp_n_customer_tab_type)
IS
t_cust_tab_type_i temp_n_customer_tab_type;
v_cnt NUMBER;
v_ssn temp_n_tab1.ssn%TYPE;
CURSOR c
IS
SELECT ssn
FROM temp_n_tab1
ORDER BY 1;
BEGIN
--t_cust_tab_type_i := p_cust_tab_type_i();
FOR cc IN c
LOOP
SELECT COUNT (*)
INTO v_cnt
FROM TABLE (p_cust_tab_type_i) pt
WHERE pt.ssn = cc.ssn;
IF (v_cnt > 0)
THEN
DBMS_OUTPUT.put_line (
'The array element '
|| TO_CHAR (cc.ssn)
|| ' exists in the table.');
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM);
END;
/
--caller proc
SET SERVEROUTPUT ON
declare
array temp_n_customer_tab_type := temp_n_customer_tab_type();
begin
for i in 1 .. 3
loop
array.extend;
array(i) := temp_n_cust_header_type( i, 'name ' || i, 'lname ' || i,i*i*i*i );
end loop;
temp_n_proc( array );
end;
/
caller proc output:
The array element 1 exists in the table.
The array element 3 exists in the table.
When you create a table in Postgres, a type with the same name is also created. So you can simply pass an array of the table's type as a parameter to the function.
Inside the function you can then use unnest() to treat the array like a table.
The following is the closest match to your original Oracle code:
create function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns void
as
$$
declare
l_rec record;
l_msg text;
l_count integer;
BEGIN
for l_rec in select t1.ssn
from temp_n_tab1 t1
loop
select count(*)
into l_count
from unnest(p_cust_tab_type_i) as t
where t.ssn = l_rec.ssn;
if l_count > 0 then
raise notice 'The array element % exist in the table', l_rec.ssn;
end if;
end loop;
END;
$$
language plpgsql;
The row-by-row processing is not a good idea to begin with (neither in Postgres, nor in Oracle). It would be a lot more efficient to get the existing elements in a single query:
create function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns void
as
$$
declare
l_rec record;
l_msg text;
BEGIN
for l_rec in select t1.ssn
from temp_n_tab1 t1
where t1.ssn in (select t.ssn
from unnest(p_cust_tab_type_i) as t)
loop
raise notice 'The array element % exist in the table', l_rec.ssn;
end loop;
return;
END;
$$
language plpgsql;
You can call the function like this:
select temp_n_proc(array[row(1,'f1','l1','i1'),
row(2,'f2','l2','i2'),
row(3,'f3','l3','i3')
]::temp_n_tab1[]);
However a more "Postgres" like and much more efficient way would be to not use PL/pgSQL for this, but create a simple SQL function that returns the messages as a result:
create or replace function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns table(message text)
as
$$
select format('The array element %s exist in the table', t1.ssn)
from temp_n_tab1 t1
where t1.ssn in (select t.ssn
from unnest(p_cust_tab_type_i) as t)
$$
language sql;
This returns the output of the function as a result rather than using the clumsy raise notice.
You can use it like this:
select *
from temp_n_proc(array[row(1,'f1','l1','i1'),
row(2,'f2','l2','i2'),
row(3,'f3','l3','i3')
]::temp_n_tab1[]);

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

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 use function returning Oracle REF_CURSOR in a procedure

I have to write an Oracle procedure which should invoke an Oracle function returning REF_CURSOR. The function is declared like that
FUNCTION "IMPACTNET"."TF_CONVERTPARA" (PARASTRING IN NVARCHAR2) RETURN SYS_REFCURSOR
AS
c SYS_REFCURSOR;
BEGIN
OPEN c FOR
SELECT SUBSTR(element, 1, INSTR(element, '|') - 1) as key,
SUBSTR(element, INSTR(element, '|') + 1, 99999) as val
FROM (
SELECT REGEXP_SUBSTR(PARASTRING, '[^;]+', 1, LEVEL) element
FROM dual
CONNECT BY LEVEL < LENGTH(REGEXP_REPLACE(PARASTRING, '[^;]+')) + 1
);
RETURN c;
END;
Can you tell me what I need to write in order to invoke the function from within my procedure? I'd like to insert all the returned values (shaped a table with two columns) into a rational table.
Thank you in advance!
Something along the lines of this should work (obviously, I'm guessing about table names and column names and the exact logic that you're trying to implement)
CREATE PROCEDURE some_procedure_name
AS
l_rc SYS_REFCURSOR := impactnet.tf_convertpara( <<some string>> );
l_key VARCHAR2(100);
l_val VARCHAR2(100);
BEGIN
LOOP
FETCH l_rc
INTO l_key, l_val;
EXIT WHEN l_rc%notfound;
INSERT INTO some_table( key_column, val_column )
VALUES( l_key, l_val );
END LOOP;
END;
As Ollie points out, it would be more efficient to do a BULK COLLECT and a FORALL. If you're just dealing with a few thousand rows (since your function is just parsing the data in a delimited string, I'm assuming you expect relatively few rows to be returned), the performance difference is probably minimal. But if you're processing more data, the difference can be quite noticeable. Depending on the Oracle version and your specific requirements, you may be able to simplify the INSERT statement in the FORALL to insert a record rather than listing each column from the record individually.
CREATE PROCEDURE some_procedure_name
AS
TYPE key_val_rec
IS RECORD(
key VARCHAR2(100),
val VARCHAR2(100)
);
TYPE key_val_coll
IS TABLE OF key_val_rec;
l_rc SYS_REFCURSOR := impactnet.tf_convertpara( <<some string>> );
l_coll key_val_coll;
BEGIN
LOOP
FETCH l_rc
BULK COLLECT INTO l_coll
LIMIT 100;
EXIT WHEN l_coll.count = 0;
FORALL i IN l_coll.FIRST .. l_coll.LAST
INSERT INTO some_table( key_column, val_column )
VALUES( l_coll(i).key, l_coll(i).val );
END LOOP;
END;

Resources