How to change the subscript/index value in an associative array? - oracle

Is it possible to change the subscript/index value of an existing element in an associative array?
declare
type a_arr is table of varchar2(20) index by pls_integer;
tb1 a_arr;
begin
tb1(1) := 'aaaa';
tb1(2) := 'bbbb';
tb1(3) := 'cccc';
end;
/
In the above associative array tb1, is it possible to change the subscript value from 1 to 10 (ie from tb1(1) to tb1(10)) without deleting or creating a new element in the table?

It is unclear what are you trying to do. Here's basic general example. The output is 'aaaa' 10 times. You can add some logic in between. For example, if i = 3 then tbl_val:= 'bbbb' or smth lk this. You can also parameterise the start and/or end loop boundaries if you create a procedure for example.
DECLARE
type a_arr is table of varchar2(20) index by pls_integer;
tb1 a_arr;
tbl_val VARCHAR2(20):= 'aaaa';
BEGIN
FOR i IN 1..10 LOOP
tb1(i):= tbl_val;
dbms_output.put_line(tb1(i));
END LOOP;
END;
/

Related

Sorting Data at the runtime in Oracle PL/SQL using Associative array

I am trying to sort a data from runtime query and no way I can do that without use of a collection. Essentially, I have 2 columns in Service table - SERVICE_ID, SERVICE_NAME. I have created an Associative array in a package so that I can use it in my procedure.
TYPE g_vc_arr IS TABLE OF VARCHAR2(4000) INDEX BY PLS_INTEGER;
In my pl/sql block I am creating a variable like below:
service_list my_pkg.g_vc_arr;
I am assigning the SERVICE_ID and SERVICE_NAME to this variable like below:
LOOP
service_list(services.SERVICE_ID) := services.SERVICE_NAME;
END LOOP;
Now, I am using this to sort the name column like below snippet. I am able to print the name but in this process, I am losing the ID.
for query_result_row in (SELECT * from table(service_list) order by 1) loop
dbms_output.put_line(query_result_row.COLUMN_VALUE);
end loop;
I need both ID and NAME to process further. How can I get both?
As others have mentioned, you don't need to sort it as it is already held in the order you defined it:
create or replace package demo
as
type currency_tt is table of varchar2(3) index by pls_integer;
currencies demo.currency_tt :=
demo.currency_tt
( 1 => 'USD'
, 2 => 'GBP'
, 3 => 'EUR' );
end demo;
begin
for r in (
select * from table(demo.currencies)
)
loop
dbms_output.put_line(r.column_value);
end loop;
end;
Output:
USD
GBP
EUR
But you don't need SQL, as you can iterate over an associative array procedurally:
declare
i pls_integer := demo.currencies.first;
begin
while i is not null loop
dbms_output.put_line(i||' '||demo.currencies(i));
i := demo.currencies.next(i);
end loop;
end;
Or from 21c you can loop more conveniently like this:
begin
for id, ccy in pairs of demo.currencies loop
dbms_output.put_line(id || ' '|| ccy);
end loop;
end;
1 USD
2 GBP
3 EUR
I changed my approach a bit to solve this issue. I created a package with 2 column TYPE as record and an associative array of the record type:
create or replace package my_pkg is
TYPE g_rec_type IS RECORD (ID NUMBER, name VARCHAR2(4000));
TYPE g_rec_arr IS TABLE OF g_rec_type INDEX BY PLS_INTEGER;
end my_pkg;
In my actual PL/sqL block I created couple of variables:
i NUMBER := 0;
service_list MY_PKG.g_rec_arr;
Then I created a loop to assign the values to array:
LOOP
service_list(i).ID := services.SERVICE_ID;
service_list(i).NAME := services.SUMMARY;
i := i+1;
END LOOP;
Further to sort the value I used following:
FOR query_result_row in (SELECT ID, NAME from table(service_list) order by NAME) LOOP
dbms_output.put_line('ID is =>' || query_result_row.ID|| ' Name is =>' || query_result_row.NAME);
END LOOP;
And it worked as expected.

Deleting a particular element from a nested table

I have created a type of nested table like below:
CREATE OR REPLACE TYPE STRING_ARRAY AS TABLE OF VARCHAR2(1000);
And I want to have some ways to remove a particular element from the array.
E.g.
AVC_NAMES := STRING_ARRAY('ALEX', 'BETTY', 'CARL', 'DONALD');
SP_EXCLUDE(AVC_NAMES, 'BETTY'); // this will remove BETTY from the array.
Is there any build-in methods in the nested table that can remove specific element from an array? Or should I write a s.p. to do the job?
Try Multiset operator. Multisete operators combine the results of two nested tables into a single nested table.
result := collection1 MULTISET EXCEPT collection2
declare
type mycollection is table of varchar2(10);
c1 mycollection := mycollection('A','B','C','D','E');
to_remove mycollection := mycollection('C');
begin
c1 := c1 multiset EXCEPT to_remove;
for i in c1.first..c1.last
loop
dbms_output.put_line(c1(i));
end loop;
end;
Hope this below snipet helps.
SET serveroutput ON;
DECLARE
lv number_ntt;
lv_out number_ntt;
rem_value NUMBER:='&Enter_val';
BEGIN
lv:=number_ntt(1,2,3,4,5,6);
SELECT COLUMN_VALUE BULK COLLECT
INTO lv_out
FROM TABLE(lv)
WHERE COLUMN_VALUE NOT IN (rem_value);
lv:=lv_out;
FOR i IN lv.FIRST..lv.LAST
LOOP
dbms_output.put_line(lv(i));
END LOOP;
END;

How to easily refer to the index type of an associative array?

Is there an easy way to refer to the index type of an associative array in PLSQL while declaring a variable that represents its key?
I am looking for a language construct similar to the following one.
DECLARE
i number;
j i%type;
BEGIN
null;
END;
I would like to be able to do something like that.
DECLARE
type ty_my_type is table of number index by varchar2(4);
my_array ty_my_type;
-- key my_array.key%type;
-- or
-- key my_array%keytype;
BEGIN
null;
END;
You can declare a SUBTYPE and use that:
DECLARE
SUBTYPE KEY_TYPE IS VARCHAR2(4);
TYPE ASSOC_ARRAY_TYPE IS TABLE OF NUMBER INDEX BY KEY_TYPE;
my_array ASSOC_ARRAY_TYPE;
my_key KEY_TYPE;
BEGIN
NULL;
END;
/

Is this a table of tables in PL/SQL (If not what is going on with this code?)

Can someone clarify what the PL/SQL code below is doing? It looks as if assets_type is a table of base_Asset. Does that make it a Table of Tables?
I'm having a difficult time visualizing this when it comet to populating the data:
assets(v_ref_key)(dbfields(i).field) := rtrim(replace(strbuf_long2,'''',''''''));
Is this similar to a two dimensional array? Does this mean to populate the field column in the assets (temporary) table with an index of v_ref_key?
PROCEDURE LOAD
IS
TYPE dbfields_rec IS RECORD (field dbfields.field%TYPE,
article_title dbfields.title%TYPE,
image_title dbfields.title%TYPE);
TYPE dbfields_type IS TABLE OF dbfields_rec INDEX BY BINARY_INTEGER;
TYPE base_Asset IS TABLE OF VARCHAR2(4000) INDEX BY VARCHAR2(32);
TYPE assets_type IS TABLE OF asset_type INDEX BY VARCHAR2(4000);
dbfields dbfields_type;
assets assets_type;
v_ref_key assets.ref_key%TYPE;
-- CLIPPED Populate dbfields array code
-- It correctly populates
FOR i IN 1..dbfields.COUNT LOOP
BEGIN
sqlbuf := '(select rtrim(ufld3), ' || dbfields(i).field ||
' as col_label from assetstable ' ||
' where rtrim(ufld3) = ' || '''' || in_id || '''' || ' )';
OPEN assets_cur FOR
sqlbuf;
LOOP
FETCH assets_cur INTO v_ref_key, strbuf_long2;
EXIT WHEN assets_cur%NOTFOUND;
IF (trim(strbuf_long2) is not null and dbfields(i).field is not null) THEN
assets(v_ref_key) (dbfields(i).field)
:= rtrim(replace(strbuf_long2,'''',''''''));
END IF;
END LOOP;
close assets_cur;
END;
END LOOP;
END LOAD;
PL/SQL really only provides one-dimensional arrays - but each element of the array is allowed to be another array if you want to make arrays that act like multi-dimensional ones.
Here is an awfully contrived example to illustrate:
DECLARE
TYPE rows_type IS TABLE OF VARCHAR2(4000) INDEX BY VARCHAR2(4000);
TYPE spreadsheet_type IS TABLE OF row_type INDEX BY VARCHAR2(4000);
spreadsheet spreadsheet_type;
BEGIN
spreadsheet ('row 1') ('column A') := 'XYZ';
END;
The first ('row 1') is the index into the spreadsheet_type array, which would hold all the columns for a particular "row"; and the second ('column A') is the index into the rows_type array.
The "multi-dimensional" aspect of this implementation isn't perfect, however: while you can work with a whole row if you want, e.g.:
my_row rows_type;
...
my_row := spreadsheet ('row 1');
you can't pick out a particular column - there's no way to refer to the set of all elements in the rows for a particular index into rows_type. You'd have to do something like make another type and loop through the first array.

Find specific varchar in Oracle Nested Table

I'm new to PL-SQL, and struggling to find clear documentation of operations are nested tables. Please correct any misused terminology etc.
I have a nested table type that I use as a parameters for a stored procedure.
CREATE OR REPLACE TYPE "STRARRAY" AS TABLE OF VARCHAR2 (255)
In my stored procedure, the table is initialized and populated. Say I have a VARCHAR2 variable, and I want to know true or false if that varchar exists in the nested table.
I tried
strarray.exists('somevarchar')
but I get an ORA-6502
Is there an easier way to do that other than iterating?
FOR i IN strarray.FIRST..strarray.LAST
LOOP
IF strarray(i) = value THEN
return 1;--found
END IF;
END LOOP;
For single value check I prefer the "member" operator.
zep#dev> declare
2 enames strarray;
3 wordToFind varchar2(255) := 'King';
4 begin
5 select emp.last_name bulk collect
6 into enames
7 from employees emp;
8 if wordToFind member of enames then
9 dbms_output.put_line('Found King');
10 end if;
11 end;
12 /
Found King
PL/SQL procedure successfully completed
zep#dev>
You can use the MULTISET INTERSECT operator to determine whether the string you're interested in exists in the collection. For example
declare
l_enames strarray;
l_interesting_enames strarray := new strarray( 'KING' );
begin
select ename
bulk collect into l_enames
from emp;
if( l_interesting_enames = l_interesting_enames MULTISET INTERSECT l_enames )
then
dbms_output.put_line( 'Found King' );
end if;
end;
will print out "Found King" if the string "KING" is an element of the l_enames collection.
You should pass an array index, not an array value to an exists in case you'd like to determine whether this element exists in collection. Nested tables are indexed by integers, so there's no way to reference them by strings.
However, you might want to look at associative arrays instead of collections in case you wish to reference your array element by string index. This will look like this:
DECLARE
TYPE assocArray IS TABLE OF VARCHAR2(100) INDEX BY VARCHAR2(100);
myArray assocArray;
BEGIN
myArray('foo') := 'bar';
IF myArray.exists('baz') THEN
dbms_output.put_line(myArray('baz'));
ELSIF myArray.exists('foo') THEN
dbms_output.put_line(myArray('foo'));
END IF;
END;
Basically, if your array values are distinct, you can create paired arrays referencing each other, like,
arr('b') := 'a'; arr('a') := 'b';
This technique might help you to easily look up any element and its index.
When a nested table is declared as a schema-level type, as you have done, it can be used in any SQL query as a table. So you can write a simple function like so:
CREATE OR REPLACE FUNCTION exists_in( str VARCHAR2, tab stararray)
RETURN BOOLEAN
AS
c INTEGER;
BEGIN
SELECT COUNT(*)
INTO c
FROM TABLE(CAST(tab AS strarray))
WHERE column_value = str;
RETURN (c > 0);
END exists_in;

Resources