exception in stored procedure - oracle

I have following procedure:
procedure mayFailProc() as
begin
insert into t1 (id, val) values (1, '123');
insert into t1 (id, val) values (2, '123');
insert into t1 (id, val) values (3, '123'); //fails, i.e. due to pk uniqueness error
end;
this exception thrown in mayFailProc is a normal thing and it is handled by its caller. So the transaction is not rolled back and execution continues as if there was no exception in mayFailProc. I wonder what will happen to first two successfully executed inserts? Will they be retained or not?

See here for Oracle's explanation. You can jump to the How Oracle Does Implicit Rollbacks section to start.
Before executing an INSERT, UPDATE, or DELETE statement, Oracle marks
an implicit savepoint (unavailable to you). If the statement fails,
Oracle rolls back to the savepoint. Usually, just the failed SQL
statement is rolled back, not the whole transaction. If the statement
raises an unhandled exception, the host environment determines what is
rolled back.
More:
You should explicitly commit or roll back every transaction. Whether
you issue the commit or rollback in your PL/SQL program or from a
client program depends on the application logic. If you do not commit
or roll back a transaction explicitly, the client environment
determines its final state.
For example, in the SQL*Plus environment, if your PL/SQL block does
not include a COMMIT or ROLLBACK statement, the final state of your
transaction depends on what you do after running the block. If you
execute a data definition, data control, or COMMIT statement or if you
issue the EXIT, DISCONNECT, or QUIT command, Oracle commits the
transaction. If you execute a ROLLBACK statement or abort the SQL*Plus
session, Oracle rolls back the transaction.

Just done a quick test... the rows in my test were not retained when the third insert caused a "unique constraint ... violated" error

Related

What is meant by PRAGMA AUTONOMOUS_TRANSACTION?

I need a clarity about pragma autonomous transaction. I have used a overlap select query in trigger and insert query in procedure. If I import two records in single file, the 2nd record is same as 1st record so the second record should be shown as overlap error. Now the select query in trigger executes but the error is not thrown by using the pragma autonomous transaction.
AUTONMOUS_TRANSACTION is a nested transaction. It executes DML independently of the calling transaction. So a query issued in an autonomous transaction won't see any uncommitted changes in the outer transaction. This is why you don't see your error message: the invalid state only exists in the uncommitted changes of the transaction.
Obviously you are using AUTONMOUS_TRANSACTION to avoid a mutating table error. However, a better solution would be to use a COMPOUND DML trigger: use a FOR EACH ROW to store the changes in an array, then verify them for no overlaps in the AFTER statement stage. Find out more.

Why is it not allowed to use a ROLLBACK statement in a PL/SQL trigger, but RAISE_APPLICATION_ERROR is?

Correct me if I'm wrong, but I was under the impression that a call to RAISE_APPLICATION_ERROR() forces a ROLLBACK. How is it possible that a call to RAISE_APPLICATION_ERROR() is allowed in PL/SQL triggers when ROLLBACK statements and/or methods that execute ROLLBACK statements are not?
I have the feeling I am missing a crucial point here :)
Thanks in advance!
Consider yourself corrected. Sort of. Raising (or encountering) an exception doesn't cause a rollback of the curent transaction. From the documentation:
In most cases, if a trigger runs a statement that raises an exception, and the exception is not handled by an exception handler, then the database rolls back the effects of both the trigger and its triggering statement.
Note that it's the statement, not the transaction; but "roles back the effect of" is a little confusing I suppose...
There is an implicit savepoint around every statement, and the trigger exception rolls back to that savepoint (except for after triggers etc. as noted in the docs). From Tom Kyte's Expert Oracle Database Architecture:
Oracle achieves this statement-level atomicity by silently wrapping a SAVEPOINT aroind each of our calls to the database.
Inside a trigger a RAISE_APPLICATION_ERROR does not perform a ROLLBACK, it aborts the current operation, i.e. a single UPDATE/INSERT/DELETE. Everything inside a trigger belongs to such an operation (thats' also the reason for famous error "ORA-04091 - Table is mutating, trigger/function may not see it").
A Rollback reverts all changes within current transaction (or up to given Savepoint), that's different.

BEGIN - END block atomic transactions in PL/SQL

This information should be easy to find, but I haven't had any luck.
When I have a BEGIN - END block in a PL/SQL, does it behave as an atomic transaction, that will try to commit on hitting the END block and if anything goes wrong rolls back the changes?
If not, how do I make sure that the code inside the BEGIN - END block behaves like an atomic transaction and how does the block behave "by default"?
EDIT: I am running from a stored procedure and I am using an implicit block, I think.
Firstly, BEGIN..END are merely syntactic elements, and have nothing to do with transactions.
Secondly, in Oracle all individual DML statements are atomic (i.e. they either succeed in full, or rollback any intermediate changes on the first failure) (unless you use the EXCEPTIONS INTO option, which I won't go into here).
If you wish a group of statements to be treated as a single atomic transaction, you'd do something like this:
BEGIN
SAVEPOINT start_tran;
INSERT INTO .... ; -- first DML
UPDATE .... ; -- second DML
BEGIN ... END; -- some other work
UPDATE .... ; -- final DML
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO start_tran;
RAISE;
END;
That way, any exception will cause the statements in this block to be rolled back, but any statements that were run prior to this block will not be rolled back.
Note that I don't include a COMMIT - usually I prefer the calling process to issue the commit.
It is true that a BEGIN..END block with no exception handler will automatically handle this for you:
BEGIN
INSERT INTO .... ; -- first DML
UPDATE .... ; -- second DML
BEGIN ... END; -- some other work
UPDATE .... ; -- final DML
END;
If an exception is raised, all the inserts and updates will be rolled back; but as soon as you want to add an exception handler, it won't rollback. So I prefer the explicit method using savepoints.
BEGIN-END blocks are the building blocks of PL/SQL, and each PL/SQL unit is contained within at least one such block. Nesting BEGIN-END blocks within PL/SQL blocks is usually done to trap certain exceptions and handle that special exception and then raise unrelated exceptions. Nevertheless, in PL/SQL you (the client) must always issue a commit or rollback for the transaction.
If you wish to have atomic transactions within a PL/SQL containing transaction, you need to declare a PRAGMA AUTONOMOUS_TRANSACTION in the declaration block. This will ensure that any DML within that block can be committed or rolledback independently of the containing transaction.
However, you cannot declare this pragma for nested blocks. You can only declare this for:
Top-level (not nested) anonymous PL/SQL blocks
List item
Local, standalone, and packaged functions and procedures
Methods of a SQL object type
Database triggers
Reference: Oracle
You don't mention if this is an anonymous PL/SQL block or a declarative one ie. Package, Procedure or Function.
However, in PL/SQL a COMMIT must be explicitly made to save your transaction(s) to the database. The COMMIT actually saves all unsaved transactions to the database from your current user's session.
If an error occurs the transaction implicitly does a ROLLBACK.
This is the default behaviour for PL/SQL.
The default behavior of Commit PL/SQL block:
You should explicitly commit or roll back every transaction. Whether you issue the commit or rollback in your PL/SQL program or from a client program depends on the application logic. If you do not commit or roll back a transaction explicitly, the client environment determines its final state.
For example, in the SQLPlus environment, if your PL/SQL block does
not include a COMMIT or ROLLBACK statement, the final state of your
transaction depends on what you do after running the block. If you
execute a data definition, data control, or COMMIT statement or if you
issue the EXIT, DISCONNECT, or QUIT command, Oracle commits the
transaction. If you execute a ROLLBACK statement or abort the SQLPlus
session, Oracle rolls back the transaction.
https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/sqloperations.htm#i7105

Populating a database in PostgreSQL

The following link on the PostgreSQL documentation manual http://www.postgresql.org/docs/8.3/interactive/populate.html says that to disable autocommit in postgreSQL you can simply place all insert statements within BEGIN; and COMMIT;
However I have difficulty in capturing any exceptions that may happen between the BEGIN; COMMIT; and if an error occurs (like trying to insert a duplicate PK) I have no way to explicitly call the ROLLBACK or COMMIT commands. Although all insert statements are automatically rolled back, PostgreSQL still expects an explicit call to either the COMMIT or ROLLBACK commands before it can consider the transaction to be terminated. Otherwise, the script has to wait for the transaction to time out and any statements executed thereafter will raise an error.
In a stored procedure you can use the EXCEPTION clause to do this but the same does not apply in my circumstance of performing bulk inserts. I have tried it and the exception block did not work for me because the next statement/s executed after the error takes place fails to execute with the error:
ERROR: current transaction is aborted, commands ignored until end of transaction block
The transaction remains open as it has not been explicitly finalised with a call to COMMIT or ROLLBACK;
Here is a sample of the code I used to test this:
BEGIN;
SET search_path TO testing;
INSERT INTO friends (id, name) VALUES (1, 'asd');
INSERT INTO friends (id, name) VALUES (2, 'abcd');
INSERT INTO friends (id, nsame) VALUES (2, 'abcd'); /*note the deliberate mistake in attribute name and also the deliberately repeated pk value number 2*/
EXCEPTION /* this part does not work for me */
WHEN OTHERS THEN
ROLLBACK;
COMMIT;
When using such technique do I really have to guarantee that all statements will succeed? Why is this so? Isn't there a way to trap errors and explicitly call a rollback?
Thank you
if you do it between begin and commit then everything is automatically rolled back in case of an exception.
Excerpt from the url you posted:
"An additional benefit of doing all insertions in one transaction is that if the insertion of one row were to fail then the insertion of all rows inserted up to that point would be rolled back, so you won't be stuck with partially loaded data."
When I initialize databases, i.e. create a series of tables/views/functions/triggers/etc. and/or loading in the initial data, I always use psql and it's Variables to control the flow. I always add:
\set ON_ERROR_STOP
to the top of my scripts, so whenever I hit any exception, psql will abort. It looks like this might help in your case too.
And in cases when I need to do some exception handling, I use anonymous code blocks like this:
DO $$DECLARE _rec record;
BEGIN
FOR _rec IN SELECT * FROM schema WHERE schema_name != 'master' LOOP
EXECUTE 'DROP SCHEMA '||_rec.schema_name||' CASCADE';
END LOOP;
EXCEPTION WHEN others THEN
NULL;
END;$$;
DROP SCHEMA master CASCADE;

Scope of Oracle transactions when used from ADO.NET and involving triggers?

I am told by someone that when calling Oracle from ADO.net, when calling multiple inserts in a loop, where each insert causes a trigger to fire that includes within it's PL-Sql a Commit statement, that it is impossible to stop that commit from actually commiting the transaction.
i.e., I want my ADO.Net code to begin a transaction before the loop starts, and, when the loop exits, only commit all the inserts if and only if every insert in the loop was successful. My source is telling me that the way Oracle works, if these triggers include COmmit statements, then this is impossible..
As this seems to be an very common requirement, and I know it is possible in SQL Server, this does not seem right to me.
Is this correct?
Your informant is wrong, if he is talking about Oracle database triggers:
1) You cannot put a COMMIT in an Oracle trigger that is not autonomous:
SQL> create trigger this_wont_work
2 after insert on emp
3 begin
4 commit;
5 end;
6 /
Trigger created.
SQL> insert into emp (empno) values (123)
2 /
insert into emp (empno) values (123)
*
ERROR at line 1:
ORA-04092: cannot COMMIT in a trigger
ORA-06512: at "TONY.THIS_WONT_WORK", line 2
ORA-04088: error during execution of trigger 'TONY.THIS_WONT_WORK'
2) If the trigger is autonomous (i.e. has PRAGMA AUTONOMOUS_TRANSACTION in its declaration section) then it can only commit any changes it (the trigger) makes.
There is no danger whatsoever of a trigger committing work you did outside of that trigger.
Note: the use of autonomous transactions in triggers is dangerous except for certain cases, because actions performed by the autonomous trigger will be committed even if the triggering statement is rolled back. This can easily lead to data corruption if mis-used.

Resources