PL/SQL How to compare two associative array? - oracle

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.

Related

The best way to fill varray in different code blocks pl sql

Usually I fill arrays like this:
TYPE name_options IS VARRAY(6) OF VARCHAR2(300);
dd_name_options_c name_options;
dd_name_options_c := name_options(string1, string2, string3, string4);
But what if I have two blocks that generate strings and I want to save all the strings to one array:
-- Block 1
....
dd_name_options_c := name_options(string1, string2, string3, string4);
....
-- Block 2
....
dd_name_options_c := name_options(string5, string6);
So, in the end, the array will contain 6 strings:
string1, string2, string3, string4, string5, string6
How can I do this?
you can use extend
declare
TYPE name_options IS VARRAY(6) OF VARCHAR2(300);
dd_name_options_c name_options;
begin
dd_name_options_c := name_options('a', 'b', 'c', 'd');
dd_name_options_c.Extend(2);
dd_name_options_c(5) := 'e';
dd_name_options_c(6) := 'f';
dbms_output.put_line(dd_name_options_c.count());
end;
/
what if I have two blocks that generate strings and I want to save all the strings to one array
If the blocks are separate program units or otherwise disconnected, maybe even can be executed in different orders, you need some logic which can tell whether the target array is already populated. One solution is to use a (private) PL/SQL procedure to manage this.
procedure populate_varray
( p_tgt in out name_options
, p_new in name_options)
is
n pls_integer;
begin
if p_tgt is null
or p_tgt.count() = 0
then
p_tgt := p_new;
elsif p_new is not null
and p_new.count() > 0
then
n := p_tgt.count();
for idx in 1 .. p_new.count() loop
p_tgt.extend();
p_tgt(n + idx) := p_new(idx);
end loop;
end if;
end populate_varray;
Note: untested code, please comment below if there are errors :)
You would call it like this:
-- Block 1
....
populate_varray ( dd_name_options_c
, name_options(string1, string2, string3, string4));
....
-- Block 2
....
populate_varray (dd_name_options_c
, name_options(string5, string6) );

Find source na replacement parameters in a string and perform replacement in another string

I need a function that applies a certain rule to replace words in a string.
There are two variables:
v_imp_user_list - list of users delimited by pipe (example: JOHN|PETER|MARK|USER_PROD)
v_schema_remap_list - list of users that should be remapped (old value - new value, example: JOHN-GEORGE,USER_PROD-USER_TEST)
The function should parse v_schema_remap_list variable and if the name of the first user (before dash, old value)
exist in v_imp_user_list then replace it with the second user (after dash, new value).
Example:
v_imp_user_list := 'JOHN|PETER|MARK|USER_PROD'
v_schema_remap_list := 'JOHN-GEORGE,USER_PROD-USER_TEST'
Desired outcome:
GEORGE|PETER|MARK|USER_TEST
I have a solution which I will post but for some reason I don't like it and will appreciate any comment/review/better solution.
This function helps me to parse the v_schema_remap_list.
-- extract nth occurence in a delimited string
create or replace function f_find_str (
source_string varchar2
, occurence_outer number --occurence of the old-new pair
, occurence_inner number --old/new value, enter 1 for old or 2 for new
)
return varchar2
is
v_aux varchar2(500);
v_result varchar2(100);
begin
v_aux := ltrim(regexp_substr(',' || source_string, ',[^,]*', 1, occurence_outer),',');
if occurence_inner = 1 then
v_result := substr(v_aux, 1, instr(v_aux, '-')-1);
elsif occurence_inner = 2 then
v_result := substr(v_aux, instr(v_aux, '-')+1);
end if;
return v_result;
end;
/
This anonymous block shows my solution (see the loop inside).
declare
v_schema_remap_list varchar2(1000) := 'JOHN-GEORGE,USER_PROD-USER_TEST';
v_imp_user_list varchar2(1000) := 'JOHN|PETER|MARK|USER_PROD';
v_schema_remap_cnt number := nvl(regexp_count(v_schema_remap_list, '-'), 0);
begin
for i in 1..v_schema_remap_cnt
loop
v_imp_user_list := replace(
v_imp_user_list||'|',
f_find_str (
source_string => v_schema_remap_list
, occurence_outer => i
, occurence_inner => 1
)||'|',
f_find_str (
source_string => v_schema_remap_list
, occurence_outer => i
, occurence_inner => 2
)||'|'
);
v_imp_user_list := rtrim(v_imp_user_list, '|');
end loop;
dbms_output.put_line(v_imp_user_list); --test output
end;
/

Getting the error Reference to uninitialized collection to associative arrays

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

loop counter case in oracle procedure

Is there any loop counter in procedure.
How to handle below case.
BEGIN
FOR recAnnLang IN getLang(var_non_subscribe)
LOOP
// if loop 1 recAnnLangCode.Ann_Lang assign to var1
var1 := recAnnLangCode.Ann_Lang;
// if loop 2 recAnnLangCode.Ann_Lang assign to var2
var2 := recAnnLangCode.Ann_Lang;
// if loop 3 recAnnLangCode.Ann_Lang assign to var2
var3 := recAnnLangCode.Ann_Lang;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('Debug :: Inside Exception because data not found ' );
END;
DECLARE n_counter NUMBER := 0;
BEGIN
LOOP
n_counter := n_counter + 1;
DBMS_OUTPUT.PUT_LINE(n_counter);
IF n_counter = 5 THEN
EXIT;
END IF;
END LOOP;
END;
I did it like this.
maybe you are looking for something like
declare
type a_type is table of recAnnLang.Ann_Lang%type index by pls_integer;
val a_type;
begin
for recAnnLang in getLang(var_non_subscribe) loop
val(nvl(val.last, 0) + 1) := recAnnLang.Ann_Lang;
end loop;
for i in 1 .. val.last loop
dbms_output.put_line(val(i));
end loop;
end;
maybe,if getlang is an explicit cursor,you may think of using rownum in the cursor query select clause and use it as the counter.

Access Array of Array in Pl SQL

I have Array list having List of Arrays.
for example:
//Array list type is varchar
Listarray1(0) := 'data';
Listarray1(1) := 'data1';
Listarray2(0) := 'data2';
Listarray2(1) := 'data3';
//Sub list type is listarray
SUBLIST(0) := Listarray1;
SUBLIST(0) := Listarray2;
how to print the each array using loop
Multidimensional arrays in PL/SQL you do like this:
DECLARE
TYPE Sub_Array_list IS TABLE OF VARCHAR2(100);
TYPE Array_list IS TABLE OF Sub_Array_list;
My_array Array_list := Array_list();
BEGIN
My_array.EXTEND;
My_array(My_array.LAST) := Sub_Array_list('data', 'data1');
My_array.EXTEND;
My_array(My_array.LAST) := Sub_Array_list('data2', 'data3');
FOR i IN My_array.FIRST..My_array.LAST LOOP
FOR k IN My_array(i).FIRST..My_array(i).LAST LOOP
DBMS_OUTPUT.PUT_LINE ( 'My_array('||i||')('||k||') = '||My_array(i)(k) );
END LOOP;
END LOOP;
END;

Resources