Different default error handling in Oracle and PostgreSQL - oracle

I compare the default behavior of Oracle and PostgreSQL after encountering an error in a PL/SQL (PL/pgSQL) code. For this purpose, I wrote an analogous Oracle and PostgreSQL code shown below.
Oracle code (db<>fiddle):
CREATE TABLE table1 (col1 int);
CREATE PROCEDURE raise_error AS
BEGIN
INSERT INTO table1 VALUES (1/0);
END;
/
INSERT INTO table1 VALUES (1);
CALL raise_error();
COMMIT;
SELECT * FROM table1;
PostgreSQL code (db<>fiddle):
CREATE TABLE table1 (col1 int);
CREATE PROCEDURE raise_error() AS $$
BEGIN
INSERT INTO table1 VALUES (1/0);
END;
$$ LANGUAGE plpgsql;
BEGIN TRANSACTION; -- disable auto-commit
INSERT INTO table1 VALUES (1);
CALL raise_error();
COMMIT;
SELECT * FROM table1;
Note: In PostgreSQL I additionally run the BEGIN TRANSACTION statement to disable auto-commit, because Oracle doesn't have auto-commit, and I want both codes to be analogous.
The result of the SELECT * FROM table1 query is one row in Oracle, and no rows in PostgreSQL.
As you can see, the analogous code in Oracle and PostgreSQL gives different results. What is the reason of this difference in the default error handling?

Oracle and PostgreSQL indeed behave differently here.
Oracle has something that I would call “statement-level rollback”: if a statement running inside a transaction causes an error, only the effects of that statement are rolled back, and the transaction continues.
In PostgreSQL, any error inside a transaction aborts the whole transaction, so you can only roll back the transaction, and it has no effects at all. This is more in the spirit of “all or nothing”, but as far as I can see, the SQL standard is not specific about this, so both can behaviors can be argued.
You can, however, use standard conforming savepoints in PostgreSQL to “recover” from an error in a transaction:
START TRANSACTION;
INSERT INTO table1 VALUES (1);
/* set a savepoint we can revert to */
SAVEPOINT x;
CALL raise_error();
ROLLBACK TO SAVEPOINT x;
/* now the INSERT can be committed */
COMMIT;
But be warned that you don't use too many savepoints (not more than 64) per transaction, else performance may suffer.

In Oracle you are using two separate transactions, the first is successful but the second fails. In PostgreSQL, you are explicitly telling it to only use one transaction and handle the statements together.
In Oracle, if you use a PL/SQL anonymous block to group the statements into a single transaction:
BEGIN
INSERT INTO table1 VALUES (1);
raise_error();
END;
/
And, equivalently in PostgreSQL:
DO
$$
BEGIN
INSERT INTO table1 VALUES (1);
CALL raise_error();
END;
$$ LANGUAGE plpgsql;
Then there will be no rows in the table as the exception from the procedure will rollback the entire transaction.
Or, in Oracle, you could do:
INSERT INTO table1 VALUES (1);
DECLARE
divide_by_zero EXCEPTION;
PRAGMA EXCEPTION_INIT( divide_by_zero, -1476 );
BEGIN
raise_error();
EXCEPTION
WHEN DIVIDE_BY_ZERO THEN
ROLLBACK;
END;
/
Which would have the same effect of rolling back both transactions to the last commit.
db<>fiddle Oracle PostgreSQL

Related

Сalling PL/SQL function from select statement does not work [duplicate]

Is it possible/make sense to have COMMIT statement in SQL functions?
Technically, the answer ist yes. You can do the following:
create or replace function committest return number as
begin
update my_table set col = 'x';
commit;
return 1;
end;
/
declare
number n;
begin
n := committest();
end;
/
However, you can't do the following:
select committest() from dual;
this would be a commit during a query and thus result in a
ORA-14552: Cannot Perform a DDL Commit or Rollback Inside a Query or DML
Yes, you can do that if you make the function an autonomous transaction. That way it will not be part of the current transaction anymore.
create or replace function doIt
as
pragma autonomous_transaction;
begin
... code ...
commit;
end;
/
More documentation
No, it's not possible, see the documentation:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5009.htm
Restrictions on User-Defined Functions
.... In addition, when a function is called from within a query or DML
statement, the function cannot: ....
Commit or roll back the current transaction, create a savepoint or roll back to a savepoint, or alter the session or the system. DDL
statements implicitly commit the current transaction, so a
user-defined function cannot execute any DDL statements.

Truncate table with a trigger

I want to create a trigger that after insert on another table it truncates another and after that inserts the data from the first table into the truncated one. I have managed the insert part but i don't know how to incorporate the truncate command into the same trigger.
CREATE OR REPLACE TRIGGER TEST_TRIGGER AFTER INSERT ON TEST
FOR EACH ROW
BEGIN
INSERT INTO TEST2
(col1, col2, col3)
VALUES
(:NEW.col1, :NEW.col2, :NEW.col3);
END;
Below is the snipped using which you can achieve your goal
create table table1 (id int, age number,name varchar2(100));
create table table2 (id int, age number,name varchar2(100));
create or replace trigger sandeeptest after insert on table1 for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
begin
execute immediate 'truncate table table2';
insert into table2(id,age,name) values (:new.id,:new.age,:new.name);
commit;
end;
insert into table1 (id,age,name) values (1,21,'A');
commit; -- after commit data is persisted in both the tables
insert into table1 (id,age,name) values (2,21,'B');
rollback;-- even after rollback data is presisted in table2 because we created an autonomous trigger
select * from table1;
select * from table2;
you can create procedure on target schema which you want to truncate table on it.
the proc like this:
CREATE OR REPLACE procedure target_schema.pr_truncate_table(p_table_name varchar2) is
begin
execute immediate 'truncate table ' || p_table_name;
end;
/
then you can use it on trigger.
Surely using transactional statement inside trigger is not a good idea. But i guess you are in some practical and more significant use of it.
If you use DELETE inside trigger instead of TRUNCATE, will have
slow performance in case of large table
Most important, you will have to COMMIT which is again not allowed unless you use PRAGMA AUTONOMOUS_TRANSACTION
If you create two procedure , one to INSERT and another to TRUNCATE,will have:
since procedure is having commit(implicit or external), trigger needs to be defined PRAGMA AUTONOMOUS_TRANSACTION (which is again needed)
BUT think more than twice before making it autonomous. It is double sided sword which may hurt your business logic in case of rollback because you can not rollback DDL statement.
I would suggest to use dbms_job and submit procedure to it.
CREATE OR REPLACE TRIGGER TEST_TRIGGER AFTER INSERT ON TEST
FOR EACH ROW
BEGIN
DELETE FROM TEST2 WHERE true ;
INSERT INTO TEST2
(col1, col2, col3)
VALUES
(:NEW.col1, :NEW.col2, :NEW.col3);
END;

Oracle PLSQL - Commit only database link

Is there a way to commit only the data inserted/updated on a table through a database link and not the data of the current session? Or are they considered one and the same?
For example:
INSERT INTO main_database.main_table(value1, value2)
VALUES (1 , 2)
INSERT INTO database.table#database_link(value3, value4)
VALUES (3 , 4)
And do a commit for only the database link table?
Background on why I would want to do this:
The main database is for (multiple) records while the database link is for (monetary) transactions (processed on a separate server). I want to update the records first to check to see if any of the constraints fail, but not commit the data until the transaction is complete. If the transaction fails, I want to rollback the records to save me the effort of deleting/undoing inserts/updates which could get messy.
I am assuming there is not but I am hoping that there is a way. Thanks in advance.
create or replace procedure proc_1 ( i IN number )
as
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO test_table#remote_sid (id, description)
VALUES (i, 'Description for ' || i);
COMMIT;
END;
/
create or replace procedure proc_base ( i IN number )
as
begin
insert into local_tab (id) values (i);
proc_1( i );
rollback;
end;
/
No.
If the insert is in a pl/SQL then you can run the first insert in an autonomous transaction, but in the absence of other functionality that's little different than insert....; commit; insert...

pl sql error when working with trigger

I get an error when working with the following trigger:
create or replace trigger t1
after insert or update
on student_tbl
declare
pragma autonomous_transaction;
begin
if inserting then
insert into stud_fees_details(stud_id,fees_balance,total_fees)
select stud_id,course_fees,course_fees from student_tbl s,courses_tbl c where s.stud_standard_id=c.course_id;
elsif updating('stud_standard_id') then
insert into stud_fees_details(stud_id,fees_balance,total_fees)
select stud_id,course_fees,course_fees from student_tbl s,courses_tbl c where s.stud_standard_id=c.course_id;
end if;
end;
error is
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at "SYSTEM.T1", line 15
ORA-04088: error during execution of trigger 'SYSTEM.T1'
Database Error Messages
ORA-06519: active autonomous transaction detected and rolled back
Cause: Before returning from an autonomous PL/SQL block, all autonomous transactions started within the block must be completed (either committed or rolled back). If not, the active autonomous transaction is implicitly rolled back and this error is raised.
Action: Ensure that before returning from an autonomous PL/SQL block, any active autonomous transactions are explicitly committed or rolled back.
Example from Database PL/SQL Language Reference
-- Autonomous trigger on emp table:
CREATE OR REPLACE TRIGGER log_sal
BEFORE UPDATE OF salary ON emp FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO log (
log_id,
up_date,
new_sal,
old_sal
)
VALUES (
:old.employee_id,
SYSDATE,
:new.salary,
:old.salary
);
COMMIT;
END;
/
But #a_horse_with_no_name already stated that an autonomous transaction maybe is is not appropriate here.
After removing the autonomous transaction pragma you maybe will run into the problem that #GordonLinoff adresses with his post.
If a trigger doesn't use :new or :old, then it is suspicious. Your trigger is using the same table being modified in queries.
You probably intend:
create or replace trigger t1 after insert or update on student_tbl
declare
pragma autonomous_transaction;
begin
if inserting then
insert into stud_fees_details(stud_id, fees_balance, total_fees)
select stud_id, course_fees, course_fees
from courses_tbl c
where c.course_id = :new.stud_standard_id;
elsif updating('stud_standard_id') then
insert into stud_fees_details(stud_id, fees_balance, total_fees)
select stud_id, course_fees, course_fees
from courses_tbl c
where c.course_id = :new.stud_standard_id;
end if;
commit;
end;
Notes:
The select statements should have table aliases, to distinguish the columns that come from :new and from courses_tbl.
The two clauses to the if look the same to me, so I don't understand the logic.
The join conditions between something called course_id and somethign called stud_standard_id looks suspicious. I would advise you to name foreign keys after the primary key they refer to.

Why does this Oracle PLSQL function need to be recompiled when a table is updated?

I have written this function for an Oracle db for a class project:
create or replace Function loginUser ( name_in IN varchar2, pass_in IN varchar2 )
Return Number
IS
cursor c1 is
SELECT u_id
FROM userinfo
WHERE username = name_in AND pass = pass_in AND lockedout='N';
user_id_return number;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
open c1;
fetch c1 into user_id_return;
if c1%notfound then
user_id_return := 0;
INSERT INTO LoginAttempt(username, whenattempted, attempt_status) VALUES (name_in, SYSDATE, 'N');
commit;
ELSE
INSERT INTO LoginAttempt(username, whenattempted, attempt_status) VALUES (name_in, SYSDATE, 'Y');
commit;
END IF;
close c1;
Return user_id_return;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END;
It works great, I get an insert into a table called LoginAttempt when you call
SELECT loginUser('name','pass') FROM DUAL;
The issue, however, is that new records and updates to userinfo are not reflected in the SELECT statement at the top of the function.
I have to recompile the function each time I update the userinfo table.
Why is this? Is this how functions with SELECT statements work? Is the table that is being SELECTED from compiled when the function is compiled?
Is this related to the PRAGME AUTONOMOUS_TRANSACTION bit?
The schema for the table can be found on github (https://github.com/tmsimont/cs3810schema/blob/master/export.sql)
Because your function executes its DML in an autonomous transaction (i.e. a separate session to the calling one), it cannot see data that has not been committed by the calling session. Until your code commits, the changes it makes are not visible to any other session, including the one used by your function.
This has nothing to do with needing to compile the function. The reason compilation caused it to suddenly see the data is because DDL always issues a commit, thus having the side effect of committing the data you had inserted.

Resources