Showing posts with label varrays. Show all posts
Showing posts with label varrays. Show all posts

Wednesday, January 09, 2008

PLS-00707: unsupported construct or internal error

A reader asked me to investigate why he was getting the error being discussed whenever he tried to execute the following PL/SQL block:
declare
type WARRAY is varray(324) OF varchar2(200);
LLISTA WARRAY;
VALS WARRAY;
number pls_integer := 1;
begin
--VALS := WARRAY(null, null);
--LLISTA := WARRAY(null);
--LLISTA := VALS;
dbms_output.put_line(number);
loop
exit when LLISTA(number)%NOTFOUND;
LLISTA(number) := VALS(number);
Number:= Number + 1;
end loop;
end;

Error report:
ORA-06550: line 0, column 0:
PLS-00707: unsupported construct or internal error [2704]
ORA-06550: line 12, column 2:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:

There are several things worth noting in this tiny PL/SQL snippet, but let's start off with the main issue.
PLS-00707 is raised because Oracle doesn't support the %NOTFOUND construct with collections as it is meant to be used with cursors. I must guess that the user got confused with the EXISTS method that allows to check for the presence of non-initiatilized elements in collections (not to be confused with null elements!).
declare
type WARRAY is varray(324) OF varchar2(200);
LLISTA WARRAY;
VALS WARRAY;
number pls_integer := 1;
begin
VALS := WARRAY('a', 'b');
LLISTA := WARRAY('c', 'd');
--LLISTA := VALS;
loop
--exit when LLISTA(number)%NOTFOUND;
exit when not LLISTA.EXISTS(number);
LLISTA(number) := VALS(number);
number:= number + 1;
end loop;
dbms_output.put_line(number);
dbms_output.put_line(LLISTA(number - 1));
end;
If the intention of this procedure was to copy the elements of collection VALS into LLISTA, then it must be noted that if both varrays are exactly of the same type and dimension, then we could get rid of the loop altogether:

declare
type WARRAY is varray(324) OF varchar2(200);
LLISTA WARRAY;
VALS WARRAY;
number pls_integer := 1;
begin
VALS := WARRAY('a', 'b');
LLISTA := WARRAY('c', 'd', 'e');
LLISTA := VALS;
number := LLISTA.COUNT;
dbms_output.put_line(number);
dbms_output.put_line(LLISTA(number));
end;
Entire collections can be copied as if they were simple variables when the aforementioned conditions are met.
Note also that the third element of LLISTA ('e') has disappeared after the copy, so, don't expect that Oracle copies only the subset of the collection containing the initialized elements, you'll need to do it yourself (see later on).
Let's see what happens when the type looks equal but is not, as in the following example where i duplicated they custom type WARRAY:
declare
type WARRAY is varray(324) OF varchar2(200);
type WARRAY2 is varray(324) OF varchar2(200);
LLISTA WARRAY;
VALS WARRAY2;
number pls_integer := 1;
begin
VALS := WARRAY2('a', 'b');
LLISTA := WARRAY('c', 'd');
LLISTA := VALS;
number := LLISTA.COUNT;
dbms_output.put_line(number);
dbms_output.put_line(LLISTA(number));
end;

Error report:
ORA-06550: line 10, column 12:
PLS-00382: expression is of wrong type
ORA-06550: line 10, column 2:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
The collection assignment doesn't work anymore because even if WARRAY and WARRAY2 appear to be perfectly equivalent, they are not the same type, strictly speaking.

Finally, let's go back to the previous step, the working one, and assume that we want to copy only a subset of the elements of VALS. I guess that for some reason VALS contains either updated or saved values that at a certain point we want to restore in LLISTA, but without erasing the elements of LLISTA that have no corresponding subscript in VALS:
declare
LLISTA string_table_type;
VALS string_table_type;
BKP string_table_type;
n pls_integer := 1;
begin
VALS := string_table_type('a','b');
LLISTA := string_table_type('c','d','e','f');
BKP := LLISTA;
n := VALS.count;
select c
bulk collect into LLISTA
from (
select column_value as c
from table(VALS)
union all
select c
from (select rownum r, column_value as c
from table(BKP))
where r > n
);
dbms_output.put_line(LLISTA.count);
for i in LLISTA.first .. LLISTA.last
loop
dbms_output.put_line(LLISTA(i));
end loop;
end;

4
a
b
e
f

Again, some remarks: first of all i had to use an externally defined collection type (string_table_type), not a locally defined collection type because PL/SQL doesn't allow me to use local collections in SQL statements:
create or replace TYPE "STRING_TABLE_TYPE"                                                                                                                                                                                                                                                                                                     as table
of varchar2(200)
Secondly, i had to create a third collection named BKP where i staged the data of LLISTA because otherwise i'd get an empty subset. Oracle is implicitly erasing LLISTA before performing BULK COLLECT so i cannot access its elements while performing the query.

Is this memory wasting approach faster than manually copying each and every element from VALS to LLISTA?
declare
type WARRAY is varray(324) OF varchar2(200);
LLISTA WARRAY;
VALS WARRAY;
n pls_integer := 1;
begin
VALS := WARRAY('a', 'b');
LLISTA := WARRAY('c', 'd', 'e', 'f');
loop
exit when not VALS.EXISTS(n);
LLISTA(n) := VALS(n);
n:= n + 1;
end loop;

dbms_output.put_line(LLISTA.count);
for i in LLISTA.first .. LLISTA.last
loop
dbms_output.put_line(LLISTA(i));
end loop;
end;

4
a
b
e
f
The performance comparison test is left to the reader as exercise, as some professors say when they are running late for lunch... ;-)

See message translations for PLS-00707 and search additional resources.

Tuesday, January 08, 2008

ORA-06532: Subscript outside of limit

This error is returned if, for some reason, a subscript value is lower than 1 (one) or greater than the declared upper bound of a varray.
A negative or zero value subscript will also cause an error when working with nested tables.
Do not confuse the upper bound with the actual number of initialized elements in the varray or table. A varray may contain 5 elements out of a maximum of 10, so if you specify 6 as subscript, the error returned will be different, as explained in a previous article.

Let's look at a couple of simple situations:
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type(null, null, null);
BEGIN
my_array(0) := 'a';
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;

Error report:
ORA-06532: Subscript outside of limit
ORA-06512: at line 5
06532. 00000 - "Subscript outside of limit"
*Cause: A subscript was greater than the limit of a varray
or non-positive for a varray or nested table.
*Action: Check the program logic and increase the varray limit
if necessary.
Another case being:
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type(null, null, null);
BEGIN
my_array(11) := 'a';
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;

Error report:
ORA-06532: Subscript outside of limit
ORA-06512: at line 5
06532. 00000 - "Subscript outside of limit"
*Cause: A subscript was greater than the limit of a varray
or non-positive for a varray or nested table.
*Action: Check the program logic and increase the varray limit
if necessary.

It should be clear that in both situations the subscript is out of range.
A slightly different situation is the following one:
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type();
BEGIN
my_array.EXTEND(11);
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;

Error report:
ORA-06532: Subscript outside of limit
ORA-06512: at line 5
06532. 00000 - "Subscript outside of limit"
*Cause: A subscript was greater than the limit of a varray
or non-positive for a varray or nested table.
*Action: Check the program logic and increase the varray limit
if necessary.
In this case it's easy to spot that we tried to initialize the collection to a larger number of elements that it can hold, but there can be subtler situations as follows:
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type(null);
BEGIN
my_array.EXTEND(10);
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;
It would be fine to extend the varray by 10 elements if we hadn't already initialized one element at declaration time (the null element above), indeed if you remove the null from the type constructor, the program will run without errors.

If you are populating the collection by means of some iterative process where you extend (that is initialize) the elements one at a time, then you must ensure that you do not extend the varray beyond its limits.
As already explained, you can check the limits with the collection method LIMIT, provided you have initialized the collection.
-----------------------------------------------
ORA-06532: Indice inferiore fuori dal limite
ORA-06532: Subíndice fuera del límite
ORA-06532: Subscript fora de límit
ORA-06532: Indice hors limites
ORA-06532: Index außerhalb der Grenzen
ORA-06532: Δείκτης εκτός ορίου
ORA-06532: Subscript uden for begrænsning
ORA-06532: Indexvariabel utanför gränsvärdet
ORA-06532: Subskript utenfor grense
ORA-06532: Alikomento ylitti rajan
ORA-06532: Határon kívüli index
ORA-06532: Indicele este în afara limitei
ORA-06532: Subscript ligt buiten limiet.
ORA-06532: Subscrito além do limite
ORA-06532: Subscrito fora do limite
ORA-06532: Индекс превышает пределы
ORA-06532: Dolní index přesahuje limit
ORA-06532: Dolný index mimo limitu
ORA-06532: Indeks (współrzędna elementu tablicy) spoza zakresu
ORA-06532: İndis sınırın dışında
ORA-06532: Subscript outside of limit

See message translations for ORA-06532 and search additional resources

Friday, January 04, 2008

ORA-06533: Subscript beyond count

This error is easily explained with the help of a few examples:
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type(null, null, null);
BEGIN
my_array(1) := 'a';
my_array(2) := 'b';
my_array(3) := 'c';
my_array(4) := 'd';
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;

Error report:
ORA-06533: Subscript beyond count
ORA-06512: at line 8
06533. 00000 - "Subscript beyond count"
*Cause: An in-limit subscript was greater than the count of a varray
or too large for a nested table.
*Action: Check the program logic and explicitly extend if necessary.

my_array has been initialized with three elements (out of a maximum of 10), but at line 8 we are trying to set the value of a fourth element.
You cannot set a non-existent varray or table element if you haven't properly initialized the varray as shown in a previous posting.

Another typical situation involves nested tables:
DECLARE
TYPE table_type IS TABLE OF VARCHAR2(200);
my_table table_type := table_type();
BEGIN
my_table.EXTEND(5);
my_table.TRIM(5);
my_table(3) := 'c';
dbms_output.enable;
dbms_output.put_line('total:'||my_table.COUNT);
END;

Error report:
ORA-06533: Subscript beyond count
ORA-06512: at line 7
06533. 00000 - "Subscript beyond count"
*Cause: An in-limit subscript was greater than the count of a varray
or too large for a nested table.
*Action: Check the program logic and explicitly extend if necessary.
By trimming 5 elements starting from the end of a collection consisting of 5 elements, you are shrinking its size to zero and as a consequence you cannot set the third element to a value, because it's like having unitialized all the elements.

Unfortunately DELETE and TRIM can easily lead to some degree of confusion, because their behavior is not so consistent as it could be:

DECLARE
TYPE table_type IS TABLE OF VARCHAR2(200);
my_table table_type := table_type();
BEGIN
my_table.EXTEND(5);
my_table.DELETE(1,5);
my_table(3) := 'c';
dbms_output.enable;
dbms_output.put_line('total:'||my_table.COUNT);
dbms_output.put_line('last subscript:'||my_table.LAST);
dbms_output.put_line('first subscript:'||my_table.FIRST);
END;

total:1
last subscript:3
first subscript:3
However, if instead of selectively deleting the elements from 1 to 5, you omit the parameters altogether:
DECLARE
TYPE table_type IS TABLE OF VARCHAR2(200);
my_table table_type := table_type();
BEGIN
my_table.EXTEND(5);
my_table.DELETE;
my_table(3) := 'a';
dbms_output.enable;
dbms_output.put_line('total:'||my_table.COUNT);
dbms_output.put_line('last subscript:'||my_table.LAST);
dbms_output.put_line('first subscript:'||my_table.FIRST);
END;

Error report:
ORA-06533: Subscript beyond count
ORA-06512: at line 7
06533. 00000 - "Subscript beyond count"
*Cause: An in-limit subscript was greater than the count of a varray
or too large for a nested table.
*Action: Check the program logic and explicitly extend if necessary.
This happens because the DELETE method without arguments physically removes the collection elements, whereas DELETE(m,n) or DELETE(n) simply erase the contents, making the element null.

See more examples and situations involving DELETE, TRIM and related errors.
-----------------------------------
ORA-06533: Indice inferiore oltre il conteggio
ORA-06533: Subíndice mayor que el recuento
ORA-06533: Subscript més enllà del comptador
ORA-06533: Valeur de l'indice trop grande
ORA-06533: Index oberhalb der Grenze
ORA-06533: Δείκτης εκτός μέτρησης τιμών
ORA-06533: Subscript uden for antal
ORA-06533: Indexvariabel större än faktiskt antal
ORA-06533: Subskript over antall
ORA-06533: Alikomento ylitti määrän
ORA-06533: Számlálón kívüli index érték
ORA-06533: Indicele este mai mare decât dimensiunea tabloului
ORA-06533: Subscript is te hoog.
ORA-06533: Subscrito acima da contagem
ORA-06533: Subscrito para além da contagem
ORA-06533: Индекс выходит за пределы счетчика массива
ORA-06533: Dolní index přesahuje čítač
ORA-06533: Dolný index presahuje počet
ORA-06533: Indeks (współrzędna elementu tablicy) przekracza licznik
ORA-06533: İndis sayımın ötesinde
ORA-06533: Subscript beyond count

See message translations for ORA-06533 and search additional resources

PLS-00306: wrong number or types of arguments in call to 'DELETE'

This compilation error occurs when you specify a parameter for the DELETE method, as follows:
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type();
BEGIN
my_array.EXTEND(10);
my_array.DELETE(4);
dbms_output.enable;
dbms_output.put_line('total:'||my_array.COUNT);
END;

Error report:
ORA-06550: line 6, column 1:
PLS-00306: wrong number or types of arguments in call to 'DELETE'
ORA-06550: line 6, column 1:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:

The DELETE method doesn't accept parameters when it's applied to varrays.
When dealing with varrays you can only clear the whole array by specifying the DELETE method without any parameters.
If you want to remove the last n elements, use the TRIM(n) method instead.
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type();
BEGIN
my_array.EXTEND(10);
my_array.TRIM(4);
dbms_output.enable;
dbms_output.put_line('total:'||my_array.COUNT);
END;

total:6

One or two numeric parameters are allowed in DELETE when the collection is defined as TABLE, as in this example of sparse nested table:
DECLARE
TYPE table_type IS TABLE OF VARCHAR2(200);
my_table table_type := table_type();
BEGIN
my_table.EXTEND(10);
my_table.DELETE(1,10);
my_table(5) := 'a';
dbms_output.enable;
dbms_output.put_line('total:'||my_table.COUNT);
dbms_output.put_line('first subscript:'||my_table.FIRST);
END;

total:1
first subscript:5
See other articles on collection tips and techniques.

Thursday, January 03, 2008

ORA-06531: Reference to uninitialized collection

A recent comment of a reader about an obscure PL/SQL compiler error suggested me to begin writing a few postings about errors that you may come across when working with collections, so this is the first of a series, that i don't know yet how short or long will be, not counting the errors already described in previous articles.

Let's have a look at one of the most common ones as it is reported by SQLDeveloper:
ORA-06531: Reference to uninitialized collection
ORA-06512: at line 10
06531. 00000 - "Reference to uninitialized collection"
*Cause: An element or member function of a nested table or varray
was referenced (where an initialized collection is needed)
without the collection having been initialized.
*Action: Initialize the collection with an appropriate constructor
or whole-object assignment.

What does this mean?
It's easy to explain, let's take the following PL/SQL block:
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type;
BEGIN
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;

ORA-06531: Reference to uninitialized collection
ORA-06512: at line 6
You cannot use the COUNT method without first initializing the collection my_array.
Likewise, you cannot use the LIMIT method either, even if LIMIT refers to the upper bound that was specified in the declaration and theoretically has little to do with the actual varray content (see below for an example).
If you need to store the varray size in a variable for easier referencing for instance or you don't want to clutter the source with such literal values, you'll need to initialize the array first, as explained later on.

my_array has been declared of type array_type, but it has not been initialized and in this situation the collection is said to be atomically NULL. Atomically null means that there are no items whatsoever in the collection that is why you cannot count them.

In order to initialize a collection you must use the type constructor, that strange beast that is named after the collection type (array_type in my example):
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type;
BEGIN
my_array := array_type();
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;
Alternatively and according to a better programming practice, you can initialize the collection at declaration time:
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type();
BEGIN
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;
both programs will return the value 0 (zero) in the dbms_output buffer.

So now we have a VARRAY with zero elements, but we declared it to hold up to 10 items.
Let's have a look at how to initialize this collection with a given number of elements.
DECLARE
TYPE array_type IS VARRAY(10) OF VARCHAR2(200);
my_array array_type := array_type('a','b');
BEGIN
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;
In dbms_output you'll find now the number 2 because we initialized the varray with two elements: a and b.

Imagine however that you have a large number of elements, say 32000, clearly you cannot type all of them in the constructor, so, how do you proceed?

If you are tempted to initialize the last element of the collection, see the next posting, so that is not an option.

How do you fully initialize a 32000 elements varray?

Before adding anything else, let me just suggest to ask yourself whether this is really necessary.
If the answer is yes, then read on, otherwise try to implement some other algorithm that doesn't consume db's resources so savagely...
Are you absolutely sure that this tiny program will not end up being used by dozen of concurrent users?

All right, so here comes into play the EXTEND collection method that allows us to initialize the varray by the desired number of null elements:
DECLARE
TYPE array_type IS VARRAY(32000) OF VARCHAR2(200);
my_array array_type := array_type();
BEGIN
my_array.EXTEND(32000);
dbms_output.enable;
dbms_output.put_line(my_array.COUNT);
END;
Last note: interestingly enough, the official documentation says that this form of EXTEND cannot be used when you impose a NOT NULL constraint on the array (or table) type, but, at least on Oracle XE, this is not true:
DECLARE
TYPE array_type IS VARRAY(32000) OF VARCHAR2(200) NOT NULL;
my_array array_type := array_type();
BEGIN
dbms_output.enable;
my_array.EXTEND(31000);
dbms_output.put_line(my_array.COUNT);
dbms_output.put_line(nvl(my_array(31000),'31000th is null!'));
END;

Indeed if you try to initialize the array using a non empty constructor containing nulls, Oracle will complain at parse time:
DECLARE
TYPE array_type IS VARRAY(32000) OF VARCHAR2(200) NOT NULL;
my_array array_type := array_type(null,null);
BEGIN
dbms_output.enable;
my_array.EXTEND(31000);
dbms_output.put_line(my_array.COUNT);
dbms_output.put_line(nvl(my_array(31000),'31000th is null'));
END;

Error report:
ORA-06550: line 3, column 42:
PLS-00567: cannot pass NULL to a NOT NULL constrained formal parameter
So, either i got it wrong or this is a bug...

Last but not least, let's peek at the most sensible and probably useful way of initializing a collection that is by using bulk SQL:
DECLARE
TYPE array_type IS VARRAY(32000) OF VARCHAR2(200);
my_array array_type := array_type();
upper_bound pls_integer := my_array.LIMIT;
BEGIN
dbms_output.enable;

select 'item_' || n
bulk collect into my_array
from (
select level n
from dual
connect by level <= upper_bound);

dbms_output.put_line(my_array.COUNT);
dbms_output.put_line(nvl(my_array(31000),'31000th is null'));
END;
Please note that i had to explicitly initialize the array because i used LIMIT for retrieving the array upper bound as i don't wanted to hardcode the literal 32000 inside the query, but if you don't use this kind of approach, you can omit the array initialization, it will be performed automatically when BULK COLLECT is performed.

------------------------------------------------
ORA-06531: Riferimento a collection non inizializzata
ORA-06531: Referencia a una recopilación no inicializada
ORA-06531: Referència a recollida no inicialitzada
ORA-06531: Référence à un ensemble non initialisé
ORA-06531: Nicht initialisierte Zusammenstellung referenziert
ORA-06531: Αναφορά σε μη αρχικοποιημένη συλλογή
ORA-06531: Reference til ikke-initialiseret samling
ORA-06531: Referens till ej initierad insamling
ORA-06531: Referanse til uinitialisert samling
ORA-06531: Viittaus alustamattomaan kokoelma
ORA-06531: Inicializálatlan gyűjtőre való hivatkozás
ORA-06531: Referinţă la o colecţie neiniţializată
ORA-06531: Verwijzing naar niet-geïnitialiseerde verzameling.
ORA-06531: Referência para coleta não-inicializada
ORA-06531: Referência a uma recolha não inicializada
ORA-06531: Ссылка на неинициализированный набор
ORA-06531: Odkaz na neinicializovanou skupinu
ORA-06531: Odkaz na neiniciovanú kolekciu
ORA-06531: Odwołanie do nie zainicjowanej kolekcji
ORA-06531: Başlatılmamış koleksiyona başvuru
ORA-06531: Reference to uninitialized collection

See message translations for ORA-06531 and search additional resources

yes you can!

Two great ways to help us out with a minimal effort. Click on the Google Plus +1 button above or...
We appreciate your support!

latest articles