Showing posts with label collections. Show all posts
Showing posts with label collections. Show all posts

Monday, October 20, 2014

ORA-20104: create_collection_from_query ParseErr:ORA-00918: column ambiguously defined

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.
 
If you are wondering why you are getting this error message after following the example in the documentation describing procedure CREATE_COLLECTION_FROM_QUERYB2 in the APEX_COLLECTION API, the quick answer is that the sample code is flawed.

ORA-20104: create_collection_from_queryb2 Error:ORA-20104: create_collection_from_query 
ParseErr:ORA-00918: column ambiguously defined

The problem is in the missing aliases for the null columns:

Begin
    l_query := 'select empno, sal, comm, deptno, null, hiredate
              , null, null, null, null, ename, job, mgr from emp';
    APEX_COLLECTION.CREATE_COLLECTION_FROM_QUERYB2 (
        p_collection_name => 'EMPLOYEES', 
        p_query => l_query,
        p_generate_md5 => 'NO');
End;

After adding the aliases, the API call works without a hitch.
 
Begin
    l_query := 'select empno, sal, comm, deptno, null as n5, hiredate
              , null as d2, null as d3, null as d4, null as d5
              , ename, job, mgr from emp';
    APEX_COLLECTION.CREATE_COLLECTION_FROM_QUERYB2 (
        p_collection_name => 'EMPLOYEES', 
        p_query => l_query,
        p_generate_md5 => 'NO');
End;

 See message translations for ORA-00918 and search additional resources.

Thursday, April 08, 2010

When APEX_UTIL.STRING_TO_TABLE is not enough

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

Yesterday i needed to find a way to ensure that a list values entered manually by a user were unique.
If these values came from a table, it would be fairly easy to build a query returning DISTINCT values, but in this case values are pulled in as comma separated values.

This type of problems are suitable for set operations using PL/SQL collections because we are dealing with a limited, although unspecified amount of values that are not permanently stored as individual rows in a table.
PL/SQL comes with an array of operators for manipulating collections as sets and you can find a few basic examples in PL/SQL Language Reference manual. The functional description of each set operator can be found in the SQL Reference instead. The links refer to Oracle version 11gR2 but these functions are available at least since version 10gR1. There is also a nice tutorial by Steven Feuerstein discussing these functions in an old Oracle Magazine issue, so you may want to check it out.

It is worth noting that such set operators work only on PL/SQL nested tables, but not on associative arrays or varying arrays and this makes a difference as we shall see.

One of the most common operations involving collections in Oracle Application Express is to take some delimited string (by commas, semicolons, colons or spaces typically) and convert into a collection or viceversa using function APEX_UTIL.STRING_TO_TABLE (or its counterpart APEX_UTIL.TABLE_TO_STRING). Unfortunately the result of function STRING_TO_TABLE is APEX_APPLICATION_GLOBAL.VC_ARR2, that is an associative array, not a PL/SQL table, which means that an attempt to use the SET function on it will lead to the following run time error:
PLS-00306: wrong number or types of arguments in call to 'SET'
This error can be puzzling at first because the difference between a PL/SQL table and an associative array indexed by positive integers is very subtle and the only way to say what kind of collection type is APEX_APPLICATION_GLOBAL.VC_ARR2 is to look at its definition, which can be not so straightforward for an unprivileged user. So, once we are aware of the fact that APEX_APPLICATION_GLOBAL.VC_ARR2 is an associative array, we are basically stuck at the original problem.
How do we move forward?

The options are the following:
  1. stick to the apex STRING_TO_TABLE function and copy the values one by one into a PL/SQL nested table, using a FOR...LOOP structure.
  2. write you own STRING_TO_TABLE function and return a PL/SQL table instead.
  3. find another way of quickly transforming the associative array into a PL/SQL table other than #1.
  4. normalize the values yourself instead of using SET.
  5. ?
I opted for option #2 because from a performance point of view is the most effective probably and it also gave me some additional flexibility, but let's review each point first.

Option #1 saves me from rewriting a function but it forces me to perform a not so efficient extra loop that it's also requiring extra memory allocation.
Option #2 is probably comparable in terms of memory and speed although i hate to "reinvent the wheel", unless this is absolutely necessary. It gives me also the advantage of further customization, like space trimming and case insensitive or sensitive comparison.
Option #3 cannot be performed with CAST, so i guess there is no easy way of doing it unless we fall back to option #1, but i do not exclude it could be done in another fashion.
Option #4 is probably the worst choice because i need to replace an efficient piece of C code with my own PL/SQL logic based on a search loop.
Option #5 is an open question, if you know a better way of doing this, please let me know.

So, after rewriting my custom STR2TAB function, i could finally leverage the power of SET function and simply return the result in the following manner:
DECLARE
str varchar2(1000) := 'A,B,C,A,D';
TYPE nested_typ IS TABLE OF VARCHAR2(255);
nt1 nested_typ;
nt2 nested_typ;
PROCEDURE print_nested_table(p_nt nested_typ) IS
output VARCHAR2(128);
BEGIN
IF p_nt IS NULL THEN
DBMS_OUTPUT.PUT_LINE('Results: ');
RETURN;
END IF;
IF p_nt.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('Results: empty set');
RETURN;
END IF;
FOR i IN p_nt.FIRST .. p_nt.LAST
LOOP
output := output || p_nt(i) || ' ';
END LOOP;
DBMS_OUTPUT.PUT_LINE('Results: ' || output);
END;

FUNCTION str2tab(
p_text in varchar2,
p_separator in varchar2 default ',')
return nested_typ
as
l_string varchar2(32767) := p_text || p_separator;
l_data nested_typ := nested_typ();
n binary_integer;
begin
loop
exit when l_string is null;
n := instr( l_string, p_separator );
l_data.extend;
l_data(l_data.count) := substr(l_string, 1, n-1);
l_string := substr(l_string, n + length(p_separator));
end loop;

return l_data;
END;
BEGIN
nt1 := str2tab(str);
nt2 := set(nt1);
print_nested_table(nt2);
END;
/

Results: A B C D
In conclusion, apex function APEX_UTIL.STRING_TO_TABLE does an excellent job when you are taking the values as they have been typed in, but if you need to work on the values as a set, then you'll need to come up with your own custom function.

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

Thursday, July 16, 2009

ORA-22908: reference to NULL table value

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

This exception is raised when an attempt of updating the rows of an atomically null nested table column is made.
For instance:

create type complex_number as (real_part number, imaginary_part number);

create or replace type complex_tab as table of complex_number;

create table complex_sets (
set_id number,
set_name varchar2(50),
set_values complex_tab)
nested table set_values store as nested_complex_set_values;

insert into COMPLEX_SETS (SET_ID,SET_NAME,SET_VALUES)
values (1,'first set',null);

update table(select set_values
from complex_sets
where set_id = 1)
set real_part = real_part + 1
where real_part = 0;

ORA-22908: reference to NULL table value
The nested table underlying the column set_values for row having set_id = 1 has not been initialized, in other words it is said to be atomically null.
Let's repeat the last steps in order to initialize the nested table, ORA-22908 will disappear:

delete from complex_sets where set_id = 1;

insert into COMPLEX_SETS (SET_ID,SET_NAME,SET_VALUES)
values (1,'first set',complex_tab(complex_number(0,0), complex_number(0,1)));

update table(select set_values
from complex_sets
where set_id = 1)
set real_part = real_part + 1
where real_part = 0;
--

2 rows updated

select a.set_id, rownum, b.real_part, b.imaginary_part
from complex_sets a, table(a.set_values) b
where set_id = 1;

SET_ID ROWNUM REAL_PART IMAGINARY_PART
------ ------ --------- --------------
1 1 1 0
1 2 1 1
I initialized the nested table with two rows, each containing a complex_number (user-defined) type, note however that it is possible to initialize the nested table to an empty set by specifying the nested table type constructor:

delete from complex_sets where set_id = 1;

insert into COMPLEX_SETS (SET_ID,SET_NAME,SET_VALUES)
values (1,'first set',complex_tab());

update table(select set_values
from complex_sets
where set_id = 1)
set real_part = real_part + 1
where real_part = 0;

--
0 rows updated
Whether it is preferable to have atomically null nested tables or nested tables initialized to an empty set depends on your application. If most nested table columns are going to remain null, then it can make sense to keep them atomically null, otherwise you might consider initializing the nested table to an empty set either using the default clause for the column or inside a before insert trigger or handle ORA-22908 at the application code level, depending on performance considerations.
-- initialize the nested table to an empty set using the DEFAULT column clause

drop table complex_sets cascade constraints;

create table complex_sets (
set_id number,
set_name varchar2(50),
set_values complex_tab default complex_tab())
nested table set_values store as nested_complex_set_values;

insert into COMPLEX_SETS (SET_ID,SET_NAME)
values (1,'first set');

update table(select set_values
from complex_sets
where set_id = 1)
set real_part = real_part + 1
where real_part = 0;
--

0 rows updated

-- handling ORA-22908 inside the application
declare
l_set number := 1;
null_table exception;
pragma exception_init(null_table, -22908);
begin
update table(select set_values
from complex_sets
where set_id = l_set)
set real_part = real_part + 1
where real_part = 0;
exception
when null_table then
update complex_sets
set set_values = complex_tab()
where set_id = l_set;
end;
/

See message translations for ORA-22908 and search additional resources.

Wednesday, December 03, 2008

PLS-00457: expressions have to be of SQL types

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

PLS-00457: expressions have to be of SQL types

This error can be seen when attempting to compile a PL/SQL unit containing a FORALL statement in combination with EXECUTE IMMEDIATE, as follows:

create table tab_pls_435 (
col_a number,
col_b varchar2(30));

create or replace
procedure test_pls_435
is

type tab_type is table of tab_pls_435%rowtype;
plsql_rec_tab tab_type := tab_type();

begin
plsql_rec_tab.extend;
plsql_rec_tab(1).col_a := 100;
plsql_rec_tab(1).col_b := 'TEST';

forall i in 1..plsql_rec_tab.count
execute immediate
'insert into tab_pls_435
values :1'
using
plsql_rec_tab(i);
end;
The PL/SQL parser doesn't like the syntax of the FORALL statement in combination with EXECUTE IMMEDIATE and a collection of RECORD data type.

Note however that the program can be successfully compiled and executed if we make the FORALL statement static (as opposed to native dynamic):

create or replace
procedure test_pls_435
is

type tab_type is table of tab_pls_435%rowtype;
plsql_rec_tab tab_type := tab_type();

begin
plsql_rec_tab.extend;
plsql_rec_tab(1).col_a := 100;
plsql_rec_tab(1).col_b := 'TEST';

forall i in 1..plsql_rec_tab.count
insert into tab_pls_435
values plsql_rec_tab(i);
end;

See also PLS-00436 for a list of other possibilities with native dynamic SQL.

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

PLS-00435: DML statement without BULK In-BIND cannot be used inside FORALL

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

PLS-00435: DML statement without BULK In-BIND cannot be used inside FORALL
You may see this error when you attempt to compile a PL/SQL unit containing a FORALL statement combined with an EXECUTE IMMEDIATE statement like, as follows:
create or replace
procedure test_pls_435
is

type tab_type is table of tab_pls_435%rowtype; -- defines a RECORD data type
plsql_rec_tab tab_type := tab_type(); -- defines a collection of RECORDs

begin
plsql_rec_tab.extend;
plsql_rec_tab(1).col_a := 100;
plsql_rec_tab(1).col_b := 'TEST';

forall i in 1..plsql_rec_tab.count
execute immediate
'insert into tab_pls_435
values (:1, :2)'
using
plsql_rec_tab(i).col_a
,plsql_rec_tab(i).col_b;
end;
Whilst it is possible to specify bulk FORALL statements, in practice there are limitations when RECORD data type collections are involved. If PLS-00436 is present in the list of parsing errors, then i suggest you to read that entry for a detailed explanation and try out alternative solutions.

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

PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND table of records

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

You may get the following error along with PLS-00435.
PLS-00436: implementation restriction:
cannot reference fields of BULK In-BIND table of records
if you attempt to compile a PL/SQL procedure containing native dynamic SQL like this:
create table tab_pls_435 (
col_a number,
col_b varchar2(30));

create or replace
procedure test_pls_435
is

type tab_type is table of tab_pls_435%rowtype;
plsql_rec_tab tab_type := tab_type();

begin
plsql_rec_tab.extend;
plsql_rec_tab(1).col_a := 100;
plsql_rec_tab(1).col_b := 'TEST';

forall i in 1..plsql_rec_tab.count
execute immediate
'insert into tab_pls_435
values (:1,:2)'
using
plsql_rec_tab(i).col_a
,plsql_rec_tab(i).col_b;

end;

The problem here is that i defined a PL/SQL table of records and i am trying to bulk bind the individual fields of the record. The main problem however is with the EXECUTE IMMEDIATE type of native dynamic SQL that is also considered invalid thus returning PLS-00435.

The solution is to completely rewrite the code, depending on the specific case, in one of these ways:

1) by eliminating the bulk FORALL (slower solution)
 ...
for i in 1..plsql_rec_tab.count
loop
execute immediate
'insert into tab_pls_435
values (:1,:2)'
using
plsql_rec_tab(i).col_a
,plsql_rec_tab(i).col_b;
end loop;
...
2) by splitting the individual record fields into different collections, thus eliminating the RECORD data type collection, while keeping the bulk FORALL:
create or replace
procedure test_pls_435
is

type col_a_tab_type is table of tab_pls_435.col_a%type;
type col_b_tab_type is table of tab_pls_435.col_b%type;
plsql_col_a_tab col_a_tab_type := col_a_tab_type();
plsql_col_b_tab col_b_tab_type := col_b_tab_type();

begin
plsql_col_a_tab.extend;
plsql_col_b_tab.extend;
plsql_col_a_tab(1) := 100;
plsql_col_b_tab(1) := 'TEST';

forall i in 1..plsql_col_a_tab.count
execute immediate
'insert into tab_pls_435
values (:1, :2)'
using
plsql_col_a_tab(i), plsql_col_b_tab(i);

end;
3) It's worth noting that using native dynamic SQL makes things more complex.
Indeed if there isn't a valid reason for using native dynamic SQL, we could rewrite the code getting rid of EXECUTE IMMEDIATE and using a shorter syntax,:
create or replace
procedure test_pls_435
is

type tab_type is table of tab_pls_435%rowtype;
plsql_rec_tab tab_type := tab_type();

begin
plsql_rec_tab.extend;
plsql_rec_tab(1).col_a := 100;
plsql_rec_tab(1).col_b := 'TEST';

forall i in 1..plsql_rec_tab.count
insert into tab_pls_435
values plsql_rec_tab(i);

end;
The shorter syntax requires that the PL/SQL RECORD structure matches exactly the table structure. Trying to perform the insert on a subset of columns may lead to ORA-03001.

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

ORA-03001: unimplemented feature

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

ORA-03001: unimplemented feature
This error can be returned attempting to compile a PL/SQL unit containing a FORALL statement, as follows:
create table tab_pls_435 (
col_a number,
col_b varchar2(30));

create or replace
procedure test_pls_435
is

type rec_type is record (col_b tab_pls_435.col_b%type);
type tab_type is table of rec_type;
plsql_rec_tab tab_type;

begin
plsql_rec_tab.extend;
plsql_rec_tab(1).col_b := 'TEST';

forall i in 1..plsql_rec_tab.count
insert into tab_pls_435 (col_b)
values plsql_rec_tab(i);

end;
My current understanding is that currently is not possible to specify a subset of columns in an bulk INSERT statement that bulk binds a RECORD type collection inside FORALL.
Note that in the code above, if we get rid of column specification, we incur in ORA-00947, however this is caused by the mismatching number of columns between the table structure and the PL/SQL RECORD data type.
If you can redefine the RECORD definition to match the table structure, then the code can be compiled and executed successfully, as follows:

create or replace
procedure test_pls_435
is

type tab_type is table of tab_pls_435%rowtype;
plsql_rec_tab tab_type := tab_type();

begin
plsql_rec_tab.extend;
plsql_rec_tab(1).col_a := 100;
plsql_rec_tab(1).col_b := 'TEST';

forall i in 1..plsql_rec_tab.count
insert into tab_pls_435
values plsql_rec_tab(i);

end;
I ignore if ORA-03001 can be returned also in different situations.

See message translations for ORA-03001 and search additional resources.

Updated January 4, 2008:
If you are getting this error when using a CAST/MULTISELECT construct, check out the comments section!

Monday, December 01, 2008

ORA-01007: variable not in select list

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

You can get the following error:
ORA-01007: variable not in select list
when you are trying to perform a SELECT... BULK COLLECT INTO using a collection of RECORD data type and the column list in the query differs from the column list of the RECORD data type.
For instance, i was executing a program containing the following code, where the RECORD definition (implicitly created with %rowtype) is based on the structure of a local table but the data is retrieved from a remote table via a db link and the two structures are supposedly identical (but they are not):
declare
type prf_runs_type is table of plsql_profiler_runs%rowtype;
rmt_runs_tab prf_runs_type;
...
begin
...
execute immediate
'SELECT *
FROM plsql_profiler_runs@yy_rmt_link
ORDER BY runid'
bulk collect into rmt_runs_tab;
...
end;
When i executed the program, i got ORA-01007 because the remote table had a missing column.
Indeed i forgot to update the remote table definition and add the same column i have in my local database.
Note also that implicit columns defined with statements like SELECT * ... are a typical source of problems because exact column positioning becomes a critical factor. If instead we specify a fixed list of columns, the column positioning issue will be ruled out. A mismatching columns order will easily lead to ORA-01858.

Likewise, we might want to define a RECORD structure that is a subset of the original columns.
In this case we have two options:
  1. manually define the record type, specifying the desired columns explicitly;
  2. define a view containing the columns in the desired sequence and use %ROWTYPE to implicitly create a RECORD type based on the view definition.
I prefer the latter approach because theoretically it makes possible to shrink or enlarge the column list without touching the program, under certain conditions.

See message translations for ORA-01007 and search additional resources.

Thursday, November 27, 2008

ORA-00947: not enough values

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

You may encounter the following exception:
ORA-00947: not enough values
when you attempt to compile a PL/SQL unit containing a FORALL construct in conjunction with a collection based on a record data type:
DECLARE
TYPE prf_runs_type IS TABLE OF plsql_profiler_runs%ROWTYPE;
rmt_runs_tab prf_runs_type;
...
BEGIN
...
FORALL i IN 1..rmt_runs_tab.COUNT
INSERT INTO plsql_profiler_runs
(runid, related_run, run_owner, run_date, run_comment, ..., spare1)
VALUES rmt_runs_tab(i);
...
END;
The problem with the FORALL statement above is in that i am specifying the list of fields to be inserted whereas the PL/SQL parser is expecting a simpler syntax:

FORALL i IN 1..rmt_runs_tab.COUNT
INSERT INTO plsql_profiler_runs
VALUES rmt_runs_tab(i);
Note that bulk operations involving record data types are perfectly legal, however i could not verify if this type of constructs is valid on Oracle versions prior to 10gR2.

ORA-00947 is also returned in trivial situations like INSERT statements containing an insufficient number of parameters in the VALUES clause or too few columns in a SELECT list as in these examples:

INSERT INTO plsql_profiler_runs
(runid, run_owner)
VALUES(1);

INSERT INTO plsql_profiler_runs
(runid, run_owner)
SELECT 1 FROM dual;

See message translations for ORA-00947 and search additional resources.

Wednesday, January 09, 2008

PLS-00382: expression is of wrong type

There are at least two distinct situations where this error can be returned, the former scenario involves collections and the latter is a relatively "simple" implicit conversion problem.
PLS-00382: expression is of wrong type
Note that PLS-00382 can be raised when you try assign a value that cannot be converted implicitly into the target data type, as in the following example:
declare
a integer;
b timestamp := systimestamp;
begin
a := b;
dbms_output.put_line(b);
end;

Error report:
ORA-06550: line 5, column 8:
PLS-00382: expression is of wrong type
ORA-06550: line 5, column 3:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
See the following Oracle 10G document about allowed implicit conversions, where, for some reason, the implicit conversion between timestamps and dates is not mentioned and the new 11G document doesn't include it either, but is indeed perfectly working:
DECLARE
a DATE;
b TIMESTAMP WITH TIME ZONE := systimestamp;
begin
a := b;
dbms_output.put_line(a);
dbms_output.put_line(b);
end;

09-JAN-08
09-JAN-08 02.37.02.339176 PM +01:00

However you should always avoid using implicit conversions as they are affected by the server and client locale settings and a program that works in your environment may stop working altogether when executed from another client.

PLS-00642: local collection types not allowed in SQL statements

This error is easily explained:
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 := VALS.COUNT;

select column_value
bulk collect into LLISTA
from table(cast (VALS as string_table_type));

dbms_output.put_line(number);
dbms_output.put_line(LLISTA(number));
end;

Error report:
ORA-06550: line 13, column 21:
PLS-00642: local collection types not allowed in SQL statements
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:

Collection VALS is not a SQL object type (a user-defined type anyway), but it's a PL/SQL local type, that is a type defined inside the program.
Casting the local type to an external object type, string_table_type in the sample code above, is not sufficient to make it work, you need to change the collection type altogether, as shown in the PL/SQL snippet of the related article.

See message translations for PLS-00642 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-00567: cannot pass NULL to a NOT NULL constrained formal parameter

ORA-06550: line 3, column 42:
PLS-00567: cannot pass NULL to a NOT NULL constrained formal parameter
See a typical scenario described in the linked article.

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

Friday, August 03, 2007

PLS-00430: FORALL iteration variable XYZ is not allowed in this context

As i wrote in my previous posting, it was my intention to explain why i used a collection for storing the indices of the messages collected from dbms_output buffer.

Let's take my FORALL statement from the function source code and see where is the problem:

forall i in 1..messages_tab.count
execute immediate
'insert into '|| p_owner||'.'||p_table
||'('||p_txt_col||',' || p_cnt_col ||') values(:1, :2)'
using messages_tab(i), subscripts_tab(i);
In a perfect world i could have written (without using an additional collection):
forall i in 1..messages_tab.count
execute immediate
'insert into '|| p_owner||'.'||p_table
||'('||p_txt_col||',' || p_cnt_col ||') values(:1, :2)'
using messages_tab(i), i;

However if you try to compile a program containing this FORALL statement, you'll get:

PLS-00430: FORALL iteration variable I is not allowed in this context

This is why i decided to use the additional collection called subscripts_tab.

This collection contains the subscripts of the elements themselves plus an offset corresponding to the size of the "chunk" of dbms_ouput messages that i retrieve every time (100 at a time if you look at the value maxbuf), so i populate the collection with the help of a bizarre hierarchical SQL statement courtesy of Tom Kyte using BULK COLLECT:

select level + k
bulk collect into subscripts_tab
from dual
connect by level <= maxbuf;
And this should end the dbms_output tormentone, a nice italian word meaning "some gossip, tune or jingle that keeps buzzing in your head and you can't get rid of".

Thursday, February 01, 2007

PLS-00315 and PLS-00657

Have you ever tried to create an associative array with index by other than binary_integer?

In Oracle 8i, if i remember well, binary_integer was the only option available.

Starting from 9i, you could define a varchar2 indexed array, making oracle arrays very similar to the associative arrays found in other programming languages.
However there are still some limitations, for instance you cannot create a collection based on a date index and a declaration like the following would return a compilation error:
DECLARE
TYPE bank_holiday_tab_type IS
TABLE OF VARCHAR INDEX BY DATE;
current_year_tab bank_holiday_tab_type;
BEGIN
...
END;
PLS-00315 Implementation restriction:
unsupported table index type.

But not all is lost.
An index-by-date array could be easily simulated by converting the date value into a proper varchar2 type value using function TO_CHAR with the desired date format mask.

The drawback when using such associative arrays, is in that you cannot use BULK COLLECT or FORALL constructs, because they work only with numerical integer indices.

In case you attempt to use one of such statements, you'll get an exception at compile-time like the following:

PLS-00657: Implementation restriction:
bulk SQL with associative arrays with
VARCHAR2 key is not supported.

The reason is quite simple, it's impossible for Oracle to predict what the next VARCHAR2 subscript should be, whereas in the case of a binary integer subscript it is just automatically filling up the array with consecutive numbers, starting from 1.

In other words a normal array is something like:
A(1), A(2),... A(n)

Where the value of each element can be any of any type you like, including a user defined object type.

On the contrary an associative array is something like:

A(string1), A(string2),... A(stringn)

If you look at the syntax of BULK COLLECT, you can see that there is no way to specify a mapping between the value being returned from the table and its corresponding index.

For instance, let's take my original array definition and transform it into a varchar2(8) indexed array.
Suppose we need to fill up the aforementioned array of dates, where each value is a string indicating the bank holiday name.
There is no way of doing this with BULK COLLECT.

CREATE TABLE bank_holidays(
bank_holiday DATE,
bank_holiday_name VARCHAR2(50));

DECLARE
TYPE bank_holiday_tab_type IS
TABLE OF VARCHAR2(50)
INDEX BY VARCHAR2(8);
current_year_tab bank_holiday_tab_type;
BEGIN
SELECT TO_CHAR(bank_holiday,'YYYYMMDD'),
bank_holiday_name

BULK COLLECT INTO current_year_tab
FROM dates_of_year;
...
END;

As you see we would need to stuff a pair of table columns (the subscript and its value) as a single element of the array, but BULK COLLECT is not designed to do that.

For the sake of discussion, let's suppose it makes some sense to get rid of the column bank_holiday_name from the last sql statement above, just to see what happens when we try to compile this program in 10g.

Well, all we get is:

PLS-00657: Implementation restriction:
bulk SQL with associative arrays with
VARCHAR2 key is not supported

So, while we wait until oracle comes up with a statement syntax extension like:

SELECT BULK_PAIR(TO_CHAR(bank_holiday,'YYYYMMDD'), bank_holiday_name)
BULK COLLECT INTO current_year_tab
FROM bank_holidays;
we can stick to a classic and slower explicit cursor doing the job:
DECLARE
TYPE bank_holiday_tab_type IS
TABLE OF VARCHAR2(50) INDEX BY VARCHAR2(8);
current_year_tab bank_holiday_tab_type;
BEGIN
FOR each_record IN (
SELECT TO_CHAR(bank_holiday,'YYYYMMDD') bank_holiday$,
bank_holiday_name
FROM bank_holidays)
LOOP
current_year_tab(each_record.bank_holiday$)
:= each_record.bank_holiday_name;
END LOOP;
END;

or perhaps define a custom type like this:

CREATE TYPE bank_holiday_type AS
OBJECT (bank_holiday DATE, bank_holiday_name VARCHAR2(50));
and then use BULK COLLECT on an array of bank_holiday_type, as follows:
DECLARE
TYPE bank_holiday_tab_type IS
TABLE OF bank_holiday_type INDEX BY BINARY_INTEGER;
current_year_tab bank_holiday_tab_type;
BEGIN

SELECT
bank_holiday_type(
TRUNC(bank_holiday),
bank_holiday_name)
BULK COLLECT INTO current_year_tab
FROM bank_holidays;
END;

Happy collecting.

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