Extend table of varchar with a varchar - oracle

I'm trying to create a table of varchar array like this:
DECLARE
lna_poi_list fls_number_table := fls_number_table();
lsa_poi_list fls_varchar_table := fls_varchar_table();
ls_poi_list varchar;
BEGIN
FOR i IN 1..lna_poi_list.COUNT
LOOP
ls_poi_list := calling a function that returns a varchar and takes
lna_poi_list(i) as param;
lsa_poi_list.extend(ls_poi_list);
END LOOP
END;
After this I just want to return lsa_poi_list array but I'm getting some
character to number conversion error
Any ideas is much appreaciated

As documented, extend takes optional numeric arguments:
The EXTEND method has these forms:
EXTEND appends one null element to the collection.
EXTEND(n) appends n null elements to the collection.
EXTEND(n,i) appends n copies of the ith element to the collection.
To assign a new element, extend the collection, and then assign the value:
lsa_poi_list.extend;
lsa_poi_list(lsa_poi_list.COUNT) := ls_poi_list;
Referencing count means you're populating the last (new, null) element in the array.
If you're always adding one varhar (varchar2?) element for each element in the number array, you can size it once before the loop instead, which would be slightly more efficient, e.g:
DECLARE
lna_poi_list sys.odcinumberlist := sys.odcinumberlist();
lsa_poi_list sys.odcivarchar2list := sys.odcivarchar2list();
ls_poi_list varchar2(10);
BEGIN
lna_poi_list.extend(3);
lsa_poi_list.extend(lna_poi_list.COUNT);
FOR i IN 1..lna_poi_list.COUNT
LOOP
ls_poi_list := 'x'; -- from some function
lsa_poi_list(i) := ls_poi_list;
END LOOP;
END;
/
PL/SQL procedure successfully completed.

Related

WHILE & FOR LOOP

I have question while processing records in two different looping method i.e WHILE & FOR LOOP please refer to following codes
DECLARE
TYPE rc_emp_type IS RECORD ( empno NUMBER,ename VARCHAR(30),sal NUMBER, comm NUMBER);
TYPE rc_emp_tab IS TABLE OF rc_emp_type;
l_emp_rec rc_emp_tab := rc_emp_tab();
TYPE rc_emp_calc_type IS RECORD ( empno NUMBER,
ename VARCHAR(30),
sal NUMBER,
comm NUMBER,
new_sal NUMBER);
TYPE rc_emp_calc_tab IS TABLE OF rc_emp_calc_type;
l_emp_calc_rec rc_emp_calc_tab := rc_emp_calc_tab();
l_emp_fcalc_rec rc_emp_calc_tab := rc_emp_calc_tab();
l_idx NUMBER;
l_start_time TIMESTAMP;
l_end_time TIMESTAMP;
l_exe_time TIMESTAMP;
BEGIN
SELECT empno,ename,sal,comm
BULK COLLECT INTO l_emp_rec
FROM emp;
l_idx := l_emp_rec.FIRST;
WHILE l_idx IS NOT NULL
LOOP
l_emp_calc_rec.EXTEND;
l_emp_calc_rec(l_emp_calc_rec.LAST).empno := l_emp_rec(l_idx).empno;
l_emp_calc_rec(l_emp_calc_rec.LAST).ename := l_emp_rec(l_idx).ename;
l_emp_calc_rec(l_emp_calc_rec.LAST).sal := l_emp_rec(l_idx).sal;
l_emp_calc_rec(l_emp_calc_rec.LAST).comm := l_emp_rec(l_idx).comm;
l_emp_calc_rec(l_emp_calc_rec.LAST).new_sal := NVL(l_emp_rec(l_idx).sal,0) + NVL(l_emp_rec(l_idx).comm,0);
l_idx := l_emp_rec.NEXT(l_idx);
END LOOP;
FOR l_idx IN l_emp_rec.FIRST .. l_emp_rec.LAST
LOOP
l_emp_fcalc_rec.EXTEND;
l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).empno := l_emp_rec(l_idx).empno;
l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).ename := l_emp_rec(l_idx).ename;
l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).sal := l_emp_rec(l_idx).sal;
l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).comm := l_emp_rec(l_idx).comm;
l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).new_sal := NVL(l_emp_rec(l_idx).sal,0) + NVL(l_emp_rec(l_idx).comm,0);
END LOOP;
END;
Out of these two above procedure which is the efficient way of looping
If you know your collection will be densely-filled, as is the case with a collection filled by BULK COLLECT, I suggest you use a numeric FOR loop. That assumes densely-filled and therefore is most appropriate in that context.
Whenever you are not 100% certain that your collection is densely-filled, you should use a WHILE loop and the FIRST-NEXT or LAST-PRIOR methods to iterate through the collection.
You might argue that you might as well just use the WHILE loop all the time. Performance will be fine, memory consumption is not different....BUT: you might "hide" an error this way. If the collection is supposed to be dense, but it is not not, you will never know.
Finally, there is one way in which the WHILE loop could be a better performer than a FOR loop: if your collection is very sparse (eg, elements populated only in index values -1M, 0, 1M, 2M, 3M, etc.), the FOR loop will raise lots of NO_DATA_FOUND exceptions. Handling and continuing for all those exceptions will make loop execution very slow.
Out of these two above procedure which is the efficient way of looping
While dealing with collection,For Loop sometimes throws error when the collection is Sparse. In that case its beneficial to use WHILE LOOP. Both looping mechanism is equal in performance.
Sparse:- A collection is sparse if there is at least one index value between the lowest and highest defined index values that is not defined. For example, a sparse collection has an element assigned to index value 1 and another to index value 10 but nothing in between. The opposite of a sparse collection is a dense one.
Use a numeric FOR loop when
Your collection is densely filled (every index value between the lowest and the highest is defined)
You want to scan the entire collection, not terminating your scan if some condition is met
Conversely, use a WHILE loop when
Your collection may be sparse
You might terminate the loop before you have iterated through all the elements in the collection
As #XING points the difference is not in how efficient they are, but in what happens with sparse collections. Your example does not face this issue as both are built with bulk collect so there are no gaps in the index values. But this is NOT always the case. The following demo shows the difference between them.
declare
cursor c_numbers is
select level+23 num -- 23 has no particulat significence
from dual
connect by level <= 5; -- nor does 5
type base_set is table of c_numbers%rowtype;
while_set base_set;
for_set base_set;
while_index integer; -- need to define while loop index
begin
-- populate both while and for arrays.
open c_numbers;
fetch c_numbers bulk collect into while_set;
close c_numbers;
open c_numbers;
fetch c_numbers bulk collect into for_set;
close c_numbers;
-- Make sparse
while_set.delete(3);
for_set.delete(3);
-- loop through with while
while_index := while_set.first;
while while_index is not null
loop
begin
dbms_output.put_line('While_Set(' ||
while_index ||
') = ' ||
while_set(while_index).num
);
while_index := while_set.next(while_index);
exception
when others then
dbms_output.put_line('Error in While_Set(' ||
while_index ||
') Message=' ||
sqlerrm
);
end;
end loop;
-- loop through with for
for for_index in for_set.first .. for_set.last
loop
begin
dbms_output.put_line('For_Set(' ||
for_index ||
') = ' ||
for_set(for_index).num
);
exception
when others then
dbms_output.put_line('Error in For_Set(' ||
for_index ||
') Message=' ||
sqlerrm
);
end;
end loop;
end;
Also try a for loop with a collection defines as:
type state_populations_t is table of number index by varchar2(20);
state_populations state_populations_t;
And yes, that line is in production code and has run for years,

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

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

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

PLSQL when trying to change value of length while loop gives error

In my pl sql while loop i try to change the length of a number, which also then changes the length of my value on which my while loop is based the while loop stops working. The code is as follows
CREATE OR REPLACE FUNCTION neemrest(restnumber number)
RETURN NUMBER
AS
partrest NUMBER;
afternumber NUMBER;
BEGIN
WHILE LENGTH(TO_CHAR(restnumber))>9 LOOP
partrest := TO_NUMBER(SUBSTR(restnumber,1,10));
afternumber := TO_NUMBER(SUBSTR(restnumber,11,20));
restnumber := partrest||afternumber;
END LOOP;
RETURN MOD(restnumber,64);
END;
Now by taking each step seperatly i found that the problem liest in the last line of the while loop
restnumber := partrest||afternumber;
Now my question how can i change the lenght of my number whilst not breaking the while loop since this is vital for my function. and the usage of my function
The problem you are running into is that you are trying to assign a value to a readonly parameter.
You should modify your function to be like this:
CREATE OR REPLACE FUNCTION neemrest(restnumber in out number)
RETURN NUMBER
AS
partrest NUMBER;
afternumber NUMBER;
BEGIN
WHILE LENGTH(TO_CHAR(restnumber))>9 LOOP
partrest := TO_NUMBER(SUBSTR(restnumber,1,10));
afternumber := TO_NUMBER(SUBSTR(restnumber,11,20));
restnumber := partrest||afternumber;
END LOOP;
RETURN MOD(restnumber,64);
END;

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