How to handle different exceptions in PL/SQL differently? - oracle

My stored-procedure is looping executing different statements. I want to handle the following situations:
When the statement returns nothing (no_data_found), I want to quietly skip the rest of the loop (continue).
When the statement causes an error of any type, I want to report it, and then skip the rest of the loop (continue);
When the statement finds rows, I want to report it.
The code looks like:
...
LOOP
stmt := 'select * ......';
BEGIN
EXECUTE IMMEDIATE stmt;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
WHEN OTHERS THEN
dbms_out.put_line(stmt || ': ' || SQLCODE);
CONTINUE;
END;
dbms_out.put_line('Found! Use: ' || stmt);
END LOOP;
The above elicits no errors, but the Found-line is printed for every loop-iteration, including for statements, that yield no results...
Why is the CONTINUE-directive ignored -- am I wrong expecting the directive to be obeyed for any exception -- be it NO_DATA_FOUND or anything else?

In your exception block, the action for your NO_DATA_FOUND handler is NULL - so it executes the NULL statement (i.e. does nothing) and falls out of the BEGIN-END block, hitting the dbms_out.put_line('Found! Use: ' || stmt); statement. The only handler which will execute CONTINUE; is the WHEN OTHERS.
One way to get the behavior you describe is to do a SELECT COUNT(*)... into a numeric variable and then just check to see how many rows are returned:
DECLARE
csr SYS_REFCURSOR;
nCount NUMBER;
BEGIN
LOOP
stmt := 'SELECT COUNT(*) FROM (SELECT * from ... WHERE ...)';
OPEN csr FOR stmt;
FETCH csr INTO nCount;
CLOSE csr;
IF nCount > 0 THEN
dbms_out.put_line('Found! Use: ' || stmt);
ELSE
dbms_out.put_line(stmt || ': ' || SQLCODE);
END IF;
END LOOP;
END;
Of course this is not really valid as there's no way for the value of stmt to change, but I suspect your "real" code handles that.

Related

Oracle pl/sql Array

let's see if somebody can help me, I need to delete rows from different tables and I did think to do it using an array so i wrote this :
DECLARE
TYPE mytype_a IS TABLE OF VARCHAR2(32) INDEX BY BINARY_INTEGER;
mytype mytype_a;
BEGIN
mytype(mytype.count + 1) := 'MYTABLE';
FOR i IN 1 .. mytype.count
LOOP
DELETE mytype(i) WHERE valid = 'N';
END LOOP;
END;
Trying to run this piece of code using sqldeveloper I get the ORA-00933 command not properly ended, if I put directly the table name it works, what am I doing wrong?
Thank you.
Thank you very much guys, it works perfectly.
This is not the correct approach. You have to use Dynamic SQL for this -
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar(500) := NULL;
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid =''N''';
EXECUTE IMMEDIATE stmt;
end loop;
END;
You would need to use dynamic SQL, concatenating the table name from the collection into the statement, inside your loop:
execute immediate 'DELETE FROM ' || mytype(i) || ' where valid = ''N''';
Or you can put the statement into a variable so you can display it for debugging purposes, and then execute that, optionally with a bind variable for the valid value:
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
... which I've made also display how many rows were deleted from each table. Note though that you shoudln't rely on the caller being able to see anything printed with dbms_output - it's up to the client whether it shows it.
The whole anonymous block would then be:
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar2(4000);
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
end loop;
END;
/
You could use a built-in collection type to simplify it even further.
db<>fiddle showing some options.
Hopefully this doesn't apply, but if you might have any tables with quoted identifiers then you would need to add quotes in the dynamic statement, e.g.:
stmt := 'DELETE FROM "' || mytype(i) || '" where valid = :valid';
... and make sure the table name values in your collection exactly match the names as they appear in the data dictionary (user_tables.table_name).

Why are none of my conditions being met in this proc?

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;

How can I modify my PL/SQL procedure to go to my exception handling?

I am using SQL developer to write a procedure.
The objective is to get the name of the drainage system from one table and get the count of how much the drainage system name code appears in another table.
My procedure works, but when I enter an incorrect value, it does not go to the exception section. For example, when I input ‘Mexico River’, this name does not exist in the table. So, I want my exception section to realize that this is an incorrect value being entered.
My question is how do I modify my code, so it can detect incorrect values and go to my exception section.
Below is a snippet of my code:
PROCEDURE number_of_rivers --second procedure with 1 parameter
(input_sysName IN TBLDrainage.DRAINAGE_SYS%TYPE)
is
-- Create a cursor
cursor c_river is
select code, drainage_sys
from TBLDRAINAGE
where DRAINAGE_SYS = input_sysName;
v_rivercount Number;
r_variable c_river %rowtype;
Begin
FOR r_variable in c_river
loop
select count (Drainage_sys) into v_rivercount
from TBLRIVER
where DRAINAGE_SYS = r_variable.code;
DBMS_OUTPUT.PUT_LINE (UPPER(input_sysName) || ' has ' || v_rivercount || ' river(s) in the drainage system.');
end loop;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Error: Please enter a valid drainage system name');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Error in finding system');
END ;
The CURSOR..FOR loop has the property of executing zero or more times. It doesn't throw NO_DATA_FOUND.
There are a couple of solutions. One is to include a count inside the loop, and raise an exception afterwards.
l_count := 0;
FOR r_variable in c_river
loop
....
l_count := l_count + 1;
end loop;
if l_count = 0 then
raise NO_DATA_FOUND;
end if;
The other would be to validate the input parameter at the start of your program.
begin
open c_river;
fetch c_river into r_variable;
if c_river%notfound then
raise NO_DATA_FOUND;
else
select count (Drainage_sys)
into v_rivercount
from TBLRIVER
where DRAINAGE_SYS = r_variable.code;
DBMS_OUTPUT.PUT_LINE (UPPER(input_sysName) || ' has ' || v_rivercount || ' river(s) in the drainage system.');
end if;
close c_river;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Error: Please enter a valid drainage system name');
close c_river;
END ;
In this solution I have removed the loop, because I would expect a look-up on drainage system should be unique and return one record. Please re-instate the loop if your data model isn't like that.
I have retained your names for the cursor and its row variables but you should re-name them. They are used for selecting drainage systems not rivers, and their names ought to reflect that. Discipline in naming things is a useful habit to acquire, as misleading variable names will cause confusion in larger chunks of code.
Also, swallowing exceptions like this is very bad:
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Error in finding system');
Oracle has thousands of error messages: it better to do nothing with the error message than to throw it away.

Reusing PROCEDURE/ FUNCTION whichever to do the same thing using different values

I have many procedures that do the same thing:
they refresh materialized view and check if the count is not 0, then push that data into production tables. this is the skeleton of what each one does, the only thing that changes is the name of the materialized view. I thought about creating one function that will take in the name of the MV and process it, but it is not working :(
create or replace
function REFRESH_MV (mv_to_refresh IN VARCHAR2)
RETURN VARCHAR2
AUTHID CURRENT_USER
AS
COUNTS INT;
begin
DBMS_MVIEW.REFRESH(mv_to_refresh,'C');
COMMIT;
SELECT COUNT(*) INTO COUNTS FROM 'SEMANTIC.' || mv_to_refresh;
IF COUNTS = 0 THEN
RETURN 'SEMANTIC.' || mv_to_refresh || ' is empty';
ELSE
'SEMANTIC_READ_ONLY.' || RELOAD_TABLE(mv_to_refresh);
RETURN 'SEMANTIC_READ_ONLY.' || mv_to_refresh || ' has been refreshed today';
END IF;
EXCEPTION WHEN OTHERS THEN NULL;
end;
You have to use EXECUTE IMMEDIATE or DBMS_SQL to do that; the first one should be easier to use in your case.
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM 'SEMANTIC.' || mv_to_refresh INTO COUNTS;
should do the trick.
You should use dynamic SQL for this purpose:
CREATE OR REPLACE FUNCTION REFRESH_MV (mv_to_refresh IN VARCHAR2)
RETURN VARCHAR2
AUTHID CURRENT_USER
AS
COUNTS INT;
VSQL VARCHAR2(100);
begin
DBMS_MVIEW.REFRESH('SEMANTIC.' || mv_to_refresh, 'C');
COMMIT;
VSQL := 'SELECT COUNT(1) FROM SEMANTIC.' || mv_to_refresh;
EXECUTE IMMEDIATE VSQL INTO COUNTS;
IF COUNTS = 0 THEN
RETURN 'SEMANTIC.' || mv_to_refresh || ' is empty';
ELSE
SEMANTIC_READ_ONLY.RELOAD_TABLE(mv_to_refresh);
RETURN 'SEMANTIC_READ_ONLY.' || mv_to_refresh
|| ' has been refreshed today';
END IF;
EXCEPTION
WHEN OTHERS THEN
RETURN 'Error has occured: ' || SQLERRM;
END;
Please make sure you pass view name without schema prefix as input parameter.
You should also note that scince it function it should return value or raise exception. But in you example function will return nothing in case of exception.
I didn't quite get the semantics of RELOAD_TABLE() procedure. In example given it is supposed to be a some procedure in SEMANTIC_READ_ONLY schema. In case you really need the appropriate function to be evaluated dynamically, you again can use dynamic SQL to construct the valid string contaning the code and call it:
vsql := 'begin SCHEMA_NAME.' || GET_PROCEDURE_FOR(mv_to_refresh) || '; end;';
execute immediate vsql;

The use of sql%notfound on implicit cursor

I'm completely new to oracle PLSQL. Any help is appreicated.
I could not find similiar question on SO (Maybe it's too basic?)
I'm running the code from TOAD, Oracle 11G
SET SERVEROUTPUT ON
DECLARE
var titres%ROWTYPE;
BEGIN
select reference, sicovam into
var.reference, var.sicovam
from titres
where reference = '1234';
if sql%notfound then
dbms_output.put_line('NOT FOUND');
else
dbms_output.put_line(var.reference || ' ' || var.sicovam);
end if;
END;
If the Where Clause can extract one row of data, then it will run the else part
If the Where Clause cannot extract any row, then it will display the error:
ORA-01403: no data found
ORA-06512: at line 4
Can anyone point me in the right direction? Thanks
I have tried using the basic exception handling code
When others then
null;
end;
Then I am getting another strange result:
If the Where Clause can extract one row of data, then it will NOT run the else part or the if part.
When a query inside a pl/sql block returns no rows the NO_DATA_FOUND exception is raised immediately and execution of the block will halt. So the if sql%notfound then condition will never be evaluated. To catch that exception and respond accordingly, you need EXCEPTION section.
SET SERVEROUTPUT ON
DECLARE
var titres%ROWTYPE;
BEGIN
-- In this case you have to be sure that the query returns only one row
-- otherwise the exception ORA-01422 will be raised
select reference, sicovam into
var.reference, var.sicovam
from titres
where reference = '1234';
dbms_output.put_line(var.reference || ' ' || var.sicovam);
EXCEPTION
WHEN NO_DATA_FOUND
THEN dbms_output.put_line('NOT FOUND');
END;
with select into you need to use exceptions NO_DATA_FOUND and TOO_MANY_ROWS
SET SERVEROUTPUT ON
DECLARE
var titres%ROWTYPE;
BEGIN
select reference, sicovam into
var.reference, var.sicovam
from titres
where reference = '1234';
dbms_output.put_line(var.reference || ' ' || var.sicovam);
exception
when no_data_found
then
dbms_output.put_line('NOT FOUND');
when too_many_rows
then
dbms_output.put_line('2+ ROWS found');
END;

Resources