PLS-00382: expression is of wrong type in populating an associative array table - plsql

I have this structure of table in my database
Now i want all the values inside of it in a associative array that is indexed by the job_id%type which is a varchar2, so i created this anonymous block first before creating a procedure to test it and i want to populate my associative array with the results:
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec.job_id;
END LOOP;
END;
I know for sure that this is a wrong way to do it since I've encountered this error: PLS-00382: expression is of wrong type , but at the same time i don't know if there is any proper way to do this. I have seen the documentation but all the examples were to use a user defined string that will hold the job_id, but the problem is that i need the job_id to be indexed and not a user defined string. Is there any way to fix this problem in PL/SQL?

You don't need the index by INDEX BY jobs.job_id%type. It is making your code more complicated. The INDEX BY determines the index of the collection - that is the character used to indicate the position in the collection. That doesn't need to be a column from the table you are taking a %rowtype from.
Typically an INDEX BY VARCHAR2 is used if you want to reference the collection based on a meaningful string, like capitals('United States') = 'Washingston'
Now, about your question. Here is the answer to the original question:
create table jobs
(job_id VARCHAR2(10)
,job_title VARCHAR2(30)
,min_salary NUMBER
,max_salary NUMBER
);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('a','CLERK',100,500);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('b','PRESIDENT',1000,5000);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('c','SALESMAN',500,800);
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
l_idx VARCHAR2(100);
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec;
END LOOP;
l_idx := jobstab.FIRST;
WHILE (l_idx IS NOT NULL) LOOP
dbms_output.put_line(l_idx ||': '||jobstab(l_idx).job_title);
l_idx := jobstab.NEXT(l_idx);
END LOOP;
END;
/
a: CLERK
b: PRESIDENT
c: SALESMAN
Notice that in this block, it's a bit tedious to loop through the elements, just because the index is not an integer. Now lets look at the same functionality, but using an INDEX BY BINARY_INTEGER:
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY BINARY_INTEGER;
jobstab jobs_tab_type;
l_idx NUMBER := 1;
BEGIN
-- example 1: using a cursor for loop
-- FOR rec IN (SELECT * FROM jobs)
-- LOOP
-- jobstab(l_idx) := rec;
-- l_idx := l_idx + 1;
-- END LOOP;
-- example 2: using bulk collect
SELECT * BULK COLLECT INTO jobstab FROM jobs;
FOR r IN 1 .. jobstab.COUNT LOOP
dbms_output.put_line(r ||': '||jobstab(r).job_title);
END LOOP;
END;
/
1: CLERK
2: PRESIDENT
3: SALESMAN
It's simpler to loop through the values of a collection because of the numeric index and it also can be implicitly assigned as well, using the BULK COLLECT statement.

The value of your associative array is the entire record. Hence just remove .job_id from this line of your code.
jobstab(rec.job_id) := rec.job_id;
In other words, the line should be
jobstab(rec.job_id) := rec;
Refer to this db<>fiddle

Related

PLS-00221: 'JOBSTAB' is not a procedure or is undefined

I'm currently studying PL/SQL and was doing experiment by declaring a private PL/SQL in the package index-by table called jobs_tab_type that should be indexed by a string type based on the jobs.job_id%type.
Now I really don't want to put it in a package right away and just test it first so first I created it in an anonymous block.
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
BEGIN
for rec in(select job_id from jobs)
LOOP
jobstab(rec.job_id);
END LOOP;
END;
I compiled it and ran into this error 'PLS-00221: 'JOBSTAB' is not a procedure or is undefined .
I don't know what i did wrong based on the documentations i'm currently reading online. But I don't get what is wrong, i didn't even declared a constraint and just referenced it with the data type that is on the database.
Update:
This is the description of my jobs table:
I changed the following code to:
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
BEGIN
for rec in(select job_id from jobs)
LOOP
jobstab(rec.job_id) := rec;
END LOOP;
END;

FORALL loop - select statement from associative array

I have a function, which accepts associative array as parameters. Function - before insert/update - needs to check if records exists in table, so I need to loop over array.
Since FORALL doesn't allow SELECT statement, what is the right way to do this ?
Function (just the part I need to fix - It's an example!!):
type t_name is table of MySchema.Orders.NAME%type index by pls_integer;
type t_year is table of MySchema.Orders.YEAR%type index by pls_integer;
type t_month is table of MySchema.Orders.MONTH%type index by pls_integer;
FUNCTION Check_records (name_in IN t_name, year_in IN t_year, month_in IN t_month) RETURN INTEGER
IS
record_exists INTEGER;
BEGIN
FORALL i in name_in.FIRST..name_in.LAST
SELECT COUNT(*) INTO record_exists
FROM MySchema.Orders#link_to_table
WHERE name= name_in(i)
AND year= year_in(i)
AND month= month_in(i);
return record_exist
END Check_records;
The simplest approach would simply be to use a for loop
DECLARE
l_total_records pls_integer;
l_count pls_integer;
BEGIN
l_total_records := 0;
FOR i in name_in.FIRST..name_in.LAST
LOOP
SELECT COUNT(*)
INTO l_count
FROM MySchema.Orders#link_to_table
WHERE name= name_in(i)
AND year= year_in(i)
AND month= month_in(i);
l_total_count := l_total_count + l_count;
END LOOP;
END;
If you were querying a local table and you could use a single collection (i.e. a collection of an object type with name, year, and month fields), you could do something more elegant with a MEMBER OF function but I'm not sure that works over a database link and don't have one available to test at the moment.

Can not perform DML Operation inside a query? While trying to fetch data from collection

here is my PLSQL code:
declare
headerStr varchar2(1000):='C1~C2~C3~C5~C6~C7~C8~C9~C10~C11~C12~C16~C17~C18~C19~RN';
mainValStr varchar2(32000):='1327~C010802~9958756666~05:06AM~NO~DISPOSAL~NDL~4~P32~HELLO~U~28-OCT-2017~28-OCT-2017~Reject~C010741~1;1328~C010802~9958756666~06:07AM~MH~DROP~NDL~1~P32~~U~28-OCT-2017~28-OCT-2017~Reject~C010741~2;1329~C010802~9999600785~01:08AM~BV~DROP~NDL~2~P32~MDFG~U~28-OCT-2017~28-OCT-2017~Reject~C010741~3';
valStr varchar2(4000);
headerCur sys_refcursor;
mainValCur sys_refcursor;
valCur sys_refcursor;
header varchar2(1000);
val varchar2(1000);
iterator number:=1000;
strIdx number;
strLen number;
idx number;
TYPE T_APPROVAL_RECORD IS TABLE OF VARCHAR2(4000) INDEX BY VARCHAR2(1000);
headerTable T_APPROVAL_RECORD;
cnt number;
begin
open headerCur for select * from table(split_str(headerStr,'~'));
open mainValCur for select * from table(split_str(mainValStr,';'));
loop
fetch mainValCur into valStr;
exit when mainValCur%notfound;
insert into header_test values(cnt, valStr); -- for testing purpose
open valCur for select * from table(split_str(valStr,'~'));
loop
fetch valCur into val;
fetch headerCur into header;
exit when valCur%notfound;
exit when headerCur%notfound;
insert into header_test values(header, val);
headerTable(header):= val;
end loop;
idx := headerTable.FIRST; -- Get first element of array
WHILE idx IS NOT NULL LOOP
insert into header_test values (idx, headerTable(idx));
idx := headerTable.NEXT(idx); -- Get next element of array
END LOOP;
headerTable.delete;
end loop;
commit;
end;
c1 c2 ..... c19 are column name and RN is rownumber,
data for the columns of each will be in mainValString seperated by ;
Why i am getting ORA-14551 when i am trying to access collection "headerTable"?
Please help.
Problem is with this line.
idx := headerTable.FIRST;
The index of headertable is of TYPE VARCHAR2 whereas idx is defined as NUMBER.
declare idx as VARCHAR2(1000), it should work.
Having said that, ORA-14551 - Cannot perform DML ... is not related to this error. It is unclear to me why should you encounter this error.
Oh but it does:
EXCEPTION WHEN OTHERS THEN
v_msg:=sqlcode||sqlerrm;
insert into err_data_transfer values('SPLIT_STR',v_msg,sysdate,null);
It may only be during an exception, but it's still DML during a select statement. You may be able to create another procedure as an AUTONOMOUS_TRANSACTION to create the error log. Also, you should either re-raise or raise_application_error afterward. If not your procedure will continue as though the error did not occur; which leads to more problems as to why your main process does not work (including running to completion but doing the wrong thing).

nested table element assignment in pl/sql

My requirement in pl/sql nested table is the following:
I have a nested table collection type declared and I populate the elements based on a lookup from a table.
In cases where the lookup yields more than one row(more than one value for code), then add all those multiple values in the nested table and proceed. Here is where i am stuck.
I am not able to increment that parent counter "indx" inside the exception to process those multiple rows. Since i am not, it only stores the latest data in the nested table and not all of them.
declare
TYPE final_coll_typ IS TABLE OF varchar2(100);
l_final_coll final_coll_typ;
MULTI_FETCH EXCEPTION;
PRAGMA EXCEPTION_INIT(MULTI_FETCH, -1422); -- this is an ora error for exact fetch returns more than the required number of rows
begin
for indx in 1..<count> loop
<some processing logic here>
select code into l_final_coll(indx) from lookup_tbl where <some filter>;
exception
when MULTI_FETCH then
for p in (select code from lookup_tbl where <some filter>)
loop
l_final_coll(indx) := p.code;
dbms_output.put_line(l_final_coll(indx));
end loop;
continue; -- this is for further processing after the loop
end loop;
end;
Lets say, the first iteration of the counter indx produced only one row data for code. That gets stored in l_final_coll(indx).
Lets say the next iteration of indx i the main for loop produces 2 rows of values for code. My thought was to catch the exception (ORA-01422) and keep adding these 2 code values in the existing nested table.
So, in effect, my nested table should now have 3 values of code in its element. But, currently, I can only get it to populate 2 of them (the single value from first itreration and the latest value from the next)
Any pointers would be appreciated on how I can accomplish this.
PS: Tried manipulating the counter variables indx and p. But, obviously pl/sql does not allow it for a "for loop".
You don't need to do a single select at all, just use the cursor loop to start with, and append to the collection (which you'd have to initialise):
declare
type final_coll_typ is table of varchar2(100);
l_final_coll final_coll_typ;
begin
l_final_coll := final_coll_typ();
for indx in 1..<count> loop
<some processing logic here>
for p in (select code from lookup_tbl where <some filter>) loop
l_final_coll.extend(1);
l_final_coll(l_final_coll.count) := p.code;
end loop;
end loop;
dbms_output.put_line('Final size: ' || l_final_coll.count);
end;
/
For each row found but the cursor, the collection is extended by one (which isn't very efficient), and the cursor value is put in the last, empty, row; which is found from the current count.
As a demo, if I create a dummy table with a duplicate value:
create table lookup_tbl(code varchar2(100));
insert into lookup_tbl values ('Code 1');
insert into lookup_tbl values ('Code 2');
insert into lookup_tbl values ('Code 2');
insert into lookup_tbl values ('Code 3');
... then with a specific counter and filter:
declare
type final_coll_typ is table of varchar2(100);
l_final_coll final_coll_typ;
begin
l_final_coll := final_coll_typ();
for indx in 1..3 loop
for p in (select code from lookup_tbl where code = 'Code ' || indx) loop
l_final_coll.extend(1);
l_final_coll(l_final_coll.count) := p.code;
end loop;
end loop;
dbms_output.put_line('Final size: ' || l_final_coll.count);
end;
/
... I get:
anonymous block completed
Final size: 4
As a slightly more complicated option, you could bulk-collect all the matching data into a temporary collection, then loop over that to append those values into the real collection. Something like:
declare
type final_coll_typ is table of varchar2(100);
l_final_coll final_coll_typ;
l_tmp_coll sys.dbms_debug_vc2coll;
begin
l_final_coll := final_coll_typ();
for indx in 1..<count> loop
<some processing logic here>
select code bulk collect into l_tmp_coll from lookup_tbl where <some filter>;
for cntr in 1..l_tmp_coll.count loop
l_final_coll.extend(1);
l_final_coll(l_final_coll.count) := l_tmp_coll(cntr);
end loop;
end loop;
end;
/
There may be a quicker way to combine two collections but I'm not aware of one. Bulk-collect has to be into a schema-level collection type, so you can't use your local final_coll_typ. You can create your own schema-level type, and then use it for both the temporary and final collection variables; but I've used a built-in one, sys.dbms_debug_vc2coll, which is defined as table of varchar2(1000).
As a demo, with the same table/data as above, and the same specific count and filter:
declare
type final_coll_typ is table of varchar2(100);
l_final_coll final_coll_typ;
l_tmp_coll sys.dbms_debug_vc2coll;
begin
l_final_coll := final_coll_typ();
for indx in 1..3 loop
select code bulk collect into l_tmp_coll
from lookup_tbl where code = 'Code ' || indx;
for cntr in 1..l_tmp_coll.count loop
l_final_coll.extend(1);
l_final_coll(l_final_coll.count) := l_tmp_coll(cntr);
end loop;
end loop;
dbms_output.put_line('Final size: ' || l_final_coll.count);
end;
/
... I again get:
anonymous block completed
Final size: 4

Oracle 11g : When declaring new TYPE as TABLE, must I add "INDEX BY PLS_INTEGER"?

What is the diffecence between adding INDEX BY PLS_INTEGER and not at end of declaration of new table type. Look at this example:
DECLARE
GC_BULK_LIMIT CONSTANT INTEGER := 500;
CURSOR CUR_CLIENTS IS SELECT C.ID, C.NAME FROM CLIENTS C;
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE;
-- TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE INDEX BY PLS_INTEGER;
LT_CLIENTS RT_CLIENTS;
BEGIN
OPEN CUR_CLIENTS;
LOOP
FETCH CUR_CLIENTS BULK COLLECT INTO LT_CLIENTS LIMIT GC_BULK_LIMIT;
EXIT WHEN LT_CLIENTS.COUNT = 0;
FOR I IN 1..LT_CLIENTS.COUNT LOOP
-- ... SOME LOGIC
END LOOP;
END LOOP;
CLOSE CUR_CLIENTS;
END;
in response to "must i add". The short answer is NO.
the difference is that
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE;
Is a nested table. This means that for a given variable of this type, we know that the subscripts are sequential. i.e. the subscript starts from 1 and goes up to the array length.
The following loop therefore, is the right way to access a nested table array:
FOR I IN 1..LT_CLIENTS.COUNT LOOP
This however, is called a associative array:
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE INDEX BY PLS_INTEGER;
(you could also index by a varchar2 if you wanted). The difference is that the subscripts in this case do not have to be sequential, depending on how the array was populated. In your code, they would be (as bulk collect would do that), but its not always the case.
The safe way to access and loop through an index by array is :
v_subscript := t_arr.first;
while v_subscript is not null loop
dbms_output.put_line(v_subscript || ': ' || t_arr(v_subscript));
v_subscript := t_arr.next(v_subscript);
end loop;
where v_subscript is a variable of the same datatype of the index by part.
also with a nested table, you can populate the array quickly with:
declare
type myarr is table of number;
t_arr myarr;
v_subscript number;
begin
t_arr := myarr(1, 12, 44);
whereas with an index by array you'd have to have three lines there to populate it:
t_arr(1):= 1;
t_arr(2):= 12;
t_arr(3):= 44;
for your particular case, without the index by is perfectly fine.
further reading: http://docs.oracle.com/cd/E18283_01/appdev.112/e17126/composites.htm

Resources