Oracle Exception Handling - Is this correct? - oracle

I have the following...
IF CONDITION1 THEN
-- SELECT STATEMENT MIGHT RETURN DATA
IF CONDITION2 THEN
-- SELECT COUNT
IF CONDITION3 THEN
INSERT INTO TABLE
(
---
)
VALUES (
---
);
End IF;
END IF;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN;
END of Trigger
Is this a correct way of handling exception for select statement inside CONDITION1?

PL/SQL has no way to return to the site of the error, so you need to create a block around the portion you want to ignore specific errors:
IF CONDITION1 THEN
BEGIN
-- SELECT STATEMENT MIGHT RETURN DATA
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
IF CONDITION2 THEN
-- SELECT COUNT
IF CONDITION3 THEN
INSERT INTO TABLE
(
---
)
VALUES (
---
);
End IF;
END IF;
END IF;
END TRIGGER_NAME;
An alternative is to use an explicit cursor, which does not return an error when it is empty:
DECLARE
CURSOR cur_sample is select dummy from dual where 1=0;
v_dummy dual.dummy%type;
BEGIN
IF CONDITION1 THEN
open cur_sample;
fetch cur_sample into v_dummy;
close cur_sample;
IF CONDITION2 THEN
-- SELECT COUNT
IF CONDITION3 THEN
INSERT INTO TABLE
(
---
)
VALUES (
---
);
End IF;
END IF;
END IF;
END;

Depends what you mean by "correct". What you have presented is syntactically valid, yes. But you haven't told us what you actually want to happen so we can't tell you whether the code you posted will actually do what you want.
From a business logic standpoint, are you certain that it really is not an error if your SELECT INTO returns 0 rows? If you are catching and swallowing an exception, that means that you know that it's not really an error. If you're coding a SELECT INTO, however, that implies that you expect exactly one row. It's certainly possible that both of these statements are true but it would be more common that it really is an exception and that it shouldn't simply be swallowed and ignored.
In general, I would prefer to put the exception handler as close as possible to the query that might throw the exception as possible. I would find it cleaner to have something like
IF condition1
THEN
BEGIN
<<select statement>>
EXCEPTION
WHEN no_data_found
THEN
<<do something>>
END;
IF condition2
THEN
...
END IF;
END IF;
That way, if you end up with multiple places in your procedure where a NO_DATA_FOUND exception might be thrown, it will be clear which exceptions are expected and which are unexpected.
When you get to the point of having three layers of nested IF statements, I would tend to suspect that you want to refactor the code into multiple procedures to make the code clearer. For example, rather than having a nested PL/SQL block that executes the SELECT statement, catches the exception, and handles the exception, it would probably be clearer to have a separate function that did all that and then call that function from your trigger.

Related

Oracle Procedure- Statements outside of End Loop not Executing

Need help with below. statements after end loop not executing. Structure is as follows:
Create or replace procedure a.xyz (b in varchar2,c in varchar2.....) is
bunch of variable declaration
cursor c1
begin
open c1;
loop
fetch c1 into ....;
exit when c1%notfound;
insert
insert
merge
merge
commit;
end loop;
insert
select into
send email
exception
end;
insert, select into, send email not getting executed. Any leads?
You didn't post interesting parts of the procedure - what EXCEPTION does?
This is your pseudocode, modified. Read comments I wrote.
Create or replace procedure a.xyz (b in varchar2,c in varchar2.....) is
bunch of variable declaration
cursor c1
begin
open c1;
loop
begin --> inner begin - exception - end block
fetch c1 into ....;
exit when c1%notfound;
insert
insert
merge
merge
exception
when ... then ... --> handle exceptions you expect. If you used
-- WHEN OTHERS THEN NULL, that'a usually a huge mistake.
-- Never use unless you're testing something,
-- without RAISE, or if you really don't care if
-- something went wrong
end; --> end of inner block
end loop;
insert
select into
send email
commit; --> commit should be out of the loop; even better,
-- you should let the CALLER to decide when and
-- whether to commit, not the procedure itself
exception
when ... then ...
end;
Inner BEGIN-EXCEPTION-END block - if exceptions are properly handled - will let the LOOP end its execution. You should log the error you got (for testing purposes, it could be even
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(c1.id ||': '|| sqlerrm);
END;
so that you'd actually see what went wrong. If it were just
EXCEPTION
WHEN OTHERS THEN NULL;
END;
you have no idea whether some error happened, where nor why.

Oracle Trigger only activates the first IF statement

I have a trigger that has multiple IF statements (not elseif). I did bring the code back down to having twice the same IF statement but only the first IF statement is run.
Is this standard Oracle trigger behavior? one of the if statements is pretty much hard-coded while the other uses a function so there is only a select few cases where both IFs are to be run.
Is there a different way to approach an issue like this? or would the issue be in the trigger code and should i post it here?
the code below has this behavior on our server but is dummied down from the original.
create or replace TRIGGER V_INV_TRANS_BIZTALK
AFTER INSERT ON INVENTORY_TRANSACTION
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
l_type pre_advice_header.user_def_type_4%type;
l_status order_header.status%TYPE;
l_from_loc_zone location.zone_1%TYPE;
l_to_loc_zone location.zone_1%TYPE;
l_patype pre_advice_header.pre_advice_type%type;
l_retour pre_advice_header.user_def_type_6%TYPE;
l_client pre_advice_header.client_id%TYPE;
BEGIN
l_client := :new.client_id;
--Client_id = SD
IF l_client = 'SD'
THEN
CASE
--InBound + Return : Pre_Advice_header
WHEN (:new.code = 'PreAdv Status' and :new.notes in ('In Progress --> Complete')) THEN
select PRE_ADVICE_TYPE
into l_patype
from pre_advice_header
where pre_advice_id = :new.reference_id
and client_id = :new.client_id;
END CASE;
END IF;
--TRANSPARIX
IF l_client = 'SD'
--(beldba.is_transparix_client(p_client_id => :new.client_id) = '1' )
THEN
CASE
--Order is shipped
WHEN (:new.notes like ('%--> Shipped')) THEN
--TRANSEXT
INSERT INTO beldba.biztalk_trigger
(
event_id,
status,
system_id,
client_id,
reference_id,
receiver_id,
user_def_type_1
)
VALUES
(
'TRANSExt',
'Pending',
'DCS',
:new.client_id,
:new.reference_id,
'TRANSPARIX',
''
);
END CASE;
END IF;
COMMIT;
EXCEPTION when others then
NULL;
END;
I think there are two problems here:
If there are no matching cases in the CASE statement, and there is no ELSE clause, the CASE statement will raise an ORA-06592 error 'CASE not found when executing CASE statement'. If you don't want to do anything when there is no matching case, add the following section to your CASE statement:
ELSE
NULL;
You end your trigger with
EXCEPTION when others then
NULL;
This swallows all exceptions, including the one that Oracle was raising to tell you that it couldn't find a CASE to execute. Of course, as this is at the end of your trigger, once your trigger raises an exception no further trigger code gets executed.
EXCEPTION WHEN OTHERS THEN NULL is, quite frankly, a cardinal sin in Oracle. I cannot recommend strongly enough deleting this section of your trigger.
Removing the case statements and replacing with IF statements resolved the issue.

if my first query returns null value then my second query has to run even the second query is null then my default value as to shown

In a procedure if my first query returns null value or returns no records then my second query has to run even the second query returns null value or returns no records then a default value has to return. how to make this procedure? should i use if else statement or exception handler?
One way of doing this would be to nest IF statements, something like this:
create or replace function get_queue_id
(p_product_code in mo_product_master.product_code%type
, p_intermediary_code in intrfc_intermediary_mstr_view.intermediary_code%type)
return mo_product_master.queue_id%type
as
return_value number;
begin
-- preferred_choice
begin
select pm.queue_id
into return_value
from mo_product_master pm
where pm.product_code=p_product_code
exception
when no_data_found then
null;
end;
if return_value is null then
-- second_choice
begin
select qim.queue_id
into return_value
from mo_queue_inter_map_master qim
, intrfc_intermediary_mstr_view imv
where qim.category_code =imv.category_code
and imv.intermediary_code=p_intermediary_code;
exception
when no_data_found then
null;
end;
if return_value is null then
-- default_value
select id
into return_value
from mo_queue_master
where queue_name='others'
and status='Active';
end if;
end if;
return return_value;
end;
/
It is a bit clunky but it does the job.
Suppressing the NO_DATA_FOUND exception is not usually recommended practice but I think it fits this scenario: not finding the first QUEUE_ID is part of the regular business logic rather than an exception which needs to be handled. I don't think nesting the subsequent selects in the exception handler is nearly as expressive of the business rules.
write your query like this
select * from tablename
where field1 in nvl(select field1 from table2 ,'defaultvalue')

cursors - %notfound is true even when row is returned

I have a cursor that is used to get some preliminary information for some other processing. It is possible that the query backing the cursor may not return any rows, and in these rare cases, we want to raise a special exception (handled and logged elsewhere so processing is not compeltely halted) so that the user knows about what is most likely bad input. Here's what it looks like:
open c_getPrs(in_pnum);
loop
fetch c_getPrs
into r_rpmRecord;
if c_getPrs%NOTFOUND then
raise X_INVALID_PNUM;
end if;
exit when c_getPrs%rowcount > 1 /*or c_getPrs%NOTFOUND*/;
end loop;
close c_getPrs;
The problem is that the if-statement ALWAYS executes so the exception is always raised, even when a row is returned. I'm not sure why. If there's a better way to handle this kind of logic, I'm open to that too ;)
Your code always goes round the loop twice, and so fails if there are less than 2 rows returned by the cursor. You probably don't need the loop at all:
open c_getPrms(in_pnum);
fetch c_getPrms
into r_prmRecord;
if c_getPrms%NOTFOUND then
raise X_INVALID_PNUM;
end if;
close c_getPrms;
I would prefer to avoid the cursor altogether, and use "select into" instead:
begin
select ...
into r_prmRecord
from ...
where ...
exception
when no_data_found then
raise X_INVALID_PNUM;
end;
This will raise TOO_MANY_ROWS if the select returns more than 1 row. If you don't want that to happen, i.e. more than 1 row is OK, you could just add "AND ROWNUM = 1" to the query.
your problem lies with your exit condition: on the first pass c_getPrms%rowcount is 1, so you get another pass which raises the exception.
Since you want only one fetch I would suggest the following construct:
OPEN c_getPrms(l_input);
FETCH c_getPrms
INTO r_prmRecord;
IF c_getPrms%NOTFOUND THEN
RAISE X_INVALID_PNUM;
END IF;
CLOSE c_getPrms;
I don't like explicit cursor much, so I will also suggest this synthax:
BEGIN
SELECT ...
INTO r_prmRecord
FROM ...
WHERE ... AND rownum = 1; -- your cursor query
EXCEPTION
WHEN no_data_found THEN
RAISE X_INVALID_PNUM;
END;

Are there alternative methods for saying 'next' in a pl/sql for loop?

So I've got a for loop that processes a list of IDs and has some fairly complex things to do. Without going into all the ugly details, basically this:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we will look for duplicate entries, so we can noop if duplicate is found
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
-- NEXT;! NOOP;! but there is no next!
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('no dups found, proceeding');
END;
-- here we have code we only want to execute if there are no dupes already
IF v_dup_check IS NULL THEN
-- if not a duplicate record, proceed...
ELSE
-- reset duplicate check variable
v_dup_check := NULL;
END;
END LOOP;
END;
How I normally handle this is by selecting into a value, and then wrap the following code in an IF statement checking to make sure that duplicate check variable is NULL. But it's annoying. I just want to be able to say NEXT; or NOOP; or something. Especially since I already have to catch the NO_DATA_FOUND exception. I suppose I could write a letter to Oracle, but I'm curious how others handle this.
I could also wrap this in a function, too, but I was looking for something a little cleaner/simpler.
Oracle 11g adds a C-style "continue" loop construct to PL/SQL, which syntactically sounds like what you're looking for.
For your purposes, why not just eliminate the duplicates prior to entering the loop? This could be done by querying l_selected using a table function, and then filtering out records you don't want instead of iterating over every value. Something like...
declare
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
cursor no_dups_cur (p_selected APEX_APPLICATION_GLOBAL.VC_ARR2) is
select * from (
select selected.*,
count(*) over (partition by county_id) cnt -- analytic to find counts grouped by county_id
from table(p_selected) selected -- use table function to treat VC_ARR2 like a table
) where cnt = 1 -- remove records that have duplicate county_ids
;
begin
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
for i in no_dups_cur(l_selected) loop
null; -- do whatever to non-duplicates
end loop;
end;
Just substitute the logic for determining a "duplicate" with your own (didn't have enough info from your example to really answer that part)
Instead of catching NO_DATA_FOUND, how about SELECTing the number of matching entries into a variable, say l_count, and proceeding if this count works out to be zero? Something like the following:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
l_count INTEGER;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we will count duplicate entries, so we can noop if duplicate is found
SELECT COUNT(*) INTO l_count FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
IF l_count = 0 THEN
-- here we have code we only want to execute if there are no dupes already
-- if not a duplicate record, proceed...
END IF;
END LOOP;
END;
To count the number of rows is also possible (see Pourquoi Litytestdata) but you can also do what you want to do in the when_no_data_found exception block.
declare
l_selected apex_application_global.vc_arr2;
l_county_id org_county_accountable.count_id%type;
begin
l_selected := apex_util.string_to_table(:p4_select_lst);
for i in l_selected.first..l_selected.last loop
begin
select count_id
into l_county_id
from org_county_accountable
where organization_id = :p4_id
and county_id = v_county_id;
exception
when no_data_found then
-- here we have code we only want to execute if there are no dupes already
-- if not a duplicate record, proceed...
end;
end loop;
end;
<xmp>
<<next_loop>>
loop
...
...
if ....
then
goto next_loop;
</xmp>
This is a case where a GOTO statement might be useful. See the Oracle Documentation in the control structures to see how to do this. Also, you may want to search around here to find out how to query for the existence of a record. Running a query and waiting for an exception isn't optimal.
Another way - turn the check into a local function:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
...snip...
FUNCTION dup_exists
( p_org_id org_county_accountable.organization_id%TYPE
, p_county_id org_county_accountable.county_id%TYPE
) RETURN BOOLEAN
IS
v_dup_check org_county_accountable.county_id%TYPE;
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = p_org_id AND county_id = p_county_id;
RETURN TRUE;
EXCEPTION WHEN NO_DATA_FOUND THEN
RETURN FALSE;
END;
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we have code we only want to execute if there are no dupes already
IF NOT dup_exists (:P4_ID, v_county_id) THEN
-- if not a duplicate record, proceed...
END;
END LOOP;
END;
Of course, the local function could be re-written to use the count method if you prefer:
FUNCTION dup_exists
( p_org_id org_county_accountable.organization_id%TYPE
, p_county_id org_county_accountable.county_id%TYPE
) RETURN BOOLEAN
IS
l_count INTEGER;
BEGIN
SELECT COUNT(*) INTO l_count
FROM org_county_accountable
WHERE organization_id = p_org_id AND county_id = p_county_id;
RETURN (l_count > 0);
END;
Another method is to raise and handle a user-defined exception:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
duplicate_org_county EXCEPTION;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
BEGIN
-- do some data checking stuff...
-- here we will look for duplicate entries, so we can noop if duplicate is found
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
RAISE duplicate_org_county;
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('no dups found, proceeding');
END;
-- here we have code we only want to execute if there are no dupes already
EXCEPTION
WHEN duplicate_org_county THEN NULL;
END;
END LOOP;
END;
I wouldn't normally do this, but if there were half a dozen reasons to jump to the next record, this might be preferable to multiple nested IFs.
I know this is an oldie but I couldn't help notice that none of the answers above take into account the cursor attributes:
There are four attributes associated with cursors: ISOPEN, FOUND, NOTFOUND, and ROWCOUNT. These attributes can be accessed with the % delimiter to obtain information about the state of the cursor.
The syntax for a cursor attribute is:
cursor_name%attribute
where cursor_name is the name of the explicit cursor.
So in this case you could use ROWCOUNT (which indicates the number of rows fetched so far) for your purposes, like this:
declare
aux number(10) := 0;
CURSOR cursor_name is select * from table where something;
begin
select count(*) into aux from table where something;
FOR row IN cursor_name LOOP
IF(aux > cursor_name%ROWCOUNT) THEN 'do something is not over';
ELSE 'do something else';
END IF;
END LOOP;
end;

Resources