Showing posts with label FORALL. Show all posts
Showing posts with label FORALL. Show all posts

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!

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, October 08, 2008

PLS-00431: bulk SQL attributes must use a single index

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

PLS-00431: bulk SQL attributes must use a single index
ORA-06550: line 15, column 37:
PL/SQL: ORA-00904: : invalid identifier
If you got a message like this, perhaps you tried to execute or compile a
PL/SQL program containing a statement like the following:
DECLARE
TYPE NUM_TAB IS TABLE OF INTEGER INDEX BY PLS_INTEGER;

COLL_OF_FLAGGED NUM_TAB;
COLL_OF_ORDERS NUM_TAB;
COLL_OF_PARTS NUM_TAB;
BEGIN
...
FORALL x IN 1..COLL_OF_FLAGGED.COUNT
UPDATE order_parts
SET is_damaged = 'Y'
WHERE order_id = COLL_OF_ORDERS(COLL_OF_FLAGGED(x))
AND part_id = COLL_OF_PARTS(COLL_OF_FLAGGED(x));
...
END;
I highlighted in red color the cause of problem: nested collection indexes.
FORALL cannot cope with nested collection indexes on 10GR2 and earlier (on 11G i didn't tried yet), which means that you will need to rewrite the statement as a non-bulk UPDATE loop structure:
...
FOR x IN 1..COLL_OF_FLAGGED.COUNT
LOOP
UPDATE order_parts
SET is_damaged = 'Y'
WHERE order_id = COLL_OF_ORDERS(COLL_OF_FLAGGED(x))
AND part_id = COLL_OF_PARTS(COLL_OF_FLAGGED(x));
END LOOP;
...
or alternatively you might want to modify the table structure and "demote" the pair (order_id, part_id) to a simple unique constraint, while creating a single column primary key based on a sequence number, which would probably allow you to rewrite the statement above in the following way (assuming that the collection COLL_OF_FLAGGED contains the new primary key values):
...
FORALL x IN 1..COLL_OF_FLAGGED.COUNT
UPDATE order_parts
SET is_damaged = 'Y'
WHERE order_part_id = COLL_OF_FLAGGED(x);
...

See message translations for PLS-00431 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".

A practical example of using global temporary tables within Apex: displaying dbms_output messages

It's late so i am not going to make it longer than necessary.

Some days ago i read an interesting question in the OTN forum and i threw my 50 cent advice (well, honestly it may have been worth 60 or 70 cents as i did some homework before shooting...).

In short, a user was asking for a method for display the messages sent to the DBMS_OUTPUT buffer.

This question eventually aroused my fantasy, so i wrote a generic function for storing dbms_output messages into a generic table (a table in the same schema or in another one, provided the user has INSERT privilege).
Having used up all my fantasy in the development, i decided to call the function STORE_DBMS_OUTPUT.

In my test apex application the staging table is defined as follows:

create global temporary table
staging_table(
id number(5,0),
text varchar2(255)
) on commit delete rows
/
The source code of the function can be downloaded from Yocoya.com.

The function takes 4 parameters and returns a number:

p_owner owner of the staging table, default to current user.
p_table name of the table, mandatory
p_txt_col name of the column for storing messages, typically VARCHAR2(255)
p_cnt_col optional name of the column for storing message sequence number

The last parameter may be omitted if you already have a before-insert trigger on the table where you stuff an oracle sequence.

The function retrieves all the content in the buffer and the value it returns is the number of messages it found.

Although i wrote the function for using within an Apex page, it's completely independent from it and it can be used in different situations.
The table where the messages are going to be stored doesn't necessarily have to be a Global Temporary Table (GTT), although in that case you must devise a method for keeping it clean without interfering with other sessions and/or users.

Users who are interested in looking at a working example of dynamic SQL in combination with FORALL and bulk binds may be interested in this function too.
Note also the n-tier usage of DUAL i talked about some time ago that i use here for initializing a pl/sql table containing the subscripts, i'll probably write something in another posting on this subject.

In case you are interested in using this function inside apex, here is how i did it (please note that this techniques are still under testing).

before header processes
step 10: truncate staging table (just in case) and call the desired procedure returning dbms_output messages (conditional on request=OUTPUT)
step 20: get the dbms_output and store it in a global temporary table (conditional on request=OUTPUT), as follows:

declare
n integer;
begin
n := STORE_DBMS_OUTPUT(
p_owner => :OWNER,
p_table => 'STAGING_TABLE',
p_txt_col => 'TEXT',
p_cnt_col => 'ID');
end;


regions
sequence 10: some region with a button branching to the same page, setting request = OUTPUT
sequence 20: report region on staging table (conditional on request=OUTPUT)
sequence 30: PL/SQL type region truncating the table (conditional on request=OUTPUT), may be omitted altogether if GTT is defined as on commit delete rows.

As you see here i have up to two truncates statements. This is just to avoid having an idle session holding some temporary segment as i related in my previous posting.
If the GTT is defined as on commit delete rows, you can probably omit both truncates, i just prefer to leave a clean table after using it.

That's it.

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