FORALL update is updating only last record from collection and it's giving error ORA-22160: element at index [1] does not exist error message - plsqldeveloper

FORALL update is updating only last record from collection and it's giving error ORA-22160: element at index [1] does not exist. I have scenario were i have to update million of records. When i execute the below code the update is happening only for the 100th record remaining 99 records remains same( i tested with 100 records). after execution it's giving ORA-22160: element at index [1] does not exist. i am using oracle 9i database.need suggestion for this issue.
Declare
CURSOR tk_iflow_cur
IS
SELECT DISTINCT top.rule_id,
top.rule_item_id,
msib.segment2
FROM iflow_rules top,
iflow_active_rules msib
WHERE top.rule_id = msib.rule_item_id
AND msib.organization_id = 5039 and rownum<=100
ORDER BY top.ora_inv_item_id; -- cursor fetches 100 row
---Variable declaration
TYPE l_iflow_id
IS
TABLE OF VARCHAR2(200) INDEX BY BINARY_INTEGER;
TYPE l_iflow_org
IS
TABLE OF NUMBER INDEX BY BINARY_INTEGER;
TYPE l_iflow_inv_id
IS
TABLE OF NUMBER INDEX BY BINARY_INTEGER;
TYPE l_r12_inv_id
IS
TABLE OF NUMBER;
TYPE l_r12_item_key
IS
TABLE OF VARCHAR2(200) INDEX BY BINARY_INTEGER;
TYPE l_r12_iflow
IS
TABLE OF VARCHAR2(200) INDEX BY BINARY_INTEGER;
TYPE l_omar_item_id
IS
TABLE OF NUMBER INDEX BY BINARY_INTEGER;
TYPE l_omar_seg2
IS
TABLE OF VARCHAR2(200) INDEX BY BINARY_INTEGER;
tk_iflow_id l_iflow_id;
tk_iflow_org l_iflow_org;
tk_iflow_inv_id l_iflow_inv_id;
t_omar_item_id l_omar_item_id;
t_omar_seg2 l_omar_seg2;
r12_inv_id l_r12_inv_id:=l_r12_inv_id();
r12_item_key l_r12_item_key;
r12_iflow l_r12_iflow;
user_excep EXCEPTION;
v_err_count NUMBER;
PRAGMA EXCEPTION_INIT (user_excep, -24381);
r12_item_id2 VARCHAR2(200);
BEGIN --
tk_iflow_id.DELETE;
tk_iflow_inv_id.DELETE;
OPEN tk_iflow_cur;
LOOP --Cursor Loop
tk_iflow_id.DELETE;
tk_iflow_inv_id.DELETE;
t_omar_seg2.DELETE;
FETCH tk_iflow_cur BULK COLLECT INTO tk_iflow_id, tk_iflow_inv_id, t_omar_seg2; ---100 records assigned to variables
FOR i IN 1..tk_iflow_id.COUNT -- to store the cursor value
LOOP --for Loop
BEGIN
--Comment : passing the cursor value to derive new records from different instance for updating the records
SELECT DISTINCT segment1,
inventory_item_id,
item_type
INTO r12_iflow(i),
r12_item_id2,
r12_item_key(i)
FROM iflow.ifl_active_rules#R12_db_link
WHERE segment1 =tk_iflow_id(i)
AND NVL(SEGMENT2,'NULL') = NVL(t_omar_seg2(i),'NULL')
AND organization_id =5063;
EXCEPTION
WHEN OTHERS THEN
r12_inv_id(i) := 0;
r12_item_key(i) := 0;
END;
r12_inv_id :=l_r12_inv_id();
r12_inv_id.EXTEND (i);
r12_inv_id (i) := r12_item_id2;
END LOOP; --end for loop
BEGIN
FORALL i IN r12_inv_id.FIRST..r12_inv_id.LAST SAVE EXCEPTIONS -- for all the derived records i'm updating the target table.100 records i'm updating
UPDATE iflow_active_rules
SET ora_org_id =5063,
ora_inv_item_id =r12_inv_id(i)
WHERE iflow_id =r12_iflow(i)
AND ora_inv_item_id = tk_iflow_inv_id(i);
EXCEPTION
WHEN user_excep THEN
v_err_count := SQL%BULK_EXCEPTIONS.COUNT;
FOR i IN 1 .. v_err_count
LOOP
DBMS_OUTPUT.put_line ( 'Error: ' || i || ' Array Index: ' || SQL%BULK_EXCEPTIONS (i).ERROR_INDEX || ' Message: ' || SQLERRM (SQL%BULK_EXCEPTIONS (i).ERROR_CODE) );
END LOOP;
END;
BEGIN
FORALL i IN r12_inv_id.FIRST .. r12_inv_id.LAST SAVE EXCEPTIONS
UPDATE iflow_rules --- updating openup system iflow cost details
SET ora_org_id =5063,
ora_inv_item_id =r12_inv_id(i)
WHERE iflow_id =r12_iflow(i)
AND ora_inv_item_id = tk_iflow_inv_id(i) ;
EXCEPTION
WHEN user_excep THEN
v_err_count := SQL%BULK_EXCEPTIONS.COUNT;
FOR i IN 1 .. v_err_count
LOOP
DBMS_OUTPUT.put_line ( 'Error: ' || i || ' Array Index: ' || SQL%BULK_EXCEPTIONS (i).ERROR_INDEX || ' Message: ' || SQLERRM (SQL%BULK_EXCEPTIONS (i).ERROR_CODE) );
END LOOP;
END;
EXIT
WHEN tk_iflow_id.COUNT=0;
COMMIT;
END LOOP; --End Cursor Loop
CLOSE tk_iflow_cur;
END;

This is happening because you are using r12_iflow collection in the scope of collection r12_inv_id where it not not accessible. FORALL statement is differnt the FOR LOOP. Even though they work pretty on same logic but they differs on scope of usage of variables. You can create a RECORD in your case and do the processing. See below how you can do it. PS Not tested.
DECLARE
CURSOR tk_iflow_cur
IS
SELECT DISTINCT top.rule_id, top.rule_item_id, msib.segment2
FROM iflow_rules top, iflow_active_rules msib
WHERE top.rule_id = msib.rule_item_id
AND msib.organization_id = 5039
AND ROWNUM <= 100
ORDER BY top.ora_inv_item_id; -- cursor fetches 100 row
Type var_cur is table of tk_iflow_cur%rowtype index by pls_integer;
l_cur var_cur;
--Record of your variables.
TYPE var_rec is RECORD
(
l_r12_iflow VARCHAR2 (200),
l_r12_inv_id number,
l_r12_item_key VARCHAR2 (200)
);
TYPE rec is table of var_rec index by pls_integer;
--Variable declared to hold the result of your cursor.
l_rec rec;
user_excep EXCEPTION;
v_err_count NUMBER;
PRAGMA EXCEPTION_INIT (user_excep, -24381);
r12_item_id2 VARCHAR2 (200);
BEGIN
OPEN tk_iflow_cur;
LOOP --Cursor Loop
FETCH tk_iflow_cur BULK COLLECT INTO l_cur LIMIT 100; ---100 records assigned to variables
FOR i IN 1 .. l_cur.COUNT -- to store the cursor value
LOOP --for Loop
BEGIN
--Comment : passing the cursor value to derive new records from different instance for updating the records
SELECT DISTINCT segment1, inventory_item_id, item_type
INTO l_rec(i).l_r12_iflow ,l_rec(i).l_r12_inv_id ,l_rec(i).l_r12_item_key
FROM iflow.ifl_active_rules#R12_db_link
WHERE segment1 = l_cur(i).rule_id
AND NVL (SEGMENT2, 'NULL') = NVL (l_cur(i).segment2 , 'NULL')
AND organization_id = 5063;
EXCEPTION
WHEN OTHERS
THEN
l_rec(i).l_r12_iflow := 0;
l_rec(i).l_r12_inv_id:= 0;
l_rec(i).l_r12_item_key := '';
END;
END LOOP; --end for loop
BEGIN
FORALL i IN 1 .. l_rec.COUNT SAVE EXCEPTIONS -- for all the derived records i'm updating the target table.100 records i'm updating
UPDATE iflow_active_rules
SET ora_org_id = 5063,
ora_inv_item_id = l_rec(i).l_r12_inv_id
WHERE iflow_id = l_rec(i).l_r12_iflow
AND ora_inv_item_id = l_rec(i).l_r12_inv_id;
EXCEPTION
WHEN user_excep
THEN
v_err_count := SQL%BULK_EXCEPTIONS.COUNT;
FOR i IN 1 .. v_err_count
LOOP
DBMS_OUTPUT.put_line (
'Error: '
|| i
|| ' Array Index: '
|| SQL%BULK_EXCEPTIONS (i).ERROR_INDEX
|| ' Message: '
|| SQLERRM (SQL%BULK_EXCEPTIONS (i).ERROR_CODE));
END LOOP;
END;
BEGIN
FORALL i IN 1..l_rec.COUNT SAVE EXCEPTIONS
UPDATE iflow_rules --- updating openup system iflow cost details
SET ora_org_id = 5063,
ora_inv_item_id = l_rec(i).l_r12_inv_id
WHERE iflow_id = l_rec(i).l_r12_iflow
AND ora_inv_item_id = l_rec(i).l_r12_inv_id;
EXCEPTION
WHEN user_excep
THEN
v_err_count := SQL%BULK_EXCEPTIONS.COUNT;
FOR i IN 1 .. v_err_count
LOOP
DBMS_OUTPUT.put_line (
'Error: '
|| i
|| ' Array Index: '
|| SQL%BULK_EXCEPTIONS (i).ERROR_INDEX
|| ' Message: '
|| SQLERRM (SQL%BULK_EXCEPTIONS (i).ERROR_CODE));
END LOOP;
END;
EXIT WHEN tk_iflow_cur%NOTFOUND;
END LOOP; --End Cursor Loop
COMMIT;
CLOSE tk_iflow_cur;
END;

Related

How to handle big data json having more than 32767 keys

I am struggling to parse a JSON having more than 32767 keys
The format of json is shown below.
The idea is to fetch all the keys and value into a temp table , but due to length limitation unable to do so
{"1335":"345435sd","8989SD":"jddk8","dDDSF","87868658"......}
declare
j JSON_OBJECT_T;
i NUMBER;
k JSON_KEY_LIST;
arr JSON_ARRAY_T;
v_key varchar2(2000);
v_value varchar2(2000);
CURSOR c_json IS
select treat(col_clob as json) myJsonCol from t_clob; -- the data is stored as clob and it need to be
begin
FOR rec IN c_json
LOOP
j := JSON_OBJECT_T.parse(rec.myJsonCol);
k := j.get_keys;
FOR i in 1..k.COUNT
LOOP
dbms_output.put_line(k(i) || ' ' || j.get_String(k(i)));
v_key :=k(i);
v_value :=j.get_String(k(i));
-- insert into temp(c1,c2) values(v_key,v_value);
END LOOP;
END LOOP;
END;
/
::Oracle Error: ORA-40684: maximum number of key names exceeded::
Reference - https://docs.oracle.com/en/database/oracle/oracle-database/12.2/adjsn/oracle-json-restrictions.html#GUID-1DB81125-54A7-4CB6-864B-78E0E7E407C9
PL/SQL getter method JSON_OBJECT_T.get_keys() returns at most 32767 field names for a given JSON object. An error is raised if it is applied to an object with more than 32767 fields.

Oracle utl_file throwing error when the input buffer size exceeds more than 1000 character

I'm tyring to write files for every cursor execution as shown in below sql. The data_payload column will have more than 1000 characters.
There is an exception at utl_file.put line.
If I use SUBRTR of 1000 characters, then the file writes successfully.
Is there any other alternate function to handle this?
SET SERVEROUTPUT ON
DECLARE
l_file_name VARCHAR2(100);
l_chr_payload VARCHAR2(4000);
fhandle utl_file.file_type;
CURSOR payload_cur IS
SELECT data_payload,
request_reference
FROM temp_tbl
WHERE message_type = 'ORDERCREATE'
AND ROWNUM < 2;
TYPE payload_typ IS
TABLE OF payload_cur%rowtype INDEX BY BINARY_INTEGER;
l_col_payload payload_typ;
BEGIN
OPEN payload_cur;
l_col_payload.DELETE;
FETCH payload_cur
BULK COLLECT INTO l_col_payload;
CLOSE payload_cur;
FOR i IN 1..l_col_payload.count LOOP
l_file_name := l_col_payload(i).request_reference
|| '_'
|| i
|| '.json';
dbms_output.put_line('l_file_name' || l_file_name);
fhandle := utl_file.fopen(
'TMP_DIR' -- File location
,
l_file_name -- File name
,
'w' -- Open mode: w = write.
);
l_chr_payload := substr(
l_col_payload(i).data_payload,
1,
1000
);
utl_file.put(
fhandle,
l_chr_payload
);
utl_file.fclose(fhandle);
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
dbms_output.put_line('ERROR: '
|| sqlcode
|| ' - '
|| sqlerrm);
RAISE;
END;
/
Assuming that temp_tbl.data_payload is a CLOB data type then you want to loop through the payload reading substring chunks (without needing to store the substrings in a temporary variable).
If you want to write more than 1024 characters at one time then you need to specify the 4th argument to UTL_FILE.FOPEN(location, filename, openmode, max_linesize) as the default is 1024.
Something like this (untested as I don't have your tables or a directory to write to):
DECLARE
l_index PLS_INTEGER := 0;
CURSOR payload_cur IS
SELECT data_payload,
request_reference
FROM temp_tbl
WHERE message_type = 'ORDERCREATE'
AND ROWNUM < 2;
TYPE payload_typ IS TABLE OF payload_cur%rowtype;
l_col_payload payload_typ;
BEGIN
OPEN payload_cur;
LOOP
-- In general, you should limit the number of rows you process at one time.
-- In your case this is not necessary due to the `ROWNUM < 2` filter.
FETCH payload_cur BULK COLLECT INTO l_col_payload LIMIT 10;
EXIT WHEN payload_cur%NOTFOUND;
FOR i IN 1..l_col_payload.count LOOP
l_index := l_index + 1;
DECLARE
l_file_name VARCHAR2(100);
c_amt CONSTANT PLS_INTEGER := 32000;
l_length PLS_INTEGER := COALESCE(dbms_lob.getlength(l_col_payload(i).data_payload),0);
l_offset PLS_INTEGER := 1;
l_fhandle utl_file.file_type;
BEGIN
l_file_name := l_col_payload(i).request_reference || '_' || l_index || '.json';
dbms_output.put_line('l_file_name' || l_file_name);
l_fhandle := utl_file.fopen('TMP_DIR', l_file_name, 'w', 32760);
WHILE ( l_offset <= l_length ) LOOP
utl_file.put(
l_fhandle,
dbms_lob.substr(l_col_payload(i).data_payload,c_amt,l_offset)
);
utl_file.fflush(l_fhandle);
l_offset := l_offset + c_amt;
END LOOP;
utl_file.fclose(l_fhandle);
END;
END LOOP;
END LOOP;
CLOSE payload_cur;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('ERROR: ' || sqlcode || ' - ' || sqlerrm);
RAISE;
END;
/

Specific errors messages for each input if not found in the database

I have developed the following function. I have been instructed that the function should return null if no teacher is found in the database. If the function returns null then it should output “no teacher found.” The code below accomplishes this. However, I would like to output specific responses if errors are made when keying in any of the 4 inputs. For instance, in addition to receiving the response “no teacher found” it would also say “subject not found” or “school not found.” Basically, if any of the 4 inputs was keyed in incorrectly, it would be clear as to the reason the function returned null. That way, if the a school name was misspelled by a single letter, the problem would be clear and evident instead of thinking that no teacher taught that particular class. How can I accomplish this?
CREATE OR REPLACE FUNCTION get_classroom_teacher(
subject_in IN subjects.subject%TYPE,
school_name_in IN schools.school_name%TYPE,
year_in IN classrooms.year%TYPE,
semester_in IN classrooms.semester%TYPE)
RETURN VARCHAR2
IS
l_teacher_name VARCHAR2(50);
BEGIN
SELECT first_name || ' ' || last_name
INTO l_teacher_name
FROM people
WHERE school_id IN (
SELECT school_id
FROM schools
WHERE school_name = school_name_in)
AND
person_id IN (
SELECT person_id
FROM teachers
WHERE subject_id IN (
SELECT subject_id
FROM subjects
WHERE subject = subject_in)
AND subject_id IN (
SELECT subject_id
FROM classrooms
WHERE semester = semester_in
AND year = year_in));
DBMS_OUTPUT.PUT_LINE(l_teacher_name);
RETURN l_teacher_name;
EXCEPTION
WHEN no_data_found THEN
RETURN NULL;
END get_classroom_teacher;
I call it like this:
DECLARE
l_subject subjects.subject%TYPE;
l_school schools.school_name%TYPE;
l_year classrooms.year%TYPE;
l_semester classrooms.semester%TYPE;
l_teacher_name VARCHAR2(60);
BEGIN
l_subject := 'Science';
l_school := 'Fayetteville-Manlius School';
l_year := 2021;
l_semester := 'spring';
l_teacher_name := get_classroom_teacher(l_subject, l_school, l_year, l_semester);
IF l_teacher_name IS NULL THEN
DBMS_OUTPUT.PUT_LINE('No teacher found.');
ELSE
DBMS_OUTPUT.PUT_LINE('The teacher is ' || l_teacher_name);
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001, 'An error was encountered - ' ||
SQLCODE ||
' -ERROR- ' ||
SQLERRM);
END;
The issue you face is that any of inputs may be the cause of a "No Data Found" exception. Further there could actually be multiple invalid parameters, i.e school and subject misspelled. To determine the exact cause then you must select each parameter independently. This could be a performance killer. Since we assume the majority of requests will be successful you can delay those selections until after the full query fails, then on a no data found exception do the individual selects. Since each individual then must also trap NO_DATA_FOUND you can create helper functions for them.
create or replace
function teacher_name(subject_in IN subjects.subject%TYPE
,school_name_in IN schools.school_name%TYPE
,year_in IN classrooms.year%TYPE
,semester_in IN classrooms.semester%TYPE
)
return varchar2
is
l_teacher varchar2(50);
l_school_id schools.school_id%TYPE;
l_subject_id subjects.subject_id%TYPE;
l_year classrooms.year%TYPE;
l_classroom classrooms.semester%TYPE;
procedure what_school() is
begin
select school_id
into l_school_id
from schools
where school_name = school_name_in)
exception
when no_data_found then l_school_id = null;
when too_many_rows then l_school_id = 0;
end what_school;
procedure what_subject() is
begin
select subject_id
into l_subject_id
from subjects
where subject_name = subject_name_in)
exception
when no_data_found then null;
when too_many_rows then subject_id = 0;
end what_subject;
-- Functions for semester, year ...
begin
<< Your query here >>
return the_teacher;
exception
when no_data_found then
l_school_id := when_school;
l_subject_id := what_subject;
l_year := what_year;
l_semester := what_semester;
if l_school_id is null then
dbms_output.put_line( ' No such school as: ' || school_name_in;
if l_subject_id is null then
dbms_output.put_line( ' No such subject as: ' || subject_in;
if l_year is null then
dbms_output.put_line( ' No such yeear as: ' || year_in;
if l_semester is null then
dbms_output.put_line( ' No such semester as: ' || semester_id;
if l_school_id is not null
and l_subject_id is not null
and l_year is not null
and l_semester is not null
then
dbms_output.put_line( ' No Date Found')
return null;
end if;
end teacher_name;
NOTE: No sample data nor table DDL; Not Tested.
We can define bespoke exceptions and raise them in procedures. EXCEPTION is a PL/SQL data type, and we can associate an error number from the range set aside for user-defined exceptions (-20999 to -20000) using the exception_init pragma. So your function might look like this:
CREATE OR REPLACE FUNCTION get_classroom_teacher(
subject_in IN subjects.subject%TYPE,
school_name_in IN schools.school_name%TYPE,
year_in IN classrooms.year%TYPE,
semester_in IN classrooms.semester%TYPE)
RETURN VARCHAR2
IS
l_teacher_name VARCHAR2(50);
x_subject_null exception;
pragma exception_init (x_subject_null, -20000);
x_school_name_null exception;
pragma exception_init (x_school_name_null, -20001);
x_semester_null exception;
pragma exception_init (x_semester_null, -20002);
x_year_null exception;
pragma exception_init (x_year_null, -20003);
BEGIN
if subject_in is null then
raise x_subject_null;
elsif school_name_in is null then
raise x_school_name_null;
elsif subject_in is null then
raise x_semester_null;
elsif year_in is null then
raise x_year_null;
end if;
SELECT first_name || ' ' || last_name
INTO l_teacher_name
FROM people
WHERE school_id IN (
SELECT school_id
FROM schools
WHERE school_name = school_name_in)
AND
person_id IN (
SELECT person_id
FROM teachers
WHERE subject_id IN (
SELECT subject_id
FROM subjects
WHERE subject = subject_in)
AND subject_id IN (
SELECT subject_id
FROM classrooms
WHERE semester = semester_in
AND year = year_in));
DBMS_OUTPUT.PUT_LINE(l_teacher_name);
RETURN l_teacher_name;
EXCEPTION
WHEN no_data_found THEN
RETURN NULL;
END get_classroom_teacher;
The catch is those user-defined exceptions are only useful within the scope of the function. They are no different from any other variable defined in a standalone program unit. Consequently, your calling block cannot reference them, which means you cannot log a bespoke error message for each exception.
The solution is to define the exceptions in a package spec which can be referenced by both the function and the anonymous block.
create or replace package ude as
x_subject_null exception;
pragma exception_init (x_subject_null, -20000);
x_school_name_null exception;
pragma exception_init (x_school_name_null, -20001);
x_semester_null exception;
pragma exception_init (x_semester_null, -20002);
x_year_null exception;
pragma exception_init (x_year_null, -20003);
end;
/
Reference the package in your function ....
CREATE OR REPLACE FUNCTION get_classroom_teacher(
subject_in IN subjects.subject%TYPE,
school_name_in IN schools.school_name%TYPE,
year_in IN classrooms.year%TYPE,
semester_in IN classrooms.semester%TYPE)
RETURN VARCHAR2
IS
l_teacher_name VARCHAR2(50);
BEGIN
if subject_in is null then
raise ude.x_subject_null;
elsif school_name_in is null then
raise ude.x_school_name_null;
elsif subject_in is null then
raise ude.x_semester_null;
elsif year_in is null then
raise ude.x_year_null;
end if;
SELECT first_name || ' ' || last_name
INTO l_teacher_name
FROM people
WHERE school_id IN (
SELECT school_id
FROM schools
WHERE school_name = school_name_in)
AND
person_id IN (
SELECT person_id
FROM teachers
WHERE subject_id IN (
SELECT subject_id
FROM subjects
WHERE subject = subject_in)
AND subject_id IN (
SELECT subject_id
FROM classrooms
WHERE semester = semester_in
AND year = year_in));
DBMS_OUTPUT.PUT_LINE(l_teacher_name);
RETURN l_teacher_name;
EXCEPTION
WHEN no_data_found THEN
RETURN NULL;
END get_classroom_teacher;
and in your calling block ....
DECLARE
l_subject subjects.subject%TYPE;
l_school schools.school_name%TYPE;
l_year classrooms.year%TYPE;
l_semester classrooms.semester%TYPE;
l_teacher_name VARCHAR2(60);
BEGIN
l_subject := 'Science';
l_school := 'Fayetteville-Manlius School';
l_year := 2021;
l_semester := 'spring';
l_teacher_name := get_classroom_teacher(l_subject, l_school, l_year, l_semester);
IF l_teacher_name IS NULL THEN
DBMS_OUTPUT.PUT_LINE('No teacher found.');
ELSE
DBMS_OUTPUT.PUT_LINE('The teacher is ' || l_teacher_name);
END IF;
EXCEPTION
when ude.x_subject_null then
dbms_output.put_line ('Subject parameter was null');
raise;
when ude.x_school_name_null then
dbms_output.put_line ('School name parameter was null');
raise
when ude.x_semester_null then
dbms_output.put_line ('Semester parameter was null');
raise;
when ude.x_year_null then
dbms_output.put_line ('Year parameter was null');
raise;
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001, 'An error was encountered - ' ||
SQLCODE ||
' -ERROR- ' ||
SQLERRM);
END;
You may choose not to re-raise those handled exceptions.
Generally speaking, a WHEN OTHERS branch like that is considered bad practice. You're suppressing some useful information without getting anything in return. WHEN OTHERS is useful when we want to take some action for all exceptions, such as writing a message to a log table. But just re-raising every error with a single ERRNO is pointless.

PL/SQL: ORA-00932: inconsistent datatypes: expected NUMBER got USER_NAME.VARCHAR_ARRAY

The below is a function that I am creating to accept an array of varchar2 items and return the internal pk of that record which is a NUMBER for each record. I am struggling to get the syntax right to pass an array of type VARCHAR_ARRAY to the simple sql query in the cursor and return the variable of type NUMBER_ARRAY. the Error is on line 8,42 i.e FROM table_name WHERE column_name IN VARCHAR_ARRAY which was passed to the function. Please help me with this error as I am learning plsql.
create or replace TYPE VARCHAR_ARRAY AS VARRAY(1000000) OF VARCHAR2(1000);
/
create or replace TYPE NUMBER_ARRAY AS VARRAY(1000000) OF NUMBER;
/
create or replace Function GET_PRODUCT_ID_ARR(V_PRODUCT_NUM_ARR IN VARCHAR_ARRAY)
RETURN NUMBER_ARRAY
IS
product_id_list number_array := number_array();
CURSOR c1
IS
SELECT cat_map_id
FROM mn_cat_map WHERE product_num IN (V_PRODUCT_NUM_ARR) and catalog_type = 'INT';
v_output NUMBER;
BEGIN
OPEN c1;
LOOP
fetch c1 into product_id_list;
EXIT WHEN c1%notfound;
product_id_list.extend;
product_id_list(product_id_list.count) := v_output;
dbms_output.put_line('Product ('||v_output ||'):'||product_id_list(v_output));
END LOOP;
Close c1;
RETURN product_id_list;
END;
/
There is two issues:
You have to cast varray to table:
CURSOR c1
IS
SELECT cat_map_id
FROM mn_cat_map
WHERE product_num IN (select column_value from table(V_PRODUCT_NUM_ARR))
and catalog_type = 'INT';
Add bulk collect after fetch:
LOOP
fetch c1 bulk collect into product_id_list limit 100;
EXIT WHEN c1%notfound;
product_id_list.extend;
product_id_list(product_id_list.count) := v_output;
dbms_output.put_line('Product ('||v_output ||'):'||product_id_list(v_output));
END LOOP;
If you write limit 100, each loop will put 100 records in product_id_list. You can omit limit clause, in this case you will get all records in one fetch.
EDIT
How to see results:
declare
myarray varchar_array;
my_num_array number_array;
begin
myarray := varchar_array();
myarray.extend(2);
myarray(1) := '151043';
myarray(2) := '2895';
my_num_array := GET_PRODUCT_ID_ARR(myarray);
for i in 1 .. my_num_array.count loop
dbms_output.put_line(my_num_array(i));
end loop;
end;
/
What #William is said is quiet true. VARRAY(1000000) is not recommended. Inplace you can create a type of table. However if i follow what you have done, there seems some mistakes in your code. Please see below how you can do it.
Tables Preparation:
create table mn_cat_map(cat_map_id number,
product_num varchar2(1000),
catalog_type varchar2(10));
/
INSERT INTO T541682.MN_CAT_MAP (CAT_MAP_ID, PRODUCT_NUM, CATALOG_TYPE)
VALUES (10, 'A123', 'INT');
INSERT INTO T541682.MN_CAT_MAP (CAT_MAP_ID, PRODUCT_NUM, CATALOG_TYPE)
VALUES (2, 'B121', '2Wheer');
INSERT INTO T541682.MN_CAT_MAP (CAT_MAP_ID, PRODUCT_NUM, CATALOG_TYPE)
VALUES (3, 'C645', '4Wheer');
COMMIT;
create or replace TYPE VARCHAR_ARRAY AS VARRAY(1000000) OF VARCHAR2(1000);
/
create or replace TYPE NUMBER_ARRAY AS VARRAY(1000000) OF NUMBER;
/
Code: read explainatory comments inline
CREATE OR REPLACE FUNCTION GET_PRODUCT_ID_ARR (V_PRODUCT_NUM_ARR VARCHAR_ARRAY)
RETURN NUMBER_ARRAY
IS
product_id_list number_array := number_array ();
CURSOR c1(tbl_list VARCHAR_ARRAY)
IS
SELECT cat_map_id
FROM mn_cat_map
WHERE product_num in (select column_value from table(tbl_list)) ---Checking if the item exists in the table with passed collection
AND catalog_type = 'INT';
v_output NUMBER:= 0;
BEGIN
--not opening cursor and am not looking for processing any records.
--OPEN c1(V_PRODUCT_NUM_ARR);
--passing the input varray to the cursor.
for i in c1(V_PRODUCT_NUM_ARR)
loop
v_output:=v_output + 1;
product_id_list.extend;
product_id_list(product_id_list.COUNT):= i.cat_map_id;
DBMS_OUTPUT.put_line('Product (' || v_output || '):' ||product_id_list(product_id_list.COUNT));
end loop;
RETURN product_id_list;
END;
/
Execution:
SQL> select GET_PRODUCT_ID_ARR(VARCHAR_ARRAY('A123','B121','C645')) COl1 from dual;
COL1
--------------------------------------------------------------------------------
NUMBER_ARRAY(10)
Product (1):10

Bulk collect and forall with save exception

I have the below code , emp1 table has data of 12 rows in which 2 empno's already present in emp table. i'm trying to save the exceptions for these 2 records and to insert remaining all to emp. But everything is getting error out and i couldn't able to insert into emp. Anyone please help.
SET SERVEROUTPUT ON;
DECLARE
CURSOR C1
IS
SELECT EMPNO FROM EMP1;
TYPE T
IS
TABLE OF C1%ROWTYPE;
L_DATA T;
L_ERRORS_COUNT NUMBER;
EX_DML_ERRORS EXCEPTION;
PRAGMA EXCEPTION_INIT (ex_DML_ERRORS, -47);
L_ERRNO NUMBER;
L_MSG VARCHAR2(4000);
L_IDX NUMBER;
BEGIN
OPEN C1;
LOOP
FETCH C1 BULK COLLECT INTO L_DATA;
BEGIN
FORALL I IN 1..L_DATA.COUNT SAVE EXCEPTIONS
INSERT
INTO EMP
(
EMPNO,
ENAME,
MGR
)
VALUES
(
L_DATA(I).EMPNO,
'SCOTT',
L_DATA(I).EMPNO
);
EXCEPTION
WHEN dup_val_on_index THEN
DBMS_OUTPUT.PUT_LINE('WHD');
L_ERRORS_COUNT := SQL%BULK_EXCEPTIONS.COUNT;
DBMS_OUTPUT.PUT_LINE('WHD::::'||L_ERRORS_COUNT);
FOR J IN 1..L_ERRORS_COUNT
LOOP
L_ERRNO := SQL%BULK_EXCEPTIONS
(
J
)
.ERROR_CODE;
L_MSG := SQLERRM(-L_ERRNO);
L_IDX := SQL%BULK_EXCEPTIONS(J).ERROR_INDEX;
DBMS_OUTPUT.PUT_LINE('I am here SVS:'||L_ERRNO||':'|| L_MSG||':'||L_IDX);
--INSERT INTO EMPS_EXC1(SQLC ,SQLE ,INDX ) VALUES(L_ERRNO, L_MSG,L_IDX);
DBMS_OUTPUT.PUT_LINE('I am here SVS:'||L_ERRNO||':'|| L_MSG||':'||L_IDX);
END LOOP;
END;
EXIT
WHEN C1%NOTFOUND;
END LOOP;
CLOSE C1;
END;