How to populate varray multiple times - oracle

I have a script that uses one VARRAY multiple times. But, I can't seem to figure out how to reset the VARRAY after looping through it once. I wrote the following basic script to help me troubleshoot:
DECLARE
TYPE multi_show_id_type IS VARRAY (60) OF VARCHAR2 (10);
multi_show_id multi_show_id_type := multi_show_id_type ();
counter NUMBER := 1;
i NUMBER := 1;
BEGIN
DBMS_OUTPUT.put_line ('BEGIN');
WHILE i < 10
LOOP
DBMS_OUTPUT.put_line (i);
--counter:=0;
--multi_show_id :=multi_show_id_type();
--multi_show_id.delete;
WHILE counter < 25
LOOP
multi_show_id.EXTEND ();
multi_show_id (counter) := counter * counter;
DBMS_OUTPUT.put_line ('VArray: [' || counter || '] [' || multi_show_id (counter) || ']');
counter := counter + 1;
END LOOP;
i := i + 1;
END LOOP;
DBMS_OUTPUT.put_line ('END');
END;
/
This script works when it is only looping through the array once. But if you uncomment the counter:=0 line, which forces it to loop through the array population loop 10 times, I get an ORA-06532 error. You can see some of the stuff I've tried in the other commented lines. Any help would be appreciated.

Actually, #akf is correct; your code as written won't work because a VARRAY starts at item 1, not zero.
Change your code thusly and it works:
...
LOOP
DBMS_OUTPUT.put_line (i);
counter:=1;
--multi_show_id :=multi_show_id_type();
multi_show_id.delete;
WHILE counter < 26
LOOP
...
EDIT: if you want to run thru the loop 25 times you do need to change the WHILE loop upper bound to 26...

there seems to be two problems here. first, the VARRAY index starts at 1. second, you will stop once your VARRAY is full at 60 items, as defined in your declaration.
use the following:
TYPE multi_show_id_type IS VARRAY (250) OF VARCHAR2 (10);
and
counter:=1;
uncomment the multi_show_id :=multi_show_id_type(); line if you want to start at 1 for each loop. if you want to ensure that no more than 4 values, your inner while loop should make that restriction.

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.

Increment for loop by a fraction/ range of values in Oracle 19c

How could we increment value by .25 instead of default 1 in a for loop of a stored procedure - oracle 19c?
Also, is it possible to use an array of numbers as a loop values? If not, please advice how to implement it.
For instance,
fctr1 - passing -4 as fct and fctr1 needs to be incremented by .25 until 4
fctr2 - range of non periodic value such as 5,7,10,14,15,21
CREATE OR REPLACE procedure sp_test
( fct in number )
is
BEGIN
for fctr1 in fct..4 ///increment by .25
loop
dbms_output.put_line(fctr1);
for fctr2 in ///range of non periodic values
loop
dbms_output.put_line(fctr2);
end loop;
end loop;
END;
/
Just multiply the limits by 4 in the FOR loop and then divide by 4 in the output:
CREATE OR REPLACE procedure sp_test
( fct in number )
is
BEGIN
for fctr1 in (fct*4) .. (4*4) loop
dbms_output.put_line(fctr1/4);
dbms_output.put_line(5);
dbms_output.put_line(7);
dbms_output.put_line(10);
dbms_output.put_line(14);
dbms_output.put_line(15);
dbms_output.put_line(21);
end loop;
END;
/
db<>fiddle here
If you are using a version prior to 21c you cannot specify an increment value in for. But you can replace it with a WHILE statement where you can (well actually must) do own incrementing; using any desired value your want.
As far as the non periodic values you step back - to the schema level. Create a collection as a 'table of integers', populate that collection with the values, pass as that collection/array as another parameter. Something like:
create or replace procedure sp_test
( fct in number
, fct2 in non_periodic_values_t
, by_incr in number default 1
)
is
l_fct number := 1;
begin
while l_fct <= fct
loop
dbms_output.put_line('For l_fct ==> ' || l_fct );
for fct2_ndx in 1 .. fct2.count
loop
dbms_output.put_line(' ' || fct2(fct2_ndx));
end loop;
l_fct := l_fct + by_incr;
end loop;
end;
Since you have a number collection you can even pass the 3 values of a traditional for loop (start, stop, increment).
create or replace procedure sp_test2
( fct in number
, fct2 in non_periodic_values_t
, for_val non_periodic_values_t default non_periodic_values_t(1,4,1)
)
is
l_fct number := for_val(1);
begin
while l_fct <= for_val(2)
loop
dbms_output.put_line('For l_fct ==> ' || l_fct );
for fct2_ndx in 1 .. fct2.count
loop
dbms_output.put_line(' ' || fct2(fct2_ndx));
end loop;
l_fct := l_fct + for_val(3);
end loop;
end;
See examples here.

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 find a dbms_output.put_line() alternative to print contents line by line every time when it gets called in every iteration?

I want to view the output as the program goes while processing some records. Reading the line will not help, as it just retrieves is from the buffer and nothing else. For example:
DECLARE
CURSOR cEmploee IS SELECT * FROM g_emploees;
iTotal INTEGER := 0;
iCount INTEGER := 0;
BEGIN
SELECT COUNT(*) FROM g_emploees INTO iTotal;
FOR rLine IN cEmploee loop
dbms_output.put_line('Porcessed['||rLine.id||']: '|| ((iCount/iTotal)*100) || '%')
iCount := iCount + 1;
END LOOP;
END;
I cannot use dbms_output.get_line(), So stop marking it answered !
I cannot pipe the output to a file for read-only reasons !
Is there a command/setting for DBMS that I can use in order to view the processed % and print the line for processed in EVERY ITERATION and not at the end as a whole bunch of lines persisting in the buffer (The line printed must show every and exact time in PL/SQL when "dbms_output.put_line" is called not like 500 lines at the end of the execution) ??
CREATE OR REPLACE FUNCTION test_pipe
RETURN sys.DBMS_DEBUG_VC2COLL
pipelined
as
CURSOR cEmploee IS
SELECT * FROM g_emploees;
iTotal INTEGER := 0;
iCount INTEGER := 0;
BEGIN
SELECT COUNT(*)
INTO iTotal
FROM g_emploees ;
FOR rLine IN cEmploee loop
PIPE row('Porcessed['||rLine.id||']: '|| ((iCount/iTotal)*100) || '%');
iCount := iCount + 1;
END LOOP;
END;
/
--execute below statements ON command window :
SQL >set arraysize 1
SQL > SELECT * FROM TABLE(test_pipe);

Need to sort a list of random numbers in a stored procedure

I am in need of help in creating a stored procedure that allows a user to input a list of random numbers and then sort them using the bubble sort algorithm. I am very new to programming and as well as PL/SQL. Any help would be much appreciated.
Below are the lines of code that I have so far:
CREATE OR REPLACE PROCEDURE test_BubbleSort (i_number IN number) AS
type l_array_type IS TABLE OF NUMBER(10);
l_temp NUMBER;
l_array l_array_type := l_array_type();
BEGIN
--Loop through numbers and re-arrange their order using bubble sort---
FOR i in 1 .. l_array.Count - 1 LOOP
FOR j IN 2 .. l_array.Count LOOP
IF l_array(j) > l_array(j - 1) THEN
l_temp := l_array(j - 1);
l_array(j - 1) := l_array(j);
l_array(j) := l_temp;
END IF;
END LOOP;
END LOOP;
--Print the newly sorted numbers user inputs
FOR i in REVERSE 1 .. l_array.COUNT LOOP
dbms_output.put_line('The new sorted numbers are: ' || l_array(i));
END LOOP;
END;
I don't think this is really what you want, but if you do actually want to generate random numbers yourself and just want the length of the list to be supplied by the user (as i_number) then you can loop to do that:
...
BEGIN
--Generate some random numbers
for i in 1..i_number loop
l_array.extend;
l_array(i) := dbms_random.value(1, 100);
end loop;
--Loop through numbers and re-arrange their order using bubble sort---
FOR i in 1 .. l_array.Count - 1 LOOP
...
When called with an i_number parameter value of 5 that might give:
The new sorted numbers are: 10
The new sorted numbers are: 55
The new sorted numbers are: 60
The new sorted numbers are: 74
The new sorted numbers are: 87
The parameters to the dbms_random.value() call are restricting the range of 'random' numbers that are generated.
Here is the answer that I ended up with finally. Just wanted to share with you guys!
CREATE OR REPLACE PROCEDURE test_BubbleSort1(i_number IN VARCHAR2)
IS
--Local variables
l_temp NUMBER;
type l_array_type IS TABLE OF NUMBER(10);
l_array l_array_type;
BEGIN
--Parse values and dump into collections
SELECT TRIM(SUBSTR(txt,
INSTR(txt, ',', 1, level) + 1,
INSTR(txt, ',', 1, level + 1) -
INSTR(txt, ',', 1, level) - 1)) BULK COLLECT
INTO l_array
FROM (select ',' || i_number || ',' AS txt FROM DUAL)
CONNECT BY LEVEL <= LENGTH(i_number) - LENGTH(replace(i_number, ',', '')) + 1;
--Loop through numbers and re-arrange their order using bubble sort
FOR i in 1 .. l_array.LAST - 1
LOOP
FOR j IN 2 .. l_array.LAST
LOOP
IF l_array(j) > l_array(j - 1) THEN
l_temp := l_array(j - 1);
l_array(j - 1) := l_array(j);
l_array(j) := l_temp;
END IF;
END LOOP;
END LOOP;
--Print the newly sorted numbers
FOR k IN reverse 1 .. l_array.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('The new sorted numbers are: ' || l_array(k));
END Loop;
END;

Resources