Raise exception in stored procedure based on view entries - sql

I have a stored procedure. I would like to implement the below logic, which I have written in pseudocode.
If the below query has one of more entries:
SELECT
NULL
FROM
table1
WHERE
condition
GROUP BY
column
HAVING
COUNT(1) > 1
UNION ALL
SELECT
NULL
FROM
table1 a
WHERE
condition
AND EXISTS (
SELECT
NULL
FROM
table2 b
WHERE
condition
);
Then raise an exception and stop the stored procedure.

Here is an example of raising an exception if a particular value is found from a query:
declare
somevar dual.dummy%type;
begin
select 'Y' into somevar
from dual;
if somevar = 'Y' then
raise_application_error(-20123, 'Hull breach on deck 15. Abandon ship.');
end if;
end;
The "select from dual" can be any query, so feel free to substitute your unions and counts (though we should really stick to the standard count(*), not count('Dracula') etc).

Let's do this with the sample emp/dept schema - just plug in your own statement for your use case. You do need to declare since in pl/sql you cannot "just select". You always need to select into a variable. I usually just select the number 1 into a dummy variable of type number. The trick is to raise the exception after the SELECT INTO and do nothing on NO_DATA_FOUND.
You can use named exceptions to distinguish different cases but since a no data found will throw an exception you have to do each of the cases in its own block. The cleanest is to handle all named exceptions in the final exception block.
DECLARE
l_dummy NUMBER;
king_exists EXCEPTION;
dave_exists EXCEPTION;
BEGIN
BEGIN
SELECT 1 INTO l_dummy FROM emp WHERE ename = 'DAVE';
RAISE dave_exists;
EXCEPTION WHEN NO_DATA_FOUND THEN
NULL;
END;
BEGIN
SELECT 1 INTO l_dummy FROM emp WHERE ename = 'KING';
RAISE king_exists;
EXCEPTION WHEN NO_DATA_FOUND THEN
NULL;
END;
EXCEPTION
WHEN dave_exists THEN
raise_application_error(-20000,'My expection error message');
WHEN king_exists THEN
raise_application_error(-20001,'King exists');
END;
/

Related

How to check if row exists before SELECT INTO statement in Oracle SQL

I'm using Oracle SQL and have a procedure that is doing some operations on tables. During the procedure there is a "SELECT x INTO y FROM TABLE z WHERE..." statement inside a loop. Unfortunatly during the procedure I can't guarante that there is always a row to the corresponding where condition because it changes dynamically.
Is it possible to check if a row exists before the statement? I was thinking of sth like "if exists(select ...) then SELECT X INTO y..."
Thanks for the help!
Jack
Well, there's no point in checking it first, and re-using the same statement again.
You could handle the exception (possibly in an inner BEGIN-EXCEPTION-END block):
declare
y number;
begin
begin --> inner block starts here
select x into y from z where ...
insert into ...
exception
-- handle it, somehow; I chose not to do anything
when no_data_found then
null;
end; --> inner block ends here
end;
Or, if you used cursor FOR loop, you wouldn't have to handle it because - if select returns x, insert would run. Otherwise, nothing in that loop would ever be executed:
begin
for cur_r in (select x from z where ...) loop
insert into ...
end loop;
end;
An exception handler as in Littlefoot's answer is the most correct and explicit approach, however just for completeness you might also consider using an aggregate.
Value 'X' exists in the table:
declare
p_someparam varchar2(1) := 'X';
l_somevalue varchar2(1);
l_check number;
begin
select max(dummy), count(*) into l_somevalue, l_check
from dual d
where d.dummy = p_someparam;
dbms_output.put_line('Result: '||l_somevalue);
dbms_output.put_line(l_check||' row(s) found');
end;
Result: X
1 row(s) found
Value 'Z' does not exist in the table:
declare
p_someparam varchar2(1) := 'Z';
l_somevalue varchar2(1);
l_check number;
begin
select max(dummy), count(*) into l_somevalue, l_check
from dual d
where d.dummy = p_someparam;
dbms_output.put_line('Result: '||l_somevalue);
dbms_output.put_line(l_check||' row(s) found');
end;
Result:
0 row(s) found
You can add logic to handle the cases where the count check is 0 or greater than 1.
If you are having procedure then I should say use if statement and then write the sql:
select some_column into some_variable from tablename where condition
IF somevariable not in (<list of values separated by comma>)THEN
{statements to execute }
END IF;

Pl/SQL problem with syntax in validation code

Trying to write this type of script in PL / SQL on the second line with "WHEN", I get a syntax error. Please help
The function should validate and contain the logic it is trying to write
I don't have much experience. How could I write it differently?
create or replace FUNCTION BUSINESS_PROVIDER_GET(valueGet IN VARCHAR2)
RETURN VARCHAR2
IS
v_value business_provider_configuration.billing_account_id%TYPE ;
BEGIN
DECLARE
V_BUSINESS_PROVIDER business_provider_configuration.business_provider%TYPE;
V_TRADING_NAME business_provider_configuration.trading_name%TYPE;
V_CUSTOMER_ID business_provider_configuration.customer_id%TYPE;
V_PROVIDER BUSINESS_PROVIDER_CONFIGURATION%TYPE;
BEGIN
SELECT
*
INTO V_BUSINESS_PROVIDER
FROM
BUSINESS_PROVIDER_CONFIGURATION
WHERE
V_BUSINESS_PROVIDER = valueGet
AND
Upper(V_BUSINESS_PROVIDER) = Upper (valueGet);
EXCEPTION
WHEN no_data_found THEN
SELECT
*
INTO V_TRADING_NAME
FROM
BUSINESS_PROVIDER_CONFIGURATION
WHERE
V_TRADING_NAME = valueGet
AND
Upper(V_TRADING_NAME) = Upper (valueGet);
EXCEPTION
WHEN no_data_found THEN
SELECT
*
INTO V_CUSTOMER_ID
FROM
BUSINESS_PROVIDER_CONFIGURATION
WHERE
V_CUSTOMER_ID = valueGet
AND
Upper(V_CUSTOMER_ID) = Upper (valueGet);
EXCEPTION
WHEN no_data_found
raise_application_error(-20000, 'Not found');
Exception is 'end-of-block' keyword - you can't just pull it out of nowhere multiple times in the same block.
In function you are allowed to have one exception statement, because function body is block on it's own.
If inside exception block you want to handle another exception, the code which may raise that exception must be inside a block.
It helps a lot to fix such issues when code is properly indented - i had to do it here:
create or replace FUNCTION BUSINESS_PROVIDER_GET(valueGet IN VARCHAR2)
RETURN VARCHAR2
IS
v_value business_provider_configuration.billing_account_id%TYPE ;
BEGIN
DECLARE
V_BUSINESS_PROVIDER business_provider_configuration.business_provider%TYPE;
V_TRADING_NAME business_provider_configuration.trading_name%TYPE;
V_CUSTOMER_ID business_provider_configuration.customer_id%TYPE;
V_PROVIDER BUSINESS_PROVIDER_CONFIGURATION%TYPE;
BEGIN
SELECT *
INTO V_BUSINESS_PROVIDER
FROM BUSINESS_PROVIDER_CONFIGURATION
WHERE V_BUSINESS_PROVIDER = valueGet
AND Upper(V_BUSINESS_PROVIDER) = Upper (valueGet);
EXCEPTION
WHEN no_data_found THEN
BEGIN -- Block that can raise second exception
SELECT *
INTO V_TRADING_NAME
FROM BUSINESS_PROVIDER_CONFIGURATION
WHERE V_TRADING_NAME = valueGet
AND Upper(V_TRADING_NAME) = Upper (valueGet);
EXCEPTION
WHEN no_data_found THEN
BEGIN -- Block that can raise third exception
SELECT *
INTO V_CUSTOMER_ID
FROM BUSINESS_PROVIDER_CONFIGURATION
WHERE V_CUSTOMER_ID = valueGet
AND Upper(V_CUSTOMER_ID) = Upper (valueGet);
EXCEPTION
WHEN no_data_found
raise_application_error(-20000, 'Not found');
END; -- End of block that can raise third exception
END; -- End of block that can raise second exception
END BUSINESS_PROVIDER_GET; -- End of function body
I do not have your tables so i was not able to test it, there might still be some issues here.
Also, the code is a little bit of mess- you should not have to look in multiple columns that have distinct business meaning for just one value.
And i have no idea, how do you want to 'select *' into single scalar variable from a table, that has multiple columns - just specify, which column you want to fetch.
I would argue that 'select *' should never be used in stored code, unless you fetch into record variable that inherits rowtype from table.
First of all, the conditions as you are using them will never return any values. You are defining the variable, for example V_BUSINESS_PROVIDER and right after that compare it against valueGet. Since the V_BUSINESS_PROVIDER is null the result of conditions will be always false
Second, the set of conditions
V_BUSINESS_PROVIDER = valueGet
AND Upper(V_BUSINESS_PROVIDER) = Upper(valueGet);
is reduntant. If you need values to be exactly the same case (e.g. 'Word' <> 'wORD') you have to use only lines like this
V_BUSINESS_PROVIDER = valueGet
otherwise, if you don't need to compare cases (e.g. 'Word' = 'wORD') you need to use such conditions only
AND Upper(V_BUSINESS_PROVIDER) = Upper(valueGet)
Third. As I see the aim is to get column values into variables named after them. In this case you have to specify the column names in the select statement. E.g.
select business_provider
into V_BUSINESS_PROVIDER
FROM BUSINESS_PROVIDER_CONFIGURATION
....
Fourth thing is the using exception handlers for non-handling purpose is a bad idea. I'd recomment you to rewrite this part like the following
create or replace FUNCTION BUSINESS_PROVIDER_GET(valueGet IN VARCHAR2)
RETURN VARCHAR2
IS
v_value business_provider_configuration.billing_account_id%TYPE ;
V_BUSINESS_PROVIDER business_provider_configuration.business_provider%TYPE;
V_TRADING_NAME business_provider_configuration.trading_name%TYPE;
V_CUSTOMER_ID business_provider_configuration.customer_id%TYPE;
V_PROVIDER BUSINESS_PROVIDER_CONFIGURATION%TYPE;
BEGIN
begin
select ... into V_BUSINESS_PROVIDER ...;
exception
when no_data_found then
V_BUSINESS_PROVIDER := null;
end;
if V_BUSINESS_PROVIDER is null then
begin
select ... into V_TRADING_NAME ...;
exception
when no_data_found then
V_TRADING_NAME := null;
end;
end if;
if V_TRADING_NAME is null then
begin
select ... into V_CUSTOMER_ID ...;
exception
when no_data_found then
raise(...); -- though I'm not sure if you have to raise it, the no_data_found will get raised anyway you can handle the native exception
end;
end if;
end BUSINESS_PROVIDER_GET;

How to get NO_DATA_FOUND exception to output when using COUNT(*)?

I am trying to determine why my exception output is not returning correctly.
This is for Oracle SQL Developer, practicing with PL/SQL. I can provide the table code if requested.
SET serveroutput on
CREATE OR REPLACE Procedure GetPermissions(user IN VARCHAR, docNum OUT
INTEGER)
IS
BEGIN
SELECT count(*) INTO docNum FROM UserPermissions where UserName=user;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('Nothing found for this username');
END;
/
DECLARE
doc_count INTEGER;
BEGIN
doc_count:=0;
GetPermissions('Steve', doc_count);
dbms_output.put_line(' Number of documents: ' || doc_count);
END;
/
I am expecting the exception to return its output (nothing found) when 'Steve' is ran through as that is not a username in the table I created. When running currently is just shows "Number of documents: 0".
COUNT always returns a result, in this case, it returns a single row with 0 in it:
COUNT(*)
--------
0
NO_DATA_FOUND occurs only when the SELECT INTO query doesn't return any row.
For example, NO_DATA_FOUND will be thrown, if you try to select permission for a given user:
SELECT permission
INTO p_permission
FROM UserPermissions
WHERE UserName=user;
In this case, the query will return an empty result.
select into query returns with No_Data_found exception when the out put of the query is no rows. not when it is 0.
Another exception you get with select into clause is too many rows where the query returns more that 1 row and you are trying to assign all those to a variable.

Too Many Rows throwing but only one is selected

I have this procedure which just deletes a row based on a column field called AppID. This procedure get's a value from another column called AppNbr based on that rows AppID column. The procedure is failing with a TOO_MANY_ROWS exception when it tries to SELECT a row. This is the PL/SQL:
DECLARE
lvnApplNbr NUMBER;
PROCEDURE deleteAppl(applId IN VARCHAR2) IS
BEGIN
BEGIN
SELECT ApplNbr -- Exception thrown here
INTO lvnApplNbr
FROM Appl
WHERE ApplID = applId;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
-- ... Delete it after some logic
END; -- End Procedure
BEGIN
...
deleteAppl('571E00BA-70E6-4523-BEAC-4568C3DD1A7D');
...
END;
The TOO_MANY_ROWS exception is thrown when it SELECT INTOs. I have no idea why it is throwing that error because if I just query this:
SELECT ApplNbr FROM Appl WHERE ApplId = '571E00BA-70E6-4523-BEAC-4568C3DD1A7D';
Only one row will come back with the correct ApplId.
What is going on?
Just use an alias for the related table (Appl):
PROCEDURE deleteAppl(applId IN VARCHAR2) IS
.....
.....
SELECT ApplNbr
INTO lvnApplNbr
FROM Appl a
WHERE a.ApplID = applId;
......
or change your parameter's name (applId) to another name such as i_applId :
PROCEDURE deleteAppl(i_applId IN VARCHAR2) IS
.....
.....
SELECT ApplNbr
INTO lvnApplNbr
FROM Appl
WHERE ApplID = i_applId;
......
Because in your case multiple matching perceived, if your parameter's name and column name are identical.

Oracle PL/SQL - Are NO_DATA_FOUND Exceptions bad for stored procedure performance?

I'm writing a stored procedure that needs to have a lot of conditioning in it. With the general knowledge from C#.NET coding that exceptions can hurt performance, I've always avoided using them in PL/SQL as well. My conditioning in this stored proc mostly revolves around whether or not a record exists, which I could do one of two ways:
SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....
-or-
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....
The second case seems a bit more elegant to me, because then I can use NEEDED_FIELD, which I would have had to select in the first statement after the condition in the first case. Less code. But if the stored procedure will run faster using the COUNT(*), then I don't mind typing a little more to make up processing speed.
Any hints? Am I missing another possibility?
EDIT
I should have mentioned that this is all already nested in a FOR LOOP. Not sure if this makes a difference with using a cursor, since I don't think I can DECLARE the cursor as a select in the FOR LOOP.
I would not use an explicit cursor to do this. Steve F. no longer advises people to use explicit cursors when an implicit cursor could be used.
The method with count(*) is unsafe. If another session deletes the row that met the condition after the line with the count(*), and before the line with the select ... into, the code will throw an exception that will not get handled.
The second version from the original post does not have this problem, and it is generally preferred.
That said, there is a minor overhead using the exception, and if you are 100% sure the data will not change, you can use the count(*), but I recommend against it.
I ran these benchmarks on Oracle 10.2.0.1 on 32 bit Windows. I am only looking at elapsed time. There are other test harnesses that can give more details (such as latch counts and memory used).
SQL>create table t (NEEDED_FIELD number, COND number);
Table created.
SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);
1 row created.
declare
otherVar number;
cnt number;
begin
for i in 1 .. 50000 loop
select count(*) into cnt from t where cond = 1;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 1;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.70
declare
otherVar number;
begin
for i in 1 .. 50000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 1;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:03.06
Since SELECT INTO assumes that a single row will be returned, you can use a statement of the form:
SELECT MAX(column)
INTO var
FROM table
WHERE conditions;
IF var IS NOT NULL
THEN ...
The SELECT will give you the value if one is available, and a value of NULL instead of a NO_DATA_FOUND exception. The overhead introduced by MAX() will be minimal-to-zero since the result set contains a single row. It also has the advantage of being compact relative to a cursor-based solution, and not being vulnerable to concurrency issues like the two-step solution in the original post.
An alternative to #Steve's code.
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
FOR foo_rec IN foo_cur LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
The loop is not executed if there is no data. Cursor FOR loops are the way to go - they help avoid a lot of housekeeping. An even more compact solution:
DECLARE
BEGIN
FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
Which works if you know the complete select statement at compile time.
#DCookie
I just want to point out that you can leave off the lines that say
EXCEPTION
WHEN OTHERS THEN
RAISE;
You'll get the same effect if you leave off the exception block all together, and the line number reported for the exception will be the line where the exception is actually thrown, not the line in the exception block where it was re-raised.
Stephen Darlington makes a very good point, and you can see that if you change my benchmark to use a more realistically sized table if I fill the table out to 10000 rows using the following:
begin
for i in 2 .. 10000 loop
insert into t (NEEDED_FIELD, cond) values (i, 10);
end loop;
end;
Then re-run the benchmarks. (I had to reduce the loop counts to 5000 to get reasonable times).
declare
otherVar number;
cnt number;
begin
for i in 1 .. 5000 loop
select count(*) into cnt from t where cond = 0;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 0;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:04.34
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 0;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.10
The method with the exception is now more than twice as fast. So, for almost all cases,the method:
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....
is the way to go. It will give correct results and is generally the fastest.
If it's important you really need to benchmark both options!
Having said that, I have always used the exception method, the reasoning being it's better to only hit the database once.
Yes, you're missing using cursors
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
OPEN foo_cur;
FETCH foo_cur INTO foo_rec;
IF foo_cur%FOUND THEN
...
END IF;
CLOSE foo_cur;
EXCEPTION
WHEN OTHERS THEN
CLOSE foo_cur;
RAISE;
END ;
admittedly this is more code, but it doesn't use EXCEPTIONs as flow-control which, having learnt most of my PL/SQL from Steve Feuerstein's PL/SQL Programming book, I believe to be a good thing.
Whether this is faster or not I don't know (I do very little PL/SQL nowadays).
Rather than having nested cursor loops a more efficient approach would be to use one cursor loop with an outer join between the tables.
BEGIN
FOR rec IN (SELECT a.needed_field,b.other_field
FROM table1 a
LEFT OUTER JOIN table2 b
ON a.needed_field = b.condition_field
WHERE a.column = ???)
LOOP
IF rec.other_field IS NOT NULL THEN
-- whatever processing needs to be done to other_field
END IF;
END LOOP;
END;
you dont have to use open when you are using for loops.
declare
cursor cur_name is select * from emp;
begin
for cur_rec in cur_name Loop
dbms_output.put_line(cur_rec.ename);
end loop;
End ;
or
declare
cursor cur_name is select * from emp;
cur_rec emp%rowtype;
begin
Open cur_name;
Loop
Fetch cur_name into Cur_rec;
Exit when cur_name%notfound;
dbms_output.put_line(cur_rec.ename);
end loop;
Close cur_name;
End ;
May be beating a dead horse here, but I bench-marked the cursor for loop, and that performed about as well as the no_data_found method:
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
otherVar := foo_rec.NEEDED_FIELD;
end loop;
otherVar := 0;
end;
end loop;
end;
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.18
The count(*) will never raise exception because it always returns actual count or 0 - zero, no matter what. I'd use count.
The first (excellent) answer stated -
The method with count() is unsafe. If another session deletes the row that met the condition after the line with the count(*), and before the line with the select ... into, the code will throw an exception that will not get handled.
Not so. Within a given logical Unit of Work Oracle is totally consistent. Even if someone commits the delete of the row between a count and a select Oracle will, for the active session, obtain the data from the logs. If it cannot, you will get a "snapshot too old" error.