Getting the error Reference to uninitialized collection to associative arrays - oracle

I am using associative arrays in a package I am creating and I get the error ORA-06531: Reference to uninitialized collection if I try to get the count (TBL.COUNT) before I have bulk collected into the array.
I have found that I can use EXISTS(1) to check if there is something there instead of the count but if I am not bulk collecting into it, how do I get the index I need to use for the next row?
loc_idx := TBL.COUNT;
OR
TBL(TBL.COUNT+1) := blah;
My thought was that you didn't need to initialize associative arrays unlike Nested tables and varrays
Here is an example of one I am using
TYPE invc_ln_item_type IS TABLE OF invc_ln_item%ROWTYPE;
invc_ln_item_tbl invc_ln_item_type;
used as an input to the following proc
PROCEDURE CREATE_INVC_LN_ITEM(P_LN_ITEM_TYPE_CD IN
invc_ln_item.ln_item_type_cd%TYPE,
P_LN_ITEM_SBTYPE_CD IN
invc_ln_item.ln_item_sbtype_cd%TYPE,
P_INVC_PK IN invc.invc_pk%TYPE,
P_INVC_LN_ITEM_TBL IN OUT
invc_ln_item_type)
IS
loc_inv_ln_item_rec INVC_LN_ITEM%ROWTYPE;
loc_idx NUMBER;
BEGIN
loc_idx := P_INVC_LN_ITEM_TBL.COUNT + 1;
INSERT INTO APP.INVC_LN_ITEM (INVC_LN_ITEM_PK,
INSRT_DT,
INSRT_USER,
LAST_UPDT_DT,
LAST_UPDT_USER,
LN_ITEM_TYPE_CD,
LN_ITEM_SBTYPE_CD,
INVC_PK,
UNITS,
AMT)
VALUES (null,
null,
null,
null,
null,
P_LN_ITEM_TYPE_CD,
P_LN_ITEM_SBTYPE_CD,
P_INVC_PK,
0,
0)
RETURNING INVC_LN_ITEM_PK,
INSRT_DT,
INSRT_USER,
LAST_UPDT_DT,
LAST_UPDT_USER,
LN_ITEM_TYPE_CD,
LN_ITEM_SBTYPE_CD,
INVC_PK,
UNITS,
AMT
INTO loc_inv_ln_item_rec;
P_INVC_LN_ITEM_TBL(loc_idx) := loc_inv_ln_item_rec;
END;
Then gets called like
CREATE_INVC_LN_ITEM(P_BILLG_PRFL_LN_ITEM_REC.ln_item_type_cd,
billg_prfl_ln_item_sbtype_tbl(c).ln_item_sbtype_cd,
invc_rec.invc_pk,
invc_ln_item_tbl);
The errors in the case above occurs at:
loc_idx := P_INVC_LN_ITEM_TBL.COUNT + 1;
[Error] Execution (39: 1): ORA-06531: Reference to uninitialized collection

There is not an incrementing index for an associative array. You do not need to initialise an associative array and using array.COUNT will not raise an exception with an associative array (unlike for collections).
DECLARE
TYPE arraytype IS TABLE OF VARCHAR2(20) INDEX BY VARCHAR2(2);
array arraytype;
idx VARCHAR2(2);
BEGIN
DBMS_OUTPUT.PUT_LINE( array.COUNT ); -- 0
array('A') := 'AAAA';
DBMS_OUTPUT.PUT_LINE( array.COUNT ); -- 1
array(array.COUNT + 1) := 'BBBB';
DBMS_OUTPUT.PUT_LINE( array.COUNT ); -- 2
array('7') := 'DDDD';
DBMS_OUTPUT.PUT_LINE( array.COUNT ); -- 3
idx := array.FIRST;
WHILE idx IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE( idx || ' - ' || array(idx) );
idx := array.NEXT( idx );
END LOOP;
END;
/
If you are expecting to use an incrementing index then you need to consider whether an associative array is the correct type to be using and whether a collection or a VARRAY would be better.
If you are using collections (not associative arrays):
DECLARE
TYPE arraytype IS TABLE OF VARCHAR2(20);
array arraytype;
BEGIN
array := arraytype(); -- Initialise
DBMS_OUTPUT.PUT_LINE( array.COUNT ); -- 0
array.EXTEND; -- Extend by 1 element
array(1) := 'AAAA';
DBMS_OUTPUT.PUT_LINE( array.COUNT ); -- 1
array.EXTEND(1); -- Number of elements to extend by
array(array.COUNT) := 'BBBB';
DBMS_OUTPUT.PUT_LINE( array.COUNT ); -- 2
array.EXTEND;
array(3) := 'DDDD';
DBMS_OUTPUT.PUT_LINE( array.COUNT ); -- 3
FOR idx IN 1 .. array.COUNT LOOP
DBMS_OUTPUT.PUT_LINE( idx || ' - ' || array(idx) );
END LOOP;
END;
/

Related

ORA-06533: Subscript beyond count ORA-06512 in PL/SQL

i want to have 10 by 10 grid with numbers from 1 to 100
and it give me this error
ORA-06533: Subscript beyond count ORA-06512: at line 25
ORA-06512: at "SYS.DBMS_SQL", line 1721
i don't understand the error and i couldn't solve it
can someone please help me
DECLARE
-- PL SQL code to create and fill a two-dimensional array
-- create VARRAY type of 10 integers
TYPE array_10_int IS VARRAY(10) of PLS_INTEGER;
-- create VARRAY type of array_10_int
TYPE grid_100_int IS VARRAY(10) of array_10_int;
-- declare a variable of the grid_100_int type
grid_var grid_100_int;
-- declare counters
i PLS_INTEGER := 0;
j PLS_INTEGER :=0;
M PLS_INTEGER :=0;
N PLS_INTEGER :=0;
BEGIN
grid_var := grid_100_int();
-- TO DO : use nested loop to fill grid_var with numbers 1- 100
/** YOUR CODE HERE **/
M:=0;
Loop
M:=M+1;
N:=0;
LOOP
J:=j+1;
If grid_var(M)(N)<100 THEN
DBMS_OUTPUT.PUT(' ' || grid_var(M)(N) || ' ');
ELSE
DBMS_OUTPUT.PUT( grid_var(M)(N) || ' ');
END IF;
EXIT WHEN (N =100);
END LOOP;
dbms_output.put_line(' ');
EXIT WHEN (M=10);
END LOOP;
-- Print the grid with nested loop
i:=0;
LOOP --outer loop
i := i+1;
j := 0;
LOOP -- inner loop
j:= j+1;
IF grid_var(i)(j) < 10 THEN
DBMS_OUTPUT.PUT(' ' || grid_var(i)(j) || ' ');
ELSE
DBMS_OUTPUT.PUT( grid_var(i)(j) || ' ');
END IF;
EXIT WHEN (j =10);
END LOOP;
dbms_output.put_line(' ');
EXIT WHEN (i =10);
END LOOP;
END;
A second look.
Creating multiple dimensional arrays (10 x 10) are tricky things. They are actually a collection of a collection. Further, each has the same definition and existence requirements: the element must exist before referenced, either by extend or array initialization. The following initializes each array before referencing it. Also it uses a FOR loop letting Postgres handle the setting, incrementing subscripts and loop exiting, rather than manually. See demo.
declare
-- PL SQL code to create and fill a two-dimensional array
-- create VARRAY type of 10 integers
type array_10_int is varray(10) of pls_integer;
-- create VARRAY type of array_10_int
type grid_100_int is varray(10) of array_10_int ;
-- declare a variable of the grid_100_int type
grid_var grid_100_int;
begin
grid_var := grid_100_int(null,null,null,null,null,null,null,null,null,null); -- initialize outer array
-- TO DO : use nested loop to fill grid_var with numbers 1- 100
/* YOUR CODE HERE */
for m in 1 .. 10
loop
grid_var(m) := array_10_int(null,null,null,null,null,null,null,null,null,null); -- initialize the inner array
for n in 1 .. 10
loop
grid_var(m)(n) := 10*(m-1)+ n;
end loop ;
end loop;
-- Print the grid with nested loop
for m in 1 .. 10
loop
for n in 1 .. 10
loop
dbms_output.put_line ('grid_var(' || to_char(m)
|| ')(' || to_char(n)
|| ') = ' || to_char(grid_var(m)(n))
);
end loop ;
end loop;
end;
Take away: Creating and using multiple dimensional arrays in Oracle is doable. But use them only when there is no other option. They add considerable complexity, usually unnecessary, and are poorly understood. (The above one is vary simple.)
Take away 2 Let Postgres control your loops. Less error prone, less code, easier to read.
PL/SQL array index begin with 1. But in the following code the local variable n is 0 when first used as an index.
Loop
M:=M+1;
N:=0;
LOOP
J:=j+1;
If grid_var(M)(N)<100 THEN --<<< n is 0 which throws your exception.
...
EXIT WHEN (N =100); --<<< NEVER occurs, N is not
Unfortunately, this is not your only issue (in this logic). You exit statement is condition EXIT WHEN (N =100); will never be met. You initialize the variable n before entering the loop, but never increment it in the loop.
Adjust the above to:
Loop
M:=M+1;
N:=1; -- <<< change here
LOOP
J:=j+1;
If grid_var(M)(N)<=100 THEN
...
EXIT WHEN (N >100); --<<< NEVER occurs, N is not incremented in the loop;
END LOOP;
Your other loop does not appear to have the same issue, but you should check it.

Wrong number or TYPES of arguments, error in PL/SQL

I have to create a list of RECORD and I need to send it to a procedure.
There is my header.
CREATE OR REPLACE PACKAGE tema4 IS
TYPE obj IS RECORD(id INTEGER := 0,percent INTEGER := 0);
TYPE listObj IS TABLE OF obj INDEX BY PLS_INTEGER;
PROCEDURE ex1 (p_listObj IN listObj);
END tema4;
My body.
create or replace PACKAGE BODY tema4 IS
PROCEDURE ex1 (p_listObj IN listObj) IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Cant reach');
END ex1;
END tema4;
And my code that calls procedure ex1.
DECLARE
TYPE obj IS RECORD(id INTEGER := 0,percent INTEGER := 0);
TYPE listObj IS TABLE OF obj INDEX BY PLS_INTEGER;
v_obj obj;
v_listObj listObj;
BEGIN
FOR v_i IN (SELECT ID,BURSA FROM STUDENTI ORDER BY ID) LOOP
v_obj.id := v_i.id;
v_obj.percent := 50;
v_listObj(v_i.id) := v_obj;
END LOOP;
FOR v_i IN v_listObj.FIRST..v_listObj.LAST LOOP
DBMS_OUTPUT.PUT_LINE(v_listObj(v_i).id || ' - ' ||
v_listObj(v_i).percent);
END LOOP;
tema4.ex1(v_listObj); --this line is with problems
END;
PLS-00306: wrong number or types of arguments in call to 'EX1'
Can someone explain me what is wrong in my code? I also tried to create my type as global, but it won't let me because of 'RECORD' keyword.
Don't declare the types again (new types), use the types already declared in the package spec:
DECLARE
v_obj tema4.obj;
v_listObj tema4.listObj;
BEGIN
FOR v_i IN (SELECT ID,BURSA FROM STUDENTI ORDER BY ID) LOOP
v_obj.id := v_i.id;
v_obj.percent := 50;
v_listObj(v_i.id) := v_obj;
END LOOP;
FOR v_i IN v_listObj.FIRST..v_listObj.LAST LOOP
DBMS_OUTPUT.PUT_LINE(v_listObj(v_i).id || ' - ' ||
v_listObj(v_i).percent);
END LOOP;
tema4.ex1(v_listObj); --this line is with problems
END;

PL/SQL How to compare two associative array?

How can I check if two associative arrays are same or not?
E.g.
ARRAY1('ID') := 1;
ARRAY1('NAME') := 'Joe';
ARRAY2('ID') := 1;
ARRAY2('NAME') := 'Joe';
-- and i want to do like this
IF ARRAY1 = ARRAY2 THEN
-- Do something.
END IF;
You can compare two associative array using a function to compare the keys and values:
DECLARE
TYPE test_array IS TABLE OF VARCHAR2(200) INDEX BY VARCHAR2(10);
array1 test_array;
array2 test_array;
array3 test_array;
FUNCTION is_equal(
arr1 test_array,
arr2 test_array
) RETURN BOOLEAN
IS
i VARCHAR2(10);
BEGIN
i := arr1.FIRST;
WHILE i IS NOT NULL LOOP
-- Check if the keys in the first array do not exists in the
-- second array or if the values are different.
IF ( NOT arr2.EXISTS( i ) )
OR ( arr1(i) <> arr2(i) )
OR ( arr1(i) IS NULL AND arr2(i) IS NOT NULL )
OR ( arr1(i) IS NOT NULL AND arr2(i) IS NULL )
THEN
RETURN FALSE;
END IF;
i := arr1.NEXT(i);
END LOOP;
i := arr2.FIRST;
WHILE i IS NOT NULL LOOP
-- Check if there are any keys in the second array that do not
-- exists in the first array.
IF ( NOT arr1.EXISTS( i ) )
THEN
RETURN FALSE;
END IF;
i := arr2.NEXT(i);
END LOOP;
RETURN TRUE;
END;
BEGIN
array1('ID') := '1';
array1('NAME') := 'Joe';
array2('ID') := '1';
array2('NAME') := 'Joe';
array3('ID') := '1';
array3('NAME') := 'Joe';
array3('XYZ') := 'ABC';
IF is_equal( array1, array2 ) THEN
DBMS_OUTPUT.PUT_LINE( '1 and 2 are the same' );
ELSE
DBMS_OUTPUT.PUT_LINE( '1 and 2 are different' );
END IF;
IF is_equal( array1, array3 ) THEN
DBMS_OUTPUT.PUT_LINE( '1 and 3 are the same' );
ELSE
DBMS_OUTPUT.PUT_LINE( '1 and 3 are different' );
END IF;
END;
/
Outputs:
1 and 2 are the same
1 and 3 are different
Oracle documentation is your friend.
https://docs.oracle.com/database/121/LNPLS/composites.htm#LNPLS99915
In particular:
You cannot compare associative array variables to the value NULL or to each other.
Which means, if you need to compare associative arrays to each other, you have only two choices... check them element by element, for example in a loop (on the elements of one array - and make sure you check to see if they have the same number of elements); or, if you need to do this often, write your own function that compares arrays in that manner, and call it whenever you need to.
Just don't ask why Oracle is not providing a native PL/SQL function for this; you will very likely get no answers.

Associative array issue

I have created an associative array, I understand it can be used different way of writing but however just need tips how to make this work. Currently when I compile this block I would receive no data found. Thank you!
DECLARE
TYPE type_state IS TABLE OF VARCHAR(50)
INDEX BY VARCHAR2(50);
tbl_state type_state;
lv_statecity1_txt VARCHAR2(30):= 'TAMPA';
lv_statecity2_txt VARCHAR2(30):= 'ATLANTA';
lv_statecity3_txt VARCHAR2(30):= 'NYC';
lv_cnt_num NUMBER(5) := 0;
BEGIN
tbl_state('FLORIDA') := lv_statecity1_txt;
tbl_state('GEORGIA') := lv_statecity2_txt;
tbl_state('New_York') := lv_statecity3_txt;
FOR i IN 1..tbl_state.count loop
IF tbl_state(i) IS NOT NULL THEN
LV_CNT_NUM := LV_CNT_NUM + 1;
dbms_output.put_line(tbl_state(i));
END IF;
END LOOP;
dbms_output.put_line('That''s it folks');
END;
tbl_state is a table of strings indexed by strings - passing in index values 1, 2, 3 (numbers) won't work.
It is true that array pairs are still ordered (first, second etc.), but accessing them in a loop is a bit more complicated. You will need a WHILE loop, and the index (I kept the name i to match your code as closely as possible) must be declared to be the same data type and length as the keys in the array.
DECLARE
TYPE type_state IS TABLE OF VARCHAR(50)
INDEX BY VARCHAR2(50);
tbl_state type_state;
lv_statecity1_txt VARCHAR2(30):= 'TAMPA';
lv_statecity2_txt VARCHAR2(30):= 'ATLANTA';
lv_statecity3_txt VARCHAR2(30):= 'NYC';
lv_cnt_num NUMBER(5) := 0; -- WHAT IS THIS FOR? NEEDED??
i varchar2(50); -- Notice this line
BEGIN
tbl_state('FLORIDA') := lv_statecity1_txt;
tbl_state('GEORGIA') := lv_statecity2_txt;
tbl_state('New_York') := lv_statecity3_txt;
i := tbl_state.first; -- And this line
while (i is not null) loop -- And this one
LV_CNT_NUM := LV_CNT_NUM + 1;
dbms_output.put_line(tbl_state(i));
i := tbl_state.next(i); -- And this one
END LOOP;
dbms_output.put_line('That''s it folks');
END;
/

How to loop through a delimited list in Oracle PLSQL

I am working on an Oracle procedure that calls another procedure within it. One of my parameters (parm1) can contain one or more values in a comma separated list. How can I loop through these values to pass them one at a time to another procedure?
Here is an example of what I would like it to do:
When Parm1 = 123,312
callProcedure2(123)
callProcedure2(321)
-or-
When Parm1 123
callProcedure2(123)
I think this can be accomplished using a loop but I can't figure out how to get it to use each value as a separated call within the loop.
Any help would be appreciated!
Thanks!
CURSOR V_CUR IS
select regexp_substr(Parm1 ,'[^,]+', 1, level) As str from dual
connect by regexp_substr(Parm1, '[^,]+', 1, level) is not null;
This curor will give you result like this
123
321
Now iterate the cursor and call the procedure in loop.
For i IN V_CUR
LOOP
callProdcedure2(i.str);
END LOOP;
Just loop through substrings:
declare
parm1 varchar2(1000) := '123,234,345,456,567,789,890';
vStartIdx binary_integer;
vEndIdx binary_integer;
vCurValue varchar2(1000);
begin
vStartIdx := 0;
vEndIdx := instr(parm1, ',');
while(vEndIdx > 0) loop
vCurValue := substr(parm1, vStartIdx+1, vEndIdx - vStartIdx - 1);
-- call proc here
dbms_output.put_line('->'||vCurValue||'<-');
vStartIdx := vEndIdx;
vEndIdx := instr(parm1, ',', vStartIdx + 1);
end loop;
-- Call proc here for last part (or in case of single element)
vCurValue := substr(parm1, vStartIdx+1);
dbms_output.put_line('->'||vCurValue||'<-');
end;
There is a utility procedure COMMA_TO_TABLE and array type DBMS_UTILITY.UNCL_ARRAY dedicated for this task. Since Oracle 10g.
It is well document here.
Here is a sample solution:
SET SERVEROUTPUT ON
DECLARE
csvListElm VARCHAR2(4000) := 'elm1, elm2,elm3 ,elm4 , elm5';
csvListTable DBMS_UTILITY.UNCL_ARRAY;
csvListLen BINARY_INTEGER;
currTableName VARCHAR2(222);
BEGIN
DBMS_UTILITY.COMMA_TO_TABLE(csvListElm, csvListLen, csvListTable);
FOR csvElm IN 1..(csvListTable.COUNT - 1) LOOP
dbms_output.put_line('-- CSV element : <'||csvListTable(csvElm)||'>');
dbms_output.put_line('-- Trimmed CSV element: <'||trim(csvListTable(csvElm))||'>');
END LOOP;
END;
/
Sample output:
-- CSV element : <elm1>;
-- Trimmed CSV element: <elm1>;
-- CSV element : < elm2>;
-- Trimmed CSV element: <elm2>;
-- CSV element : <elm3 >;
-- Trimmed CSV element: <elm3>;
-- CSV element : <elm4 >;
-- Trimmed CSV element: <elm4>;
-- CSV element : < elm5>;
-- Trimmed CSV element: <elm5>;
It is possible to use a function that you can use in a for loop (without regexp for ThinkJet):
Create a type and function
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE
FUNCTION cto_table(p_sep in Varchar2, p_list IN VARCHAR2)
RETURN t_my_list
AS
l_string VARCHAR2(32767) := p_list || p_sep;
l_sep_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
l_tab t_my_list := t_my_list();
BEGIN
LOOP
l_sep_index := INSTR(l_string, p_sep, l_index);
EXIT
WHEN l_sep_index = 0;
l_tab.EXTEND;
l_tab(l_tab.COUNT) := TRIM(SUBSTR(l_string,l_index,l_sep_index - l_index));
l_index := l_sep_index + 1;
END LOOP;
RETURN l_tab;
END cto_table;
/
Then how to call it in the for loop:
DECLARE
parm1 varchar2(4000) := '123,234,345,456,567,789,890';
BEGIN
FOR x IN (select * from (table(cto_table(',', parm1)) ) )
LOOP
dbms_output.put_line('callProdcedure2 called with ' || x.COLUMN_VALUE);
callProdcedure2(x.COLUMN_VALUE);
END LOOP;
END;
/
Notice the default name COLUMN_VALUE given by Oracle, which is necessary for the use I want to make of the result.
Result as expected:
callProdcedure2 called with 123
callProdcedure2 called with 234
...

Resources