PL/SQL: Selecting from a table into an assoc array - oracle

I am trying to select data into a pl/sql associative array in one query. I know I can do this with a hardcoded key, but I wanted to see if there was some way I could reference another column (the key column) instead.
DECLARE
TYPE VarAssoc IS TABLE OF varchar2(2) INDEX BY varchar2(3);
vars VarAssoc;
BEGIN
SELECT foo, bar INTO vars(foo) FROM schema.table;
END;
I get an error saying foo must be declared when I do this. Is there some way to create my associate array in a single query or do I need to fall back on a FOR loop?

Just read your comment on APC's answer, it sounds like you figured this out on your own. But I figured I'd put the answer in anyway for future searchers.
This is simpler code, but does not have the speed advantage of using BULK COLLECT. Just loop through the rows returned by the query and set the elements in the associative array individually.
DECLARE
TYPE VarAssoc IS TABLE OF varchar2(200) INDEX BY varchar2(30);
vars VarAssoc;
BEGIN
FOR r IN (SELECT table_name,tablespace_name FROM user_tables) LOOP
vars(r.table_name) := r.tablespace_name;
END LOOP;
dbms_output.put_line( vars('JAVA$OPTIONS') );
END;

It would be neat if it were possible but that isn't a straightforward way of acheiving this.
What we can do is load the data into a regular PL/SQL collection and then load that into an associative array. Whethter this is faster than just looping round the table is a matter of tatse: it probably doesn't matter unless we're dealing with loads of data.
Given this test data ...
SQL> select * from t23
2 order by c1
3 /
C1 C2
-- ---
AA ABC
BB BED
CC CAR
DD DYE
EE EYE
ZZ ZOO
6 rows selected.
SQL>
...we can populate an associative array in two steps:
SQL> set serveroutput on
SQL>
SQL> declare
2 type varassoc is table of varchar2(3) index by varchar2(2);
3 vars varassoc;
4
5 type nt is table of t23%rowtype;
6 loc_nt nt;
7
8 begin
9 select * bulk collect into loc_nt from t23;
10 dbms_output.put_line('no of recs = '||sql%rowcount);
11
12 for i in loc_nt.first()..loc_nt.last()
13 loop
14 vars(loc_nt(i).c1) := loc_nt(i).c2;
15 end loop;
16
17 dbms_output.put_line('no of vars = '||vars.count());
18
19 dbms_output.put_line('ZZ = '||vars('ZZ'));
20
21 end;
22 /
no of recs = 6
no of vars = 6
ZZ = ZOO
PL/SQL procedure successfully completed.
SQL>
The real question is probably whether populating an associative array performs better than just selecting rows in the table. Certainly if you have 11g Enterprise edition you should consider result set caching instead.

are you absolutely married to associative arrays? And I assume that you are doing this because you want to be able to do a lookup against the array using a character key.
If so, have you considered implementing this as a collection type instead?
e.g.
CREATE OR REPLACE TYPE VAR_ASSOC as OBJECT(
KEYID VARCHAR2(3),
DATAVAL VARCHAR2(2)
)
/
CREATE OR REPLACE TYPE VAR_ASSOC_TBL AS TABLE OF VAR_ASSOC
/
CREATE OR REPLACE PROCEDURE USE_VAR_ASSOC_TBL
AS
vars Var_Assoc_tbl;
-- other variables...
BEGIN
select cast ( multiset (
select foo as keyid,
bar as dataval
from schema.table
) as var_Assoc_tbl
)
into vars
from dual;
-- and later, when you want to do your lookups
select ot.newfoo
,myvars.dataval
,ot.otherval
into ....
from schema.other_Table ot
join table(vars) as myvars
on ot.newfoo = myvars.keyid;
end;
/
This gives you the lookup by character key value and lets you do everything in bulk.

Related

why this simple programm creates an end-of-file on communicatoin channel error

CREATE OR REPLACE TYPE a IS OBJECT
(
b integer,
c varchar2(10)
);
/
declare
cursor ca return a is select 1,'e' from dual;
va a;
begin
null;
for cur in ca
loop
DBMS_OUTPUT.PUT_LINE('do nothing');
end loop;
end;
ORA-03113: end-of-file on communication channel
Process ID: 803778
Session ID: 64 Serial number: 4181
the loop as only one element and fast nothing is done in the loop.
But I get the error end-of-file communication channel
As #littlefoot said it works fine if I use a record defined in a package or no record at all. I don't know why it doesn't work with an object
code
Simply put, you can't do that.
Oracle does not support returning object types from a cursor. Cursors *always* return a rowtype. So to do something similar to what you want, you can define a table which has a column of the object type you're interested in, and then have the cursor return the rowtype of that table - i.e. something like:
CREATE OR REPLACE TYPE a IS OBJECT
(
b integer,
c varchar2(10)
);
CREATE TABLE tt
(t_a a);
INSERT INTO tt VALUES (a(1, 'e'));
declare
cursor ca return tt%ROWTYPE is select t_a from tt;
an_a a;
begin
FOR aRow IN ca LOOP
an_a := aRow.t_a;
dbms_output.put_line('an_a.b=' || an_a.b);
dbms_output.put_line('an_a.c=''' || an_a.c || '''');
end loop;
end;
db<>fiddle here
You never said what a is.
Documentation says that - if you use return clause, then it returns rowtype.
RETURN: Specifies the datatype of a cursor return value. You can use the %ROWTYPE attribute in the RETURN clause to provide a record type that represents a row in a database table or a row returned by a previously declared cursor. Also, you can use the %TYPE attribute to provide the datatype of a previously declared record.
A cursor body must have a SELECT statement and the same RETURN clause as its corresponding cursor spec. Also, the number, order, and datatypes of select items in the SELECT clause must match the RETURN clause.
ROWTYPE: A record type that represents a row in a database table or a row fetched from a previously declared cursor or cursor variable. Fields in the record and corresponding columns in the row have the same names and datatypes.
So, if your code were like this, it would work:
SQL> set serveroutput on
SQL>
SQL> declare
2 type a is record(val1 number, val2 varchar2(10));
3
4 cursor ca return a is select 1 ,'e' from dual;
5 va a;
6 begin
7 for cur in ca
8 loop
9 DBMS_OUTPUT.PUT_LINE(cur.val1 ||', '|| cur.val2 ||', do nothing');
10 end loop;
11 end;
12 /
1, e, do nothing
PL/SQL procedure successfully completed.
SQL>
Or, simpler, if you remove the return clause from your own code, it would also work:
SQL> declare
2 cursor ca --return a
3 is select 1,'e' from dual;
4 va a;
5 begin
6 null;
7 for cur in ca
8 loop
9 DBMS_OUTPUT.PUT_LINE('do nothing');
10 end loop;
11 end;
12 /
do nothing
PL/SQL procedure successfully completed.
SQL>
If you're asking what's the reason of end-of-file on communication channel, I wouldn't know.

Assign multiple values into variable oracle PLSQL

How to assign multiple values to variable using select query in PLSQL Oracle, example query is below
Select * into v_name from tbl_name;
If you just declare the variable using %rowtype, it won't work if there's not exactly one row in the table (because, if it is empty, select will return no_data_found, and if there are two or more rows, you'll get too_many_rows), e.g.
SQL> declare
2 v_name dept%rowtype;
3 begin
4 select *
5 into v_name
6 from dept;
7 end;
8 /
declare
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 4
But, if you restrict number of rows, it'll work:
SQL> declare
2 v_name dept%rowtype;
3 begin
4 select *
5 into v_name
6 from dept
7 where rownum = 1;
8
9 dbms_output.put_line(v_name.dname);
10 end;
11 /
ACCOUNTING
PL/SQL procedure successfully completed.
SQL>
Though, I believe you'd actually want to use a collection:
SQL> declare
2 type v_dept_rec is record (deptno number, dname varchar2(20), loc varchar2(10));
3 type v_dept_tab is table of v_dept_rec;
4 v_tab v_dept_tab;
5 begin
6 select *
7 bulk collect
8 into v_tab
9 from dept;
10
11 for i in v_tab.first .. v_tab.last loop
12 dbms_output.put_line(v_tab(i).dname);
13 end loop;
14 end;
15 /
ACCOUNTING
RESEARCH
SALES
OPERATIONS
PL/SQL procedure successfully completed.
SQL>
Oracle database and PL/SQL language have two categories of data types: scalar and composite. Scalar data types can store one value. It can be number, character, date or large objects. Composite data types can store multiple values of the same type or of different types. There are two composite data types: collections and records. Collections are sets of components with the same data type. Records are structures with components of different data types. If you need to store values of the same type use a collection. If you need to store values of different types use a record.
There are three types of collections in Oracle – variable arrays that have a maximum number of elements, nested tables that are unbounded and can have gaps and associative arrays that can be indexed by strings. I attached an example of nested table because it's more flexible.
set serveroutput on;
declare
type employees is table of varchar2(30);
employee employees;
first integer;
last integer;
begin
employee := employees('Kent', 'Wayne', 'Allen', 'Prince');
first := employee.first;
last := employee.last;
dbms_output.put_line('First index: ' || first);
dbms_output.put_line('Last index: ' || last);
for i in first..last loop
dbms_output.put_line('Element ' || i || ': ' || employee(i));
end loop;
end;
There are three types of records in Oracle - the user defined records with a custom structure, the table based records that copy the structure of a table using the attribute ROWTYPE and the cursor based records that copy the structure of a cursor. I attached an example of user defined record because you can define the exact fields you need.
set serveroutput on;
declare
type t_employees is record (name varchar2(30), department varchar2(30), salary number);
employee t_employees;
begin
employee.name := 'Quinn';
employee.department := 'DC';
employee.salary := 2500;
dbms_output.put_line(employee.name);
dbms_output.put_line(employee.department);
dbms_output.put_line(employee.salary);
end;
I hope this helps you. Good luck.

Oracle PL/SQL - Show results of declared table

I am using Toad. I have a declaration of a table in a package as follows:
TYPE MyRecordType IS RECORD
(ID MyTable.ID%TYPE
,FIELD1 MyTable.FIELD1%TYPE
,FIELD2 MyTable.FIELD2%TYPE
,FIELD3 MyTable.FIELD3%TYPE
,ANOTHERFIELD VARCHAR2(80)
);
TYPE MyTableType IS TABLE OF MyRecordType INDEX BY BINARY_INTEGER;
There is a procedure (lets say MyProcedure), that is using an object of this table type as input/output. I want to run the procedure and see the results (how the table is filled). So I am thinking I will select the results from the table:
declare
IO_table MyPackage.MyTableType;
begin
MyPackage.MyProcedure (IO_table
,parameter1
,parameter2
,parameter3);
select * from IO_table;
end;
I get the message:
Table or view does not exist (for IO_table). If I remove the select line, the procedure runs successfully, but I cannot see its results. How can I see the contents of IO_table after I call the procedure?
You cannot see the results for a PL/SQL table by using Select * from IO_table
You will need to loop through the collection in the annonymous block.
do something like, given in pseudo code below...
declare
IO_table MyPackage.MyTableType;
l_index BINARY_INTEGER;
begin
MyPackage.MyProcedure (IO_table
,parameter1
,parameter2
,parameter3);
l_index := IO_table.first;
While l_index is not null
loop
dbms_output.put_line (IO_table(l_index).id);
.
.
.
.
l_index :=IO_table.next(l_index_id);
end loop;
end;
You have to do it like this:
select * from TABLE(IO_table);
and, of course you missed the INTO or BULK COLLECT INTO clause
1) You can not use associated arrays in SELECT statement, Just nested tables or varrays declared globally.
2) You should use TABLE() expression in SELECT statement
3) You can't simply use SELECT in PL/SQL code - cursor FOR LOOP or REF CURSOR or BULK COLLECT INTO or INTO must be used.
4) The last but not least - please study the manual:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28371/adobjcol.htm#ADOBJ00204
Just an example:
SQL> create type t_obj as object( id int, name varchar2(10));
2 /
SQL> create type t_obj_tab as table of t_obj;
2 /
SQL> var rc refcursor
SQL> declare
2 t_var t_obj_tab := t_obj_tab();
3 begin
4 t_var.extend(2);
5 t_var(1) := t_obj(1,'A');
6 t_var(2) := t_obj(2,'B');
7 open :rc for select * from table(t_var);
8 end;
9 /
SQL> print rc
ID NAME
---------- ----------
1 A
2 B

How can I populate in memory tables, in PLSQL?

I don't know if it is the correct terminology but I call "in memory tables" to the objects created like this:
create type InMemReg is object (field1 varchar2(10), field2 varchar2(20), field3 number);
create type InMemTab is table of InMemReg;
In this case my "in memory table" is "InMemTab". My question is how can I populate this kind of object, when i don't know previously the numbers of elements? I have seen in some places this type of initialization:
declare
v_uno InMemReg := InMemReg('a','b',1999);
v_dos InMemReg := InMemReg('A','Z',2000);
t_tres InMemTab := InMemTab();
begin
t_tres := InMemTab(v_uno, v_dos);
In this situation I have explicitly 2 objects before initialize "t_tres", but in a dynamic scenario where I could have n numbers of elements I don't know how to populate it.
In another OO language could be something like this:
t_tres.add(OtherObject)
The type InMemTab is a nested table in Oracle parlance.
The equivalent to the add method would be to call the extend method and then to assign OtherObject to the last position in the nested table.
SQL> ed
Wrote file afiedt.buf
1 declare
2 v_uno InMemReg := InMemReg('a','b',1999);
3 v_dos InMemReg := InMemReg('A','Z',2000);
4 t_tres InMemTab := InMemTab();
5 begin
6 t_tres.extend;
7 t_tres( t_tres.count ) := v_uno;
8 t_tres.extend;
9 t_tres( t_tres.count ) := v_dos;
10 dbms_output.put_line( 't_tres has ' || t_tres.count || ' elements.' );
11* end;
12 /
t_tres has 2 elements.
PL/SQL procedure successfully completed.
You can factor that out into an add procedure as well
SQL> ed
Wrote file afiedt.buf
1 declare
2 v_uno InMemReg := InMemReg('a','b',1999);
3 v_dos InMemReg := InMemReg('A','Z',2000);
4 t_tres InMemTab := InMemTab();
5 procedure add( p_nt IN OUT InMemTab,
6 p_elem IN InMemReg )
7 as
8 begin
9 p_nt.extend;
10 p_nt( p_nt.count ) := p_elem;
11 end;
12 begin
13 add( t_tres, v_uno );
14 add( t_tres, v_dos );
15 dbms_output.put_line( 't_tres has ' || t_tres.count || ' elements.' );
16* end;
17 /
t_tres has 2 elements.
PL/SQL procedure successfully completed.
It is common to populate the collection from the data itself, meaning you are not explicitly adding sets of strings and numbers, you're pulling the data in from other tables. Because this is a common and natural thing to do with collections, Oracle made it easy via "BULK COLLECT INTO" clause in pl/sql. For example:
DECLARE
TYPE EmployeeSet IS TABLE OF employees%ROWTYPE;
underpaid EmployeeSet;
-- Holds set of rows from EMPLOYEES table.
CURSOR c1 IS SELECT first_name, last_name FROM employees;
TYPE NameSet IS TABLE OF c1%ROWTYPE;
some_names NameSet;
-- Holds set of partial rows from EMPLOYEES table.
BEGIN
-- With one query,
-- bring all relevant data into collection of records.
SELECT * BULK COLLECT INTO underpaid FROM employees
WHERE salary < 5000 ORDER BY salary DESC;
-- Process data by examining collection or passing it to
-- eparate procedure, instead of writing loop to FETCH each row.
DBMS_OUTPUT.PUT_LINE
(underpaid.COUNT || ' people make less than 5000.');
FOR i IN underpaid.FIRST .. underpaid.LAST
LOOP
DBMS_OUTPUT.PUT_LINE
(underpaid(i).last_name || ' makes ' || underpaid(i).salary);
END LOOP;
-- You can also bring in just some of the table columns.
-- Here you get the first and last names of 10 arbitrary employees.
SELECT first_name, last_name
BULK COLLECT INTO some_names
FROM employees
WHERE ROWNUM < 11;
FOR i IN some_names.FIRST .. some_names.LAST
LOOP
DBMS_OUTPUT.PUT_LINE
('Employee = ' || some_names(i).first_name
|| ' ' || some_names(i).last_name);
END LOOP;
END;
/
You don't typically need to worry about extending or how many elements you'll have, you can usually slurp it in and then use the built in features of the collection as you like (counts, loop through, compare different collections, set operations, etc)

Oracle PL/SQL: Forwarding whole row to procedure from a trigger

In have an Oracle (10i) PL/SQL Row-Level trigger which is responsible for three independent tasks. As the trigger is relatively cluttered that way, I want to export these three tasks into three stored procedures.
I was thinking of using a my_table%ROWTYPE parameter or maybe a collection type for the procedures, but my main concern is how to fill these parameters.
Is there a way to put the whole :NEW row of a trigger into a single variable easily?
So far the only way I could find out was assigning each field separately to the variable which is not quite satisfying, looking at code maintenance etc.
Something like
SELECT :NEW.* INTO <variable> FROM dual;
would be preferred. (I haven't tried that actually but I suppose it wouldn't work)
In the vast majority of cases, the only way to assign the new values in the row to a %ROWTYPE variable would be to explicitly assign each column. Something like
CREATE OR REPLACE TRIGGER some_trigger_name
BEFORE INSERT OR UPDATE ON some_table
FOR EACH ROW
DECLARE
l_row some_table%rowtype;
BEGIN
l_row.column1 := :NEW.column1;
l_row.column2 := :NEW.column2;
...
l_row.columnN := :NEW.columnN;
procedure1( l_row );
procedure2( l_row );
procedure3( l_row );
END;
If your table happens to be declared based on an object, :NEW will be an object of that type. So if you have a table like
CREATE OR REPLACE TYPE obj_foo
AS OBJECT (
column1 NUMBER,
column2 NUMBER,
...
columnN NUMBER );
CREATE TABLE foo OF obj_foo;
then you could declare procedures that accept input parameters of type OBJ_FOO and call those directly from your trigger.
The suggestion in the other thread about selecting the row from the table in an AFTER INSERT/ UPDATE thread, unfortunately, does not generally work. That will generally lead to a mutating table exception.
1 create table foo (
2 col1 number,
3 col2 number
4* )
SQL> /
Table created.
SQL> create procedure foo_proc( p_foo in foo%rowtype )
2 as
3 begin
4 dbms_output.put_line( 'In foo_proc' );
5 end;
6 /
Procedure created.
SQL> create or replace trigger trg_foo
2 after insert or update on foo
3 for each row
4 declare
5 l_row foo%rowtype;
6 begin
7 select *
8 into l_row
9 from foo
10 where col1 = :new.col1;
11 foo_proc( l_row );
12 end;
13 /
Trigger created.
SQL> insert into foo values( 1, 2 );
insert into foo values( 1, 2 )
*
ERROR at line 1:
ORA-04091: table SCOTT.FOO is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG_FOO", line 4
ORA-04088: error during execution of trigger 'SCOTT.TRG_FOO'
It's not possible that way.
Maybe my answer to another question can help.
Use SQL to generate the SQL;
select ' row_field.'||COLUMN_NAME||' := :new.'||COLUMN_NAME||';' from
ALL_TAB_COLUMNS cols
where
cols.TABLE_NAME = 'yourTableName'
order by cols.column_name
Then copy and paste output.
This is similar to Justins solution but a little bit shorter (no typing of left part of each assignment) :
-- use instead of the assignments in Justins example:
select :new.column1,
:new.column2,
...
:new.columnN,
into l_row from dual;

Resources