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.
Related
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
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...
I am trying to declare a function in pl/sql and return a variable content from it.
CREATE OR REPLACE FUNCTION executeTransaction
RETURN VARCHAR2 as
pragma autonomous_transaction;
myLog VARCHAR(1200) :='myLog';
BEGIN
savepoint my_savepoint;
begin
insert into datalogger(MPRN_FK,K0213,K0214) values(465,'2142342','423423');
myLog := 'transaction completed with rows insert ' || SQL%ROWCOUNT;
rollback to my_savepoint;
end;
insert into myTbl(col) values(myLog);
RETURN myLog;
END executeTransaction;
The function compiles but when I execute it like
select executeTransaction from dual;
I am getting
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at "ECO_AFMS_GAS_CUST.GET_ALLITEMS", line 14
06519. 00000 - "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.
and the variable content is not returned? I know that I must either commit or rollback but in this case I rollback so it should work?
Since you are using pragma autonomous_transaction, you need to add COMMIT at the end of your function. As the error states
If not, the active autonomous transaction is implicitly rolled back and this error is raised.
CREATE OR REPLACE FUNCTION executeTransaction
RETURN VARCHAR2 as
pragma autonomous_transaction;
myLog VARCHAR(1200) :='myLog';
BEGIN
savepoint my_savepoint;
begin
insert into datalogger(MPRN_FK,K0213,K0214) values(465,'2142342','423423');
myLog := 'transaction completed with rows insert ' || SQL%ROWCOUNT;
rollback to my_savepoint;
end;
insert into myTbl(col) values(myLog);
commit; --add commit
RETURN myLog;
END executeTransaction;
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.
I'm working on the following PL/SQL trigger:
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
FOR EACH ROW
DECLARE
BEGIN
IF :OLD.type = 'PRESIDENT' THEN
INSERT INTO trigger_log VALUES (sysdate, 'Nope.', 'No change.');
RAISE_APPLICATION_ERROR(-20111, 'Can not change!');
END IF;
END;
Here, I want to cancel the UPDATE command on the worker table, when a PRESIDENT's payment is about to get changed. At the same time, I wish to log this command into table called trigger_log. The problem is, when I RAISE_APPLICATION_ERROR the UPDATE got cancelled, but the logging (INSERT INTO trigger_log) aswell. How can I RAISE_APPLICATION_ERROR or throw an EXCEPTION, but still have all commands to be run inside the TRIGGER?
You have to commit your INSERT-Statement before raising the error.
Maybe think about AUTONOMOUS_TRANSACTION
EDIT
As others have stated already, you should not and cannot commit inside a trigger (exception autonomous transaction). So think about using the solution of Tom Thomas or calling a logging-procedure/-package.
You can call a stored procedure from your trigger. That stored procedure should be declared as PRAGMA AUTONOMOUS_TRANSACTION. Try like this,
CREATE OR REPLACE
PROCEDURE log_error_p
AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO trigger_log VALUES (SYSDATE, 'Nope.', 'No change.');
COMMIT;
END;
--
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
FOR EACH ROW
DECLARE
BEGIN
IF :OLD.TYPE = 'PRESIDENT' THEN
log_error_p();
RAISE_APPLICATION_ERROR(-20111, 'Can not change!');
END IF;
END;
/
First of all, never use a commit or a rollback inside the trigger. It's a coding standard. As for your question i think this is a better way than raise_application_error
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
REFERENCING OLD as o AND NEW as n
FOR EACH ROW
DECLARE
InsertException EXCEPTION;
BEGIN
IF o.TYPE = 'PRESIDENT' THEN
RAISE InsertException;
END IF;
EXCEPTION
WHEN InsertException THEN
INSERT INTO trigger_log VALUES (SYSDATE, 'Nope.', 'No change.');
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/