Struggling with Oracle Function with parameter (Reference to uninitialized collection) - oracle

I can't find what is wrong here, finally I decided to ask here for help.
This is how I am invoking:
DECLARE
RESSULT VARCHAR2(1000 CHAR);
V_TEST1 CARS_TABLE:=CARS_TABLE();
idx pls_integer :=0;
BEGIN
V_TEST1.EXTEND(2);
IDX := V_TEST1.COUNT; -- here there is NO problem
RESSULT:=MYFUNCTION(1, V_TEST1);
END;
This is what I am invoking..
CREATE OR REPLACE
FUNCTION MYFUNCTION(
CAR_ID IN INTEGER,
MYOUTTABLE OUT CARS_TABLE)
RETURN VARCHAR2
IS
RESSULT VARCHAR2(1000 CHAR);
idx pls_integer :=0;
BEGIN
IDX := MYOUTTABLE.COUNT; - HERE IS PROBLEM.. but why - I pass initialized collection
RETURN 'return param';
END MYFUNCTION;
CREATE OR REPLACE TYPE carElement
IS
OBJECT
(
kind VARCHAR2(60),
created DATE );
CREATE OR REPLACE TYPE CARS_TABLE
AS
TABLE OF carElement;
00000 - "Reference to uninitialized collection"
*Cause: An element or member function of a nested table or varray
was referenced (where an initialized collection is needed)
without the collection having been initialized.
*Action: Initialize the collection with an appropriate constructor
or whole-object assignment.

"IDX := MYOUTTABLE.COUNT; - HERE IS PROBLEM.. but why - I pass initialized collection"
you're resetting the collection to NULL on the input to the function call as you've defined the parameter as OUT which automatically nulls out the parameter as it calls the function (documentation link).
MYOUTTABLE OUT CARS_TABLE
if you want to preserve the input, then you need to pass it as IN or if you want to preserve teh input and also manipulate the object in the function then use IN OUT.
eg note with the first call it shows its null if i use out:
SQL> declare
2 v_test1 cars_table:=cars_table();
3
4 function myfunction(
5 car_id in integer,
6 myouttable &mode cars_table)
7 return varchar2
8 is
9 ressult varchar2(1000 char);
10 idx pls_integer :=0;
11 begin
12 if (myouttable is null)
13 then
14 dbms_output.put_line('ARRAY IS NULL');
15 else
16 idx := myouttable.count;
17 end if;
18 return 'return param';
19 end myfunction;
20 begin
21
22 v_test1.extend(2);
23 dbms_output.put_line(myfunction(1, v_test1));
24
25 end;
26 /
Enter value for mode: OUT
old 6: myouttable &mode cars_table)
new 6: myouttable OUT cars_table)
ARRAY IS NULL
return param
PL/SQL procedure successfully completed.
SQL> /
Enter value for mode: IN OUT
old 6: myouttable &mode cars_table)
new 6: myouttable IN OUT cars_table)
return param
PL/SQL procedure successfully completed.

Related

PL/SQL Table Function - How to return an empty result set?

I'm trying to create a PL/SQL table function. I don't mind if it is PIPELINED or not. I just want it to return a query-able result set.
And I want to start with an empty result set. (Because it is possible that the result set I intend to construct will be empty.)
Is it possible to create a PL/SQL table function that returns zero rows?
In the books I have, and tutorials I can find, I only see examples that must return at least one record.
Example of the problem:
CREATE OR REPLACE PACKAGE z_util AS
TYPE t_row
IS RECORD (
CATEGORY VARCHAR2( 128 CHAR )
, MEASURE NUMBER
);
TYPE t_tab
IS TABLE OF t_row;
FUNCTION f_test
RETURN t_tab;
END z_util;
/
CREATE OR REPLACE PACKAGE BODY z_util AS
FUNCTION f_test
RETURN t_tab IS
retval t_tab;
BEGIN
RETURN retval;
END;
END z_util;
/
SELECT test.*
FROM TABLE ( z_util.f_test ) test;
Output:
Error starting at line : 24 in command -
SELECT test.*
FROM TABLE ( z_util.f_test ) test
Error at Command Line : 25 Column : 14
Error report -
SQL Error: ORA-00902: invalid datatype
00902. 00000 - "invalid datatype"
*Cause:
*Action:
Something like this?
SQL> CREATE TYPE t_row AS OBJECT (id NUMBER, name VARCHAR2 (50));
2 /
Type created.
SQL> CREATE TYPE t_tab IS TABLE OF t_row;
2 /
Type created.
SQL> CREATE OR REPLACE FUNCTION f_test
2 RETURN t_tab
3 AS
4 retval t_tab;
5 BEGIN
6 RETURN retval;
7 END;
8 /
Function created.
SQL> SELECT f_test FROM DUAL;
F_TEST(ID, NAME)
--------------------------------------------------------------------
SQL>
Saying that this is too simple and that it doesn't work while in package:
SQL> CREATE OR REPLACE PACKAGE pkg_test
2 AS
3 FUNCTION f_test
4 RETURN t_tab;
5 END;
6 /
Package created.
SQL> CREATE OR REPLACE PACKAGE BODY pkg_test
2 AS
3 FUNCTION f_test
4 RETURN t_tab
5 AS
6 retval t_tab;
7 BEGIN
8 RETURN retval;
9 END f_test;
10 END pkg_test;
11 /
Package body created.
SQL> select pkg_test.f_test from dual;
F_TEST(ID, NAME)
-------------------------------------------------------
SQL>
Works OK as well. Did you, by any chance, declare type within the package? If so, try to create it at SQL level.
See 6.4.6 Querying a Collection:
Note: In SQL contexts, you cannot use a function whose return type was declared in a package specification.
You cannot unnest the result of function, but the variable with the same data type:
create or replace package utils as
type t_row is record (category varchar2 (8), measure number);
type t_tab is table of t_row;
function passon (t t_tab:=null) return t_tab;
end utils;
/
create or replace package body utils as
function passon (t t_tab:=null) return t_tab is
begin
return t;
end;
end utils;
/
Usage and outcomes:
var rc refcursor
declare
tab1 utils.t_tab := utils.passon (); -- empty
tab2 utils.t_tab := utils.passon (utils.t_tab (utils.t_row ('category', 50)));
begin
open :rc for
select * from table (tab1) union all
select * from table (tab2);
end;
/
CATEGORY MEASURE
-------- ----------
category 50
jsut use a return statment without any parameters.
I have already one table function that splits a string based in token.
I modified the code for you as an example answet for your quetion.
If you pass the first string null, the function will return no rows, otherwise will return each token in separate row based on the second parameter offcurse.
// create needed types
// row object
CREATE OR REPLACE TYPE T_TOKEN_ROW AS OBJECT (
id NUMBER,
token_text VARCHAR2(50)
);
// table object
CREATE OR REPLACE TYPE T_TOKEN_TAB IS TABLE OF t_token_row;
// create table function to toknize a string
// input : P_string : the string to be toknized
// P_separator : a character to separate tokens
// Outputs : each token in separate record with id field
CREATE OR REPLACE FUNCTION PIPE_tokens (P_string varchar2,P_separator char) RETURN t_token_tab PIPELINED
AS
sLine VARCHAR2(2000);
nPos INTEGER;
nPosOld INTEGER;
nIndex INTEGER;
nLength INTEGER;
nCnt INTEGER;
sToken VARCHAR2(200);
BEGIN
if (P_string is null ) then
return ;
else
sLine := P_string;
IF (SUBSTR(sLine, LENGTH(sLine), 1) <> '|') THEN
sLine := sLine || '|';
END IF;
nPos := 0;
sToken := '';
nLength := LENGTH(sLine);
nCnt := 0;
FOR nIndex IN 1..nLength LOOP
IF ((SUBSTR(sLine, nIndex, 1) = P_separator) OR (nIndex = nLength)) THEN
nPosOld := nPos;
nPos := nIndex;
nCnt := nCnt + 1;
sToken := SUBSTR(sLine, nPosOld + 1, nPos - nPosOld - 1);
PIPE ROW(t_token_row(nCnt,sToken));
--tTokenTab(nCnt) := sToken;
END IF;
END LOOP;
RETURN;
end if;
END;
// 2 Test query
select * from table(PIPE_tokens(null,';')) ;
select * from table(PIPE_tokens('5;2;3',';')) ;

Oracle: Creating an array of object type in package

I want to implement a stack data structure in 10gR2.
However, when I try to do:
CREATE or replace TYPE ContainerArray AS VARRAY(25) OF XDB.DBMS_XMLDOM.DOMNODE ;
I get
"schema-level type has illegal reference to XDB.DBMS_XMLDOM."
So I've figured out that's because Oracle will not let you create a schema-level type which references a package-level type.
I can fix that problem by creating the type inside of the package:
TYPE ContainerArray AS VARRAY(25) OF XDB.DBMS_XMLDOM.DOMNODE;
However, then when I try to create my object, I can't reference the package-level type:
CREATE TYPE DOMNode_Stack AS OBJECT (
max_size INTEGER,
top INTEGER,
position ContainerArray,
MEMBER PROCEDURE initialize,
MEMBER FUNCTION full RETURN BOOLEAN,
MEMBER FUNCTION empty RETURN BOOLEAN,
MEMBER PROCEDURE push (n IN INTEGER),
MEMBER PROCEDURE pop (n OUT INTEGER)
);
and I can't create the object type inside of the package because I get
"Object not supported in this context."
Any thoughts on how I can solve this issue?
sql level objects like an OBJECT can only reference SQL types in their attributes.
if you see the package spec:
TYPE DOMNode IS RECORD (id RAW(13));
so you could create an sql array of
SQL> CREATE or replace TYPE ContainerArray AS VARRAY(25) OF raw(13);
2 /
Type created.
then in your object put
position ContainerArray
which will work. when you reference it, just assign a local variable
v_dom xml_dom.domnode;
begin
v_dom.id := your array index value that you're dealing with at the time;
be advised that on any Oracle upgrade you need to verify that the structure of DOMNODE hasn't changed.
edit: here's a sample:
SQL> set serverout on
SQL> create or replace type containerarray as varray(25) of raw(13);
2 /
Type created.
SQL> create type domnode_stack as object (
2 position containerarray,
3 member procedure initialize,
4 member procedure print_node_names
5 );
6 /
Type created.
SQL> CREATE TYPE body DOMNode_Stack
2 as
3 member procedure initialize
4 is
5 v_dom dbms_xmldom.DOMNode;
6 v_doc dbms_xmldom.domdocument;
7 v_nl dbms_xmldom.DOMNodeList;
8 begin
9 position := containerarray();
10 v_doc := dbms_xmldom.newDOMDocument(xmltype('<root><foo>test</foo><foo2>test2</foo2></root>')); -- just some summy data.
11 v_nl := dbms_xmldom.getElementsByTagName(v_doc, '*');
12 for idx in 0..dbms_xmldom.getLength(v_nl)-1 loop
13 v_dom := DBMS_XMLDOM.item(v_nl, idx);
14 position.extend;
15 position(position.last) := v_dom.id;
16 end loop;
17 end;
18
19 member procedure print_node_names
20 is
21 v_dom dbms_xmldom.DOMNode;
22 begin
23 for idx in 1..position.count
24 loop
25 v_dom.id := position(idx);
26 dbms_output.put_line(dbms_xmldom.getnodename(v_dom)||'='||dbms_xmldom.getnodevalue(dbms_xmldom.getfirstchild(v_dom)));
27 end loop;
28 end;
29 end;
30 /
Type body created.
SQL> show errors type body DOMNode_Stack
No errors.
SQL> declare
2 o_domnode DOMNode_Stack := DOMNode_Stack(null);
3 begin
4 o_domnode.initialize();
5 o_domnode.print_node_names();
6 end;
7 /
root=
foo=test
foo2=test2
PL/SQL procedure successfully completed.
the important bit being:
to assign from DBMS_XMLDOM to the sql type we take v_dom.id.
13 v_dom := DBMS_XMLDOM.item(v_nl, idx);
14 position.extend;
15 position(position.last) := v_dom.id;
and when reversing:
23 for idx in 1..position.count
24 loop
25 v_dom.id := position(idx);
ie assign the ID part (not the record itself..that would error out) back.

Why is this check for null associative array in PL/SQL failing?

I have an associative array created by a type of rowtype of a table column.
To give an example, this is how it is(the table names are different, but the structure is the same):
This is the DDL of the table
CREATE TABLE employees
(
id NUMBER,
name VARCHAR2(240),
salary NUMBER
);
Here's what my procedure is doing:
DECLARE
TYPE table_of_emp
IS TABLE OF employees%ROWTYPE INDEX BY BINARY_INTEGER;
emp TABLE_OF_EMP;
BEGIN
IF emp IS NULL THEN
dbms_output.Put_line('Null associative array');
ELSE
dbms_output.Put_line('Not null');
END IF;
END;
I assume this should result in "Null associative array" being printed. However, the if condition fails and the execution jumps to the else part.
Now if I put in a for loop to print the collection values
DECLARE
TYPE table_of_emp
IS TABLE OF employees%ROWTYPE INDEX BY BINARY_INTEGER;
emp TABLE_OF_EMP;
BEGIN
IF emp IS NULL THEN
dbms_output.Put_line('Null associative array');
ELSE
dbms_output.Put_line('Not null');
FOR i IN emp.first..emp.last LOOP
dbms_output.Put_line('Emp name: '
|| Emp(i).name);
END LOOP;
END IF;
END;
then the program unit raises an exception, referencing the for loop line
ORA-06502: PL/SQL: Numeric or value error
which I presume is because of the null associative array. Is the error being raised because of null associative array?
So why is the first check failing then? What am I doing wrong?
The database server is Oracle 11g EE (version 11.2.0.3.0 64 bit)
I assume this should result in "Null associative array" being printed. That assumption is wrong for associative arrays. They exist when declared, but are empty. It would be correct for other types of PL/SQL collections:
Until you initialize it, a nested table or varray is atomically null;
the collection itself is null, not its elements. To initialize a
nested table or varray, you use a constructor, a system-defined
function with the same name as the collection type. This function
constructs collections from the elements passed to it.
You must explicitly call a constructor for each varray and nested
table variable. Associative arrays, the third kind of collection, do
not use constructors. Constructor calls are allowed wherever function
calls are allowed. Initializing and Referencing Collections
Compare:
SQL> declare
2 type varchar2_100_aa is table of varchar2(100) index by binary_integer;
3 test varchar2_100_aa;
4 begin
5 test(1) := 'Hello';
6 dbms_output.put_line(test(1));
7 end;
8 /
Hello
PL/SQL procedure successfully completed.
SQL> declare
2 type varchar2_100_va is varray(100) of varchar2(100);
3 test varchar2_100_va;
4 begin
5 test(1) := 'Hello';
6 dbms_output.put_line(test(1));
7 end;
8 /
declare
*
ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at line 5
Variable array done correctly:
SQL> declare
2 type varchar2_100_va is varray(10) of varchar2(100);
3 test varchar2_100_va;
4 begin
5 test := varchar2_100_va(); -- not needed on associative array
6 test.extend; -- not needed on associative array
7 test(1) := 'Hello';
8 dbms_output.put_line(test(1));
9 end;
10 /
Hello
PL/SQL procedure successfully completed.
Because the associative array is empty first and last are null, which is why your second example results in ORA-06502: PL/SQL: Numeric or value error:
SQL> declare
2 type varchar2_100_aa is table of varchar2(100) index by binary_integer;
3 test varchar2_100_aa;
4 begin
5 dbms_output.put_line(test.count);
6 dbms_output.put_line(coalesce(to_char(test.first), 'NULL'));
7 dbms_output.put_line(coalesce(to_char(test.last), 'NULL'));
8 test(1) := 'Hello';
9 dbms_output.new_line;
10 dbms_output.put_line(test.count);
11 dbms_output.put_line(coalesce(to_char(test.first), 'NULL'));
12 dbms_output.put_line(coalesce(to_char(test.last), 'NULL'));
13 end;
14 /
0
NULL
NULL
1
1
1
PL/SQL procedure successfully completed.
EDIT Also note that associative arrays can be sparse. Looping over the numbers between first and last will raise an exception for any collection that is sparse. Instead use first and next like so: (Last and prev to loop the other direction.)
SQL> declare
2 type varchar2_100_aa is table of varchar2(100) index by binary_integer;
3 test varchar2_100_aa;
4 i binary_integer;
5 begin
6 test(1) := 'Hello';
7 test(100) := 'Good bye';
8 dbms_output.put_line(test.count);
9 dbms_output.put_line(coalesce(to_char(test.first), 'NULL'));
10 dbms_output.put_line(coalesce(to_char(test.last), 'NULL'));
11 dbms_output.new_line;
12 --
13 i := test.first;
14 while (i is not null) loop
15 dbms_output.put_line(to_char(i, '999') || ' - ' || test(i));
16 i := test.next(i);
17 end loop;
18 end;
19 /
2
1
100
1 - Hello
100 - Good bye
PL/SQL procedure successfully completed.
I'm not going to answer why the first check is failing. I've never thought of doing anything like that and am quite surprised that it doesn't raise an error.
The reason why you're getting an exception raised on the loop is, as you've noted, that the index emp.first does not exist.
Rather than checking for nulls, you should really be checking for the existence of this index. Which you can do be using the .exists(i) syntax:
if not emp.exists(emp.first) then
dbms_output.put_line('Nothing in here.');
end if;

pl/sql object types "ORA-06530: Reference to uninitialized composite" error

i have a type as follows:
CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
item_id NUMBER,
system_event_cd VARCHAR2 (20),
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
);
CREATE OR REPLACE TYPE BODY tbusiness_inter_item_bag
AS
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
AS
BEGIN
RETURN;
END;
END;
when i execute the following script, i got a "Reference to uninitialized composite" error, which is imho quite suitable.
DECLARE
item tbusiness_inter_item_bag;
BEGIN
item.system_event_cd := 'ABC';
END;
This also raises the same error:
item.item_id := 3;
But if i change my object type into:
CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
item_id NUMBER(1),
system_event_cd VARCHAR2 (20),
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
);
then the last statement raises no more error (where my "item" is still uninitialized):
item.item_id := 3;
Shouldn't i get the same ORA-06530 error?
ps: Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi
I reproduced the same behaviour in Oracle 11gR1. I would agree with you, this seems like a bug to me too, albeit a trivial one.
SQL> CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
2 item_id NUMBER(1),
3 system_event_cd VARCHAR2 (20),
4 CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
5 );
6 /
Type created.
SQL> DECLARE
2 item tbusiness_inter_item_bag;
3 BEGIN
4 item.item_id := 1;
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>
Note that this still fails:
SQL> DECLARE
2 item tbusiness_inter_item_bag;
3 BEGIN
4 item.item_id := 1;
5 item.system_event_cd := 'ABC';
6 END;
7 /
DECLARE
*
ERROR at line 1:
ORA-06530: Reference to uninitialized composite
ORA-06512: at line 5
SQL>
Obviously, the correct practice is always initialize objects before referencing them.
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 BEGIN
4 item.system_event_cd := 'ABC';
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>
You need to call the constructor you defined:
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 /* ^^ call the constructor */
4 BEGIN
5 item.system_event_cd := 'ABC';
6 END;
7 /
PL/SQL procedure successfully completed
I observe the behaviour you described on a 10.2.0.3 database. I wouldn't rely on it though, it looks like a bug.
I dont think its bug. As per documentation, you are supposed to initialize the composite type. This will always work :
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 BEGIN
4 item.system_event_cd := 'ABC';
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>

Checking if a collection element exists in Oracle

I create a simple type:
create or replace TYPE SIMPLE_TYPE AS OBJECT (ID NUMBER(38), NAME VARCHAR2(20));
Simple test:
DECLARE
TYPE ObjectList IS TABLE OF SIMPLE_TYPE;
tmp SIMPLE_TYPE := SIMPLE_TYPE(1, 'a');
o ObjectList := new ObjectList(SIMPLE_TYPE(2, 'a'), SIMPLE_TYPE(3, 'a'));
BEGIN
IF tmp.EXISTS(tmp) THEN
dbms_output.put_line('OK, exists.');
END IF;
END;
I get an exception: PLS-00302: component 'EXISTS' must be declared
But this example work:
DECLARE
TYPE NumList IS TABLE OF INTEGER;
n NumList := NumList(1,3,5,7);
BEGIN
n.DELETE(2);
IF n.EXISTS(1) THEN
dbms_output.put_line('OK, element #1 exists.');
END IF;
IF n.EXISTS(3) = FALSE THEN
dbms_output.put_line('OK, element #2 has been deleted.');
END IF;
IF n.EXISTS(99) = FALSE THEN
dbms_output.put_line('OK, element #99 does not exist at all.');
END IF;
END;
Is it possible to implement EXISTS method in SIMPLE_TYPE type?
As the documentation states, EXISTS() tests for the existence of a numbered entry in a collection. That is, array.exists(3) asserts that the third element of array is populated.
What you are trying to do in your first example is test whether the instance tmp matches an element in ObjectList. From 10g onwards we can do this using the MEMBER OF syntax. Unfortunately, in order to make that work we have to declare a MAP method, which is rather clunky and would get rather annoying if the object has a lot of attributes.
SQL> create or replace type simple_type as object
2 ( id number
3 , name varchar2(30)
4 , map member function compare return varchar2);
5 /
Type created.
SQL>
SQL> create or replace type body simple_type as
2 map member function compare return varchar2
3 is
4 return_value integer;
5 begin
6 return to_char(id, '0000000')||name;
7 end compare;
8 end;
9 /
Type body created.
SQL>
Running the example...
SQL> set serveroutput on size unlimited
SQL>
SQL> declare
2 type objectlist is table of simple_type;
3 tmp simple_type := simple_type(1, 'a');
4 o objectlist := new objectlist(simple_type(2, 'a'), simple_type(3, 'a'));
5 begin
6 if tmp MEMBER OF o then
7 dbms_output.put_line('ok, exists.');
8 else
9 dbms_output.put_line('search me');
10 end if;
11 end;
12 /
search me
PL/SQL procedure successfully completed.
SQL>
tmp SIMPLE_TYPEE := SIMPLE_TYPE(1, 'a');
…
IF tmp.EXISTS(tmp) THEN
You declare tmp as SIMPLE_TYPE, not ObjectList.
SIMPLE_TYPE is scalar type, not a collection.
Probably you wanted to check o.EXISTS instead (which is an ObjectList)?
Update:
EXISTS when applied to a collection takes an integer index as an argument and checks if the element with this index exists (not its value).
To check that SIMPLE_TYPE(1, 'a') exists in your table, you should so the following:
Create ObjectList in a dictionary:
CREATE TYPE ObjectList IS TABLE OF SIMPLE_TYPE;
Issue the SELECT query:
DECLARE
tmp SIMPLE_TYPE := SIMPLE_TYPE(1, 'a');
o ObjectList := new ObjectList(SIMPLE_TYPE(2, 'a'), SIMPLE_TYPE(3, 'a'));
myid INT;
BEGIN
SELECT 1
INTO myid
FROM TABLE(o) q
WHERE SIMPLE_TYPE(q.id, q.name) = tmp
AND rownum = 1;
IF (myid = 1) THEN
dbms_output.put_line('OK, exists.');
END IF;
END;

Resources