I have created a type to print experience of employees from employees table for 5 employees with one incorrect id. I have created an exception to skip it but I am not getting the result for it. What mistake have I done? Please refer my query and tell the error.
CREATE OR REPLACE TYPE emp_id_typ IS TABLE OF NUMBER;
/
CREATE OR REPLACE TYPE emp_exp_typ IS TABLE OF NUMBER;
/
CREATE OR REPLACE PROCEDURE exp_sp ( p_empid IN emp_id_typ
, p_exp OUT emp_exp_typ)
IS
v_exp NUMBER;
BEGIN
FOR i IN p_empid.FIRST..p_empid.COUNT
LOOP
p_exp.EXTEND;
SELECT ROUND(MONTHS_BETWEEN(SYSDATE, hire_date)/12) INTO v_exp FROM employees
WHERE employee_id = p_empid(i);
p_exp(p_exp.LAST) := v_exp;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
v_exp := NVL(v_exp, 'NULL');
END exp_sp;
/
DECLARE
v_empid emp_id_typ := emp_id_typ( );
v_exp emp_exp_typ := emp_exp_typ( );
BEGIN
v_empid := emp_id_typ(101, 102, 52, 100, 206);
FOR i IN v_empid.FIRST..v_empid.COUNT
LOOP
exp_sp(v_empid(i), v_exp(i));
dbms_output.put_line(v_empid(i), v_exp(i));
END LOOP;
END;
/
Thanks in advance...
You have several errors in both the procedure and the anonymous block. In the procedure:
You haven't initialised the OUT p_exp collection. As it's an OUT parameter you have to do it inside the procedure. (Alternatively make it IN OUT).
Your exception handler terminates the loop, so the v_exp you set in there is never used, and the p_exp collection is incomplete. (It's also bad practise to use when others.)
You can't assign a string like ``NULL'` to a number variable or collection. Either assign an a magic number that represents an error to the caller, or leave it null.
CREATE OR REPLACE PROCEDURE exp_sp ( p_empid IN emp_id_typ
, p_exp OUT emp_exp_typ)
IS
v_exp NUMBER;
BEGIN
-- have to initialise p_exp
p_exp := emp_exp_typ();
-- better to use either 1..count, or first..last; don't mix them
--FOR i IN p_empid.FIRST..p_empid.COUNT
FOR i IN p_empid.FIRST..p_empid.LAST
LOOP
p_exp.EXTEND;
-- you want the exception handler to deal with this row then continue,
-- so use a nested block
BEGIN
SELECT ROUND(MONTHS_BETWEEN(SYSDATE, hire_date)/12) INTO v_exp FROM employees
WHERE employee_id = p_empid(i);
EXCEPTION
WHEN OTHERS THEN
-- you can't assign a string to a number
--v_exp := NVL(v_exp, 'NULL');
-- just an example as not clear what you need
v_exp := -1;
END; -- nested block with exception handler
p_exp(p_exp.LAST) := v_exp;
END LOOP;
END exp_sp;
/
And in your anonymous block:
The initialisation of v_exp is redundant as the procedure argument is OUT. Not an error but pointless.
You are calling the procedure inside the loop, passing a single element; it should be called once outside the loop and passed the whole collection(s).
Your call to dbms_output.put_line is wrong; that only takes a single argument, so you have to build a string up e.g. with concatenation.
DECLARE
v_empid emp_id_typ := emp_id_typ( );
-- assignment is redundant
-- v_exp emp_exp_typ := emp_exp_typ( );
v_exp emp_exp_typ;
BEGIN
v_empid := emp_id_typ(101, 102, 52, 100, 206);
-- moved this call up, without indexing
exp_sp(v_empid, v_exp);
-- better to use either 1..count, or first..last; don't mix them
-- FOR i IN v_empid.FIRST..v_empid.COUNT
FOR i IN v_empid.FIRST..v_empid.LAST
LOOP
--exp_sp(v_empid(i), v_exp(i));
-- fixed call to dbms_output
dbms_output.put_line(v_empid(i) ||': '|| v_exp(i));
END LOOP;
END;
/
which gets:
101: 28
102: 25
52: -1
100: 30
206: 24
PL/SQL procedure successfully completed.
I need not to print the third variable as it is not in the table.
You can test the v_exp value for the magic number (or null, which is safer, but the procedure needs to be modified slightly for that), and only print the results which you need:
...
FOR i IN v_empid.FIRST..v_empid.LAST
LOOP
IF v_exp(i) != -1 THEN
dbms_output.put_line(v_empid(i) ||': '|| v_exp(i));
END IF;
END LOOP;
...
which gets:
101: 28
102: 25
100: 30
206: 24
PL/SQL procedure successfully completed.
Related
I have a big Oracle script with thousands of package call inside a BEGIN - END;
Is there a way to ignore the lines that causes error and continue executing the next lines? Some sort of "On Error Resume Next" in vb.
If you have only one BEGIN END section, then you can use EXCEPTION WHEN OTHERS THEN NULL.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
exception when others then null;
end;
SQL> /
PL/SQL procedure successfully completed.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
--exception when others then null;
end;
/
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 6
SQL>
The whole concept of "ignore errors" is a bug, and a lie if any errors occur. That is not to say you cannot trap errors and continue processing, just that you MUST handle the errors. For example, assume the use case: "Data has been loaded into a stage table from multiple .csv files. Now load into the tables A and Table B according to ....".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, col_a2);
insert into table_b (col1, col2)
values (rec.col_b1, col_b2);
exception
when others then null;
end;
end loop;
process_message := 'Load Tables A,B Complete';
end ;
Now suppose a user created the a .csv file entered "n/a" in numeric columns where there was no value or the value was unknown. The result of this all too common occurrence is all such rows were not loaded, but you have no way to know that until the user complains their data was not loaded even though you told them it was. Further you have no way of determining the problem.
A much better approach is to "capture and report".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
load_error_occurred boolean := False;
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, rec.col_a2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
begin
insert into table_b (col1, col2)
values (rec.col_b1, rec.col_b2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
end loop;
if load_error_occurred then
process_message := 'Load Tables A,B Complete: Error(s) Detected';
else
process_message := 'Load Tables A,B Complete: Successful No Error(s)';
end if;
end Load_Tables_A_B_from_Stage ;
Now you have informed the user of the actual status, and where you are contacted you can readily identify the issue.
User here is used in the most general sense. It could mean a calling routine instead of an individual. Point is you do not have to terminate your process due to errors but DO NOT ignore them.
I don't think there is any magic one-liner that will solve this.
As others have, use a editor to automate the wrapping of each call within a BEGIN-EXCEPTION-END block might be quicker/easier.
But, if feel a little adventurous, or try this strategy:
Let's assume you have this:
BEGIN
proc1;
proc2;
proc3;
.
.
.
proc1000;
END;
You could try this (untested, uncompiled but might give you an idea of what to try):
DECLARE
l_progress NUMBER := 0;
l_proc_no NUMBER := 0;
e_proc_err EXCEPTION;
-- A 'runner' procedure than manegrs the counters and runs/skips dpending on these vals
PROCEDURE run_proc ( pname IN VARCHAR2 ) IS
BEGIN
l_proc_no := l_proc_no + 1;
IF l_proc_no >= l_progress
THEN
-- log 'Running pname'
EXECUTE IMMEDIATE 'BEGIN ' || pname || '; END;' ;
l_progress := l_progress + 1;
ELSE
-- log 'Skipping pname'
END IF;
EXCEPTION
WHEN OTHERS THEN
-- log 'Error in pname'
l_progress := l_progress + 1;
RAISE e_proc_err;
END;
BEGIN
l_progress := 0;
<<start>>
l_proc_no := 0;
run_proc ( 'proc1' );
run_proc ( 'proc2' );
run_proc ( 'proc3' );
.
.
run_proc ( 'proc1000' );
EXCEPTION
WHEN e_proc_err THEN
GOTO start;
WHEN OTHERS THEN
RAISE;
END;
The idea here is to add a 'runner' procedure to execute each procedure dynamically and log the run, skip, error.
We maintain a global count of the current process number (l_proc_no) and overall count of steps executed (l_progress).
When an error occurs we log it, raise it and let it fall into the outer blocks EXCEPTION handler where it will restart via an (evil) GOTO.
The GOTO is placed such that the overall execution count is unchanged but the process number is reset to 0.
Now when the run_proc is called it sees that l_progress is greater than l_proc_no, and skips it.
Why is this better than simply wrapping a BEGIN EXCEPTION END around each call?
It might not be, but you make a smaller change to each line of code, and you standardise the logging around each call more neatly.
The danger is a potential infinite loop which is why I specify e_proc_err to denote errors within the called procedures. But it might need tweaking to make it robust.
How to handle cursor exception when the select query returns "zero" records
I have a cursor in a procedure, and after cursor initialization I'm iterating through the cursor to access the data from it.
But the problem is when the cursor select query returns 0 records then it throws exception
ORA-06531: Reference to uninitialized collection.
How to handle this exception?
---procedure code
create or replace PROCEDURE BIQ_SECURITY_REPORT
(out_chr_err_code OUT VARCHAR2,
out_chr_err_msg OUT VARCHAR2,
out_security_tab OUT return_security_arr_result ,
)
IS
l_chr_srcstage VARCHAR2 (200);
lrec return_security_report;
CURSOR cur_security_data IS
SELECT
"ID" "requestId",
"ROOM" "room",
"FIRST_NAME" "FIRST_NAME",
"LAST_NAME" "LAST_NAME",
FROM
"BI_REQUEST_CATERING_ACTIVITY" ;
TYPE rec_security_data IS TABLE OF cur_security_data%ROWTYPE
INDEX BY PLS_INTEGER;
l_cur_security_data rec_security_data;
begin
OPEN cur_security_data;
LOOP
FETCH cur_security_data
BULK COLLECT INTO l_cur_security_data
LIMIT 1000;
EXIT WHEN l_cur_security_data.COUNT = 0;
lrec := return_security_report();
out_security_tab := return_security_arr_result(return_security_report());
out_security_tab.delete;
FOR i IN 1 .. l_cur_security_data.COUNT
LOOP
BEGIN
l_num_counter := l_num_counter + 1;
lrec := return_security_report();
lrec.requestid := l_cur_security_data(i).requestId ; lrec.room := l_cur_security_data(i).room ; lrec.firstName := l_cur_security_data(i).firstName ;
IF l_num_counter > 1
THEN
out_security_tab.extend();
out_security_tab(l_num_counter) := return_security_report();
ELSE
out_security_tab := return_security_arr_result(return_security_report());
END IF;
out_security_tab(l_num_counter) := lrec;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE('Error occurred : ' || SQLERRM);
END;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('HERE INSIIDE OTHERS' || SQLERRM);
END;
Can you please explain how handle it.
You must be using out_security_tab, which is an output parameter in some other code where the procedure is called.
In your procedure, If cursor returns zero rows then the loop will not be executed and your code will not even initialize the out_security_tab which will lead to the error that you are facing.
There is a simple way to avoid:
initialize out_security_tab outside the loop -- which will definitely initialize it
You can create one out variable containing details as Y or N based on if cursor rows count -- Not recommended
Cheers!!
I have a record as following and I want to populate this with a for loop.
declare
type ch_type is table of record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab;
begin
for i in (select * from emp) loop
rec_typr.id := i.emp_id;
rec_typr.name := i.first_name;
end loop;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
but I get the error:
PLS:0302 component first must be declared.
Can you help me with this?
Two things are problematic in your code.
1) type ch_type is table of record is syntactically incorrect. You must first declare a record and then define its collection type.
2) Using implicit cursor loop is not an efficient method to load a collection and definitely can't be done the way you're trying to do. Use much simpler BULK COLLECT method instead.
declare
type ch_type is record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab;
begin
select emp_id,first_name bulk collect into
rec_typr from emp;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
/
Output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PL/SQL procedure successfully completed.
EDIT
I need to populate the record through a loop not through bulk collect.
Is it any way?
Yes, there is. But, it is less efficient than the method described above.
declare
type ch_type is record(id number, name varchar2(50));
type ch_type_tab is table of ch_type;
rec_typr ch_type_tab := ch_type_tab();
i INTEGER := 1;
begin
rec_typr.extend;
for rec in
(
select emp_id,first_name bulk collect into
rec_typr from emp
)
loop
rec_typr(i).id := rec.emp_id;
rec_typr(i).name := rec.first_name;
rec_typr.extend;
i := i + 1;
end loop;
for i in rec_typr.first..rec_typr.last
loop
dbms_output.put_line(rec_typr(i).id);
end loop;
end;
/
So i have a stored procedure (that's been watered down below for demo purposes) that aren't passing any conditions and thus aren't inserting/passing any values into my table. I've tried converting the varchar/string that is being passed in by Java to a number but nothing is working. Below is my 'simplified code'
Create or Replace Procedure SAMPLE(rValue IN VARCHAR)
IS
v_Max value.value%type;
v_forecast value.value%type;
BEGIN
--
SELECT BUFFER_MAX_VALUE
INTO v_MAX
FROM look_up;
--
EXCEPTION
WHEN no_data_found
THEN SELECT 0
INTO v_forecast
FROM DUAL;
--
IF to_Number(rValue) < 0 OR to_Number(rValue) > v_MAX)
THEN
dbms_output.put_line('IF1 Works');
insert into value(value_id, value)
values(1, rValue);
ELSIF rValue is null OR to_Number(rValue) = 0
THEN
dbms_output.put_line('IF1A ONLY Works');
END IF;
ELSE
insert into value(value_id, value)
values(1, v_forecast);
dbms_output.put_line('IF1 ELSE ONLY Works');
END SAMPLE;
i tried passing the following in:
BEGIN
SAMPLE('-7');
END;
If the first SELECT BUFFER_MAX_VALUE returns anything, nothing else will be executed because you put absolutely everything into the EXCEPTION section. If you meant to handle that statement only, you should have enclosed it into its own BEGIN-END block, such as
create procedure ...
begin
-- its own begin starts now
begin
select buffer_max_value into v_max
from look_up;
exception
when no_data_found then
-- do something here
end;
-- its own end ends now
-- put the rest of your code here
end;
By the way, does LOOK_UP table contain no rows or only one row, always? Because, as SELECT you wrote contains no WHERE clause, it might raise TOO_MANY_ROWS (which you should also handle).
You declared rValue as VARCHAR2, and then apply TO_NUMBER to it. Why don't you declare it to be a NUMBER, instead? Because, nothing prevents you from passing, for example, 'XYZ' to the procedure, and then TO_NUMBER will miserably fail with the INVALID NUMBER error.
[EDIT: some more exception handling]
EXCEPTION section handles all exceptions that happen in that BEGIN-END block, no matter how many SELECT statements you have. Though, you won't know which one failed, unless you include a little bit of additional (simple) programming.
Note that this is just for showing what I meant; don't handle errors with DBMS_OUTPUT (as, most probably, nobody will see it), and rarely you'd want to handle errors with WHEN OTHERS.
create procedure ...
l_position number;
begin
l_position := 1;
select ... into ... from ...;
l_position := 2;
select ... into ...
exception
when others then
dbms_output.put_line('Error on position ' || l_position ||' '|| sqlerrm);
raise;
end;
As far as I can tell, you wanted the exception section to trap the situation where there is nothing in the lookup table. In that case, you set v_forecast and then continue. That means you need to put the select inside its own block.
I also avoiding multiple to_number calls by setting a constant.
I got rid of the unnecessary select from dual.
I also really really hope that you do not have a table named VALUE with a column named VALUE. Choose more meaningful names.
See how this works for you.
CREATE OR REPLACE PROCEDURE sample (rvalue IN VARCHAR2)
IS
c_rvalue CONSTANT NUMBER := rvalue;
v_max VALUE.VALUE%TYPE;
v_forecast VALUE.VALUE%TYPE;
BEGIN
BEGIN
SELECT buffer_max_value INTO v_max FROM look_up;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
v_forecast := 0;
END;
IF c_rvalue < 0 OR c_rvalue > v_max
THEN
DBMS_OUTPUT.put_line ('IF1 Works');
INSERT INTO VALUE (value_id, VALUE)
VALUES (1, rvalue);
ELSIF c_rvalue IS NULL OR c_rvalue = 0
THEN
DBMS_OUTPUT.put_line ('IF1A ONLY Works');
ELSE
INSERT INTO VALUE (value_id, VALUE)
VALUES (1, v_forecast);
DBMS_OUTPUT.put_line ('IF1 ELSE ONLY Works');
END IF;
END sample;
Here is my problem. I am looping through some values and some of those values raise an exception. I want to log those values, but the program flow should not break. I mean, if I encounter such a value, I will simply log the error and skip to the next value.
here is the simplified version :
drop table test;
--destination Table
create table test
(
id varchar2(2)
);
-- Error log table
create table test_log
(
id varchar2(10)
);
DECLARE
l_num NUMBER;
BEGIN
FOR c IN 90..102
LOOP
INSERT INTO test
VALUES (c);
l_num:=c;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(l_num);
INSERT INTO test_log
VALUES (l_num);
COMMIT;
--raise;
END;
/
My problem is, when it's encountering an error, it simply jumps to the exception section and not looping through the later values in the loop.
You can catch the exception in an inner block:
DECLARE
l_num NUMBER;
BEGIN
FOR c IN 90..102
LOOP
l_num:=c;
BEGIN -- inner block
INSERT INTO test
VALUES (c);
EXCEPTION -- in inner block
WHEN OTHERS THEN
dbms_output.put_line(l_num);
INSERT INTO test_log
VALUES (l_num);
END; -- inner block
END LOOP;
END;
/
The loop won't be interrupted if an exception occurs; only that single insert is affected. Note that you don't really want to commit inside the exception handler as that will commit all the successful inserts so far, not just the error. (If you wanted to log the errors but later roll back you could use an autonomous procedure to do the logging, but that's a separate discussion). And you don't want to re-raise the exception as that would still break the loop and the whole outer anonymous block.
Catching 'others' is generally not a good idea; if you have known errors you could encounter - like a bad data or number format - it's better to catch those explicitly. If you have a wider problem like not being able to extend a data file then the insert inside the exception handler would presumably fail anyway though.
You don't really need l_num any more as c is still in-scope for the inner exception handler, so you could simplify slightly to:
BEGIN
FOR c IN 90..102
LOOP
BEGIN -- inner block
INSERT INTO test
VALUES (c);
EXCEPTION -- in inner block
WHEN OTHERS THEN
dbms_output.put_line(c);
INSERT INTO test_log
VALUES (c);
END; -- inner block
END LOOP;
END;
/
You can also use FORALL.
That answer to your request with SAVE EXCEPTION statement because the FORALL loop to continue even if some DML operations fail. And it will be more efficient than a simple LOOP.
Ex :
DECLARE
CURSOR LCUR$VAL IS
SELECT ID
FROM test1;
TYPE LT$TAB IS TABLE OF TEST%ROWTYPE;
LA$TAB_TEST LT$TAB;
dml_errors EXCEPTION;
PRAGMA EXCEPTION_INIT(dml_errors, -24381);
LC$ERRORS NUMBER(11);
LC$ERRNO NUMBER(11);
LC$MSG VARCHAR2(4000 CHAR);
LC$IDX NUMBER(11);
BEGIN
OPEN LCUR$VAL;
LOOP
FETCH LCUR$VAL BULK COLLECT INTO LA$TAB_TEST LIMIT 1000;
BEGIN
FORALL x IN 1 .. LA$TAB_TEST.COUNT SAVE EXCEPTIONS
INSERT INTO test VALUES LA$TAB_TEST(x);
EXIT WHEN LCUR$VAL%NOTFOUND;
EXCEPTION
WHEN DML_ERRORS THEN
LC$ERRORS := sql%BULK_EXCEPTIONS.COUNT;
FOR idx IN 1 .. LC$ERRORS
LOOP
LC$ERRNO := sql%BULK_EXCEPTIONS (idx).ERROR_CODE;
LC$MSG := sqlerrm(-LC$ERRNO);
LC$IDX := sql%BULK_EXCEPTIONS(idx).error_index;
-- here you can log in table : test_log...
END LOOP;
END;
END LOOP;
CLOSE LCUR$VAL;
END;
Hoping that it can help.