DO $$
BEGIN
-- Check to make sure that event starts after users created
IF EXISTS (
SELECT users.id
FROM event
INNER JOIN users ON event.user_id::INT = users.id
WHERE event.start > users.created
)
THEN
RAISE NOTICE 'SUCCESSFUL %', now();
ELSE
RAISE EXCEPTION 'ERROR on % - All Event start timestamps must be created after users are created',now();
END IF;
END $$;
This exception is to check for timestamps and works well. But I want to add a similar condition and at-least 3 more exception conditions. I want to now return the exceptions at the end. I do-not want process to stop if first check fails. I want all the failed tests to be raised all at once in the end. How do I do that ? I read about stacked diagnostics bu dontt know if its the right approach or should I just use IF-else if ?
It is in the nature of an exception to interrupt processing, so you cannot simply use exceptions for that.
You could collect all exceptions in a (temporary?) table and communicate them to the client in the end, or concatenate them to a single large exception.
You could also emit the individual messages as NOTICE and throw an exception at the end.
Related
I'm trying to make an exception with RAISE in PL / SQL. I did the script but when I want to run it does not show me one of the two messages and it just gives me 'anonymous block completed'.
Accept cititor prompt 'Introduceti un cititor';
DECLARE data_la_limita EXCEPTION; data_returnare varchar(10);
--cititor varchar(10);
BEGIN
SELECT s.data_dereturnat INTO data_returnare FROM fisa_imprumuturi s left join legitimatii s1
on s1.nrlegitimatie = s.nr_legitimatie left
join cititori s2 on s2.codcititor = s1.codcititor
WHERE s2.numecititor like '%&cititor%'; IF (data_returnare > sysdate ) THEN
RAISE data_la_limita;
END IF;
EXCEPTION
WHEN data_la_limita THEN
DBMS_OUTPUT.PUT_LINE('Cititorul a trecut peste data returnarii!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Cititorul trebuie sa returneze la data: ' || data_returnare); END;
I tried to replace sysdate with a date manually ('01-May-2019') but is not working.
I'm trying to make an exception with RAISE in PL / SQL ... but when I want to run it does not show me one of the two messages
You're not seeing the messages because you are running your program in an environment which doesn't show DBMS_OUTPUT. You have coded an EXCEPTION handler which suppresses your exceptions and does not re-raise them. This is bad practice because DBMS_OUTPUT is not a mechanism for propagating exceptions: when output is suppressed or the program runs as an autonomous background routine (which is the main use of PL/SQL) there is no way to tell that the program failed.
You could enable SEVEROUTPUT (in SQL*Plus) or configure a DBMS_OUTPUT tab (in an IDE like SQL Developer). Doing this means you would see the messages next time your program runs.
But it would be better just to raise exceptions and let the calling program handle them. Given that, you should probably re-write your program something like this:
Accept cititor prompt 'Introduceti un cititor';
DECLARE
data_la_limita EXCEPTION;
data_returnare varchar(10);
--cititor varchar(10);
BEGIN
SELECT s.data_dereturnat
INTO data_returnare
FROM fisa_imprumuturi s
left join legitimatii s1 on s1.nrlegitimatie = s.nr_legitimatie left
join cititori s2 on s2.codcititor = s1.codcititor
WHERE s2.numecititor like '%&cititor%';
IF (data_returnare > sysdate ) THEN
RAISE data_la_limita;
END IF;
EXCEPTION
WHEN data_la_limita THEN
raise_application_error(-20000, 'Cititorul a trecut peste data returnarii!');
END;
It is better not to code a WHEN OTHERS handler unless we're logging the message. Even then we should execute a RAISE to pass the actual error instead of an unhelpful generic message. The calling program needs to know what went wrong so it can make the correct decision about what to do next (e.g. ignore and continue, abort and re-raise, something else).
In SQL Developer, when running some PL/SQL, when the procedure is completed, the message 'PL/SQL procedure successfully completed.' is returned.
The PL/SQL that is run may return error messages to the user if the operation could not be completed for any reason through DBMS_OUTPUT.PUT_LINE, however, the user will also see 'PL/SQL procedure successfully completed.', which could be misleading (especially if the Script Output window is small enough that the DBMS_OUTPUT is not visible).
Is there any way to have the DBMS_OUTPUT return what it should whilst also having the script not return 'PL/SQL procedure successfully completed.'?
If not, are there any alternatives in SQL Developer that I may be unaware of to provide instant personalised feedback to the user?
declare
testex exception;
begin
if 1=1 then
raise testex;
end if;
exception when testex then
dbms_output.put_line('Error msg');
end;
Below code works in my end. Did you try to run your code like below?
Copied text from a website to explain the SET FEEDBACK OFF command.
Source link: https://docs.oracle.com/cd/B19306_01/server.102/b14357/ch12040.htm
SET FEED[BACK] {6 | n | ON | OFF} -
Displays the number of records returned by a script when a script selects at least n records.
ON or OFF turns this display on or off. Turning feedback ON sets n to 1. Setting feedback to zero is equivalent to turning it OFF.
SET FEEDBACK OFF also turns off the statement confirmation messages such as 'Table created' and 'PL/SQL procedure successfully completed' that are displayed after successful SQL or PL/SQL statements.
Add a RAISE statement in the error handler to re-raise the exception so that any outer handler can deal with it:
declare
testex exception;
begin
if 1=1 then
raise testex;
end if;
exception when testex then
dbms_output.put_line('Error msg');
RAISE; -- re-raise the exception to an outer handler
end;
Best of luck.
You have coded it explicitly to complete successfully in the event of a testex exception. The code says: if this happens, then print a message and end. If you want it to actually fail then it needs to raise an exception.
I would just use something like this:
begin
if 1=1 then
raise_application_error(-20000, 'Bananas are not available on a Tuesday');
end if;
end;
/
begin
*
ERROR at line 1:
ORA-20000: Bananas are not available on a Tuesday
ORA-06512: at line 3
This will cause an actual error, rather than just printing out a message that happens to talk about an error, allowing you to control deployments and build scripts etc.
It will also roll back any uncommitted transactions within the block, which your current approach does not, and it will show the actual line number.
I found this article on the Oracle site: Managing Exceptional Behavior, Part 1.
In it, someone had defined an error package, with procedures to raise, handle, report and go, report and stop and log. A breakdown of this package can be found here: errpkg.
The part I'm interested in is here:
IF l_errcode BETWEEN -20999 AND -20000 THEN
raise_application_error (l_errcode, l_errmsg);
/* Use positive error numbers -- lots to choose from! */
ELSIF l_errcode > 0 AND l_errcode NOT IN (1, 100) THEN
raise_application_error (-20000, l_errcode || '-' || l_errmsg);
/* Can't EXCEPTION_INIT -1403 */
ELSIF l_errcode IN (100, -1403) THEN
RAISE NO_DATA_FOUND;
/* Re-raise any other exception. */
ELSIF l_errcode != 0 THEN
EXECUTE IMMEDIATE
'DECLARE myexc EXCEPTION; ' ||
' PRAGMA EXCEPTION_INIT (myexc, ' || TO_CHAR (err_in) || ');' ||
'BEGIN RAISE myexc; END;';
END IF;
That snippet has been pasted in exactly as it appears in the article. I believe that the first if statement is covering all user errors, as -209999 to -20000 is reserved for user created exceptions.
In the first elsif statement (errcode > 0 and not in (1,00)), the code, according to the article, is doing the following:
I also handle positive numbers for application-specific error
numbers. By handling positive error message numbers, I am not
constrained to error numbers between -20,999 and -20,000, some of
which Oracle also uses (although I stay away from 1 and 100, the only
two positive error numbers that Oracle does use).
So to me that sounds like he is happy with declaring errors in his code like 150, 160, 170 etc, provided they don't occur between 1 and 100.
However, what are the next two parts of the procedure doing? What is the errcode in (100, -1403) for? I can see it's to do with error code 01403 (no data found), but I don't understand its position in the brackets (100, -1403). And what is the final elsif that does an execute immediate for? I'm struggling to see what its purpose is.
Oracle can use both -1403 and +100 for 'no data found', as mentioned in the exception_int documentation. It's also mentioned in the precompiler manuals; +100 is used in ANSI mode. So the IN (100, -1403) is just covering both possible values for the error raised when no data is found.
The final part seems to be declaring and immediately raising a named exception. You can only use raise application error with the -20000 range of error codes, so this is allowing the built-in exceptions to be raised without having to interpret or declare pragmas for all possible errors. no_data_found is treated explicitly because it has two error codes, and so a caller gets a consistent error whichever one actually occurred, presumably.
I'm struggling to see why you'd use the error package for this though - the article seem to be advocating catching with when others and then using this mechanism to raise a new exception with the same code; which would lose the stack trace. I don't quite get why that would be better than just letting the original exception bubble up naturally.
l_errcode IN (100, -1403) is an abbreviated form of l_errcode = 100 OR l_errcode = -1403. It can be in any order, l_errcode IN (-1403, 100) has exactly the same results. Both 100 and -1403 are errorcodes for no data found.
The final elsif with the execute immediate raises an exception with a user defined error message. Such associate a user defined exceptions with the error code EXCEPTION_INIT is needed: https://docs.oracle.com/cd/E11882_01/appdev.112/e25519/exceptioninit_pragma.htm
Hello i've this function to update my Access DB using TUniQuery :
var
Res:Boolean;
begin
Res:=false;
try
with MyQuery do
begin
Active := false;
SQL.Clear;
SQL.Add('Update MYTABLE');
SQL.Add('set username='+QuotedStr(NewUserName));
SQL.Add(',password='+QuotedStr(NewPassword));
SQL.Add('where username='+QuotedStr(ACurrentUserName));
ExecSQL;
Res:=true;
end;
except
Res:=False;
end ;
Result:=Res;
end;
Is the use of Try ... except enough to KNOW when " ExecSQL " succeeds or fails ?
or is there any other better approach ?
thank you
You may want to consider your update succeeded if no exception is raised. It means the database is responsive and parsed and executed your statement without syntax errors.
In a statement like shown, you may also want to be sure that exactly one row was updated, because I assume that's your intention.
To check that, you can resort to the result of the ExecSQL method, which returns the number of rows affected by the execution of the statement. So, you may change your code to this:
begin
with MyQuery do
begin
Active := false;
SQL.Clear;
SQL.Add('Update MYTABLE');
SQL.Add('set username='+QuotedStr(NewUserName));
SQL.Add(',password='+QuotedStr(NewPassword));
SQL.Add('where username='+QuotedStr(ACurrentUserName));
Result := ExecSQL = 1; //exactly 1 row updated
end;
end;
I also changed the unconditional exception handler, since it may not be the proper site to eat any exception, and also removed the local variable to store the Result, since that really is not necessary.
After reading your added text and re-thinking your question:
Is the use of Try ... except enough to KNOW when " ExecSQL " succeeds or fails ?
You really have to change your mind about exception handling and returning a boolean from this routine. Exceptions were introduced as a whole new concept on how to address exceptional and error situations on your programs, but you're killing this whole new (and IMHO better) approach and resorting to the old way to return a value indicating success or failure.
Particularly a try/exception block that eats any exception is a bad practice, since you will be killing exceptions that may be raised for too many reasons: out of memory, network problems like connection lost to database, etc.
You have to re-think your approach and handle these exceptional or error conditions at the appropriate level on your application.
My advise is:
change this from a function to a procedure, the new contract is: it returns only if succeded, otherwise an exception is raised.
if an exception occurs let it fly out of the routine and handle that situation elsewhere
raise your own exception in case no exactly 1 row is updated
change the query to use parameters (avoiding sql injection)
The routine may look like this:
procedure TMySecurityManager.ChangeUserNameAndPassword();
begin
MyQuery.SQL.Text := 'Update MYTABLE'
+ ' set username = :NewUserName'
+ ' , password = :NewPassword'
+ ' where username = :username';
MyQuery.Params.ParamByName('NewUserName').AsString := NewUserName;
MyQuery.Params.ParamByName('NewPassword').AsString := NewPassword;
MyQuery.Params.ParamByName('username').AsString := ACurrentUserName;
if MyQuery.ExecSQL <> 1 then
raise EUpdateFailed.Create('A internal error occurred while updating the username and password');
//EUpdateFailed is a hypotetical exception class you defined.
end;
I have a series of update statements that I need to use in my Oracle package. It's rare but there may be an occasional and unavoidable user error that would result in one of the update statements throwing a "Single row sub-query returns one or more rows" Error.
I've been looking into exception handling for oracle PL/SQl and I'm a bit stuck on how and what to use to catch this exception so the package doesn't crash.
I know of the pre-built "Too Many Rows" exception clause that exists but everything I read seems to say it is used for improper insert statements.
Can I use this as my exception? Or do I need to build my own exception clause. I've never built one myself before and have only a rough idea on where to put everything needed for it.
The following code is basically how the updates are set up in this particular procedure
but for the sake of brevity I'm only using a bare bones example of how it looks.
INSERT INTO TempTable... --(Initial insert statement)
UPDATE TempTable t SET t.Row_one = (SELECT (Statement_One))
WHERE T.Row_One is NULL
UPDATE TempTable t SET t.Row_one = (SELECT (Statement_Two))
WHERE T.Row_One is NULL
UPDATE TempTable t SET t.Row_one = (SELECT (Statement_Three))
WHERE T.Row_One is NULL
-- Does the exception clause start here?
EXCEPTION
WHEN TOO_MANY_ROWS THEN
(What do I tell the Procedure to do here, what am I able to tell it to do?)
--end of updates that need the exception handling
-- more insert statements into other tables based on data from the preceding Temp Table
END;
Will this work or do I need to build a custom exception?
Thanks in advance.
First, the TOO_MANY_ROWS exception will not catch the case where your select statements return multiple rows. The TOO_MANY_ROWS exception is for ORA-01422 when you issue a SELECT .. INTO statement that returns more than one row. The exception you'll encounter in your case is ORA-01427, Single row subquery returns more than one row.
If you want to handle this specific error in your procedure, use the EXCEPTION_INIT pragma to associate an exception name with the error:
too_many_values EXCEPTION;
PRAGMA EXCEPTION_INIT(too_many_values, -1427);
Then you can reference this name in your exception handler:
EXCEPTION
WHEN TOO_MANY_VALUES THEN
{perform your handler here}
What you put in the handler depends on what your procedure does. Many times you'll want to return some sort of error code/message to your caller:
PROCEDURE my_proc(p_one VARCHAR2, p_err OUT VARCHAR2) IS
too_many_values EXCEPTION;
PRAGMA EXCEPTION_INIT(too_many_values, -1427);
BEGIN
...
EXCEPTION
WHEN TOO_MANY_VALUES THEN
p_err := 'More than one value available to assign in the update';
RAISE; -- re-raise the exception for the caller
WHEN OTHERS THEN
p_err := SQLERRM; -- return the oracle message for the unexpected error
RAISE;
END;
Another approach is to skip the specific exception handlers and return generic oracle messages in the WHEN OTHERS handler:
EXCEPTION
WHEN OTHERS THEN
p_err := SQLERRM;
END;
The advantage with the first approach is you can customize your messages to be more end-user friendly when the output from the process is fed directly back to the user. The advantage with the latter approach is there is less coding involved. Error handling is an important and often skimped on aspect of any application.
The documentation from Oracle is here.
EDIT:
If this is a package, and you want to avoid passing a long chain of error variables through a series of procedure calls, you could declare an error variable with package scope, set it when the error is encountered, and RAISE the error again.
PACKAGE BODY my_pkg is
g_err VARCHAR2(256);
PROCEDURE procx(... , p_err OUT VARCHAR2) IS...
...
proc_y(p1);
EXCEPTION
WHEN OTHERS THEN
p_err := NVL(g_err, SQLERRM);
END;
PROCEDURE proc_y(p1 VARCHAR2) IS
...
proc_z(p2);
END;
PROCEDURE proc_z(p2 VARCHAR2) IS
too_many_values EXCEPTION;
PRAGMA EXCEPTION_INIT(too_many_values, -1427);
BEGIN
....
EXCEPTION
WHEN TOO_MANY_VALUES THEN
g_err := 'More than one value available to assign in the update';
RAISE; -- re-raise the exception for the caller
END;
When the exception is raised in proc_z, it is handled and then raised again. It propagates back through proc_y (no handler there) and then gets returned to the user in proc_x. Errors not set in the global g_err get the generic Oracle error message. This avoids having to pass the initial error parameter throughout the package.