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;
Related
Exception
WHEN OTHERS THEN
--dbms_output.put_line('pl_update_sidm_user r: ERROR CODE:' || sqlcode || '~' ||
--sqlerrm || ' EMPL_NBR:' || r.EMPL_NBR);
insert into ERROR_MSG (ERROR_MSG_ID,ERROR_MSG) values (ERROR_MSG_ID_SEQ.NEXTVAL, 'pl_update_sidm_user_duty_role r2');
END;
I would like to put the error result to a table.
However, how can I do that?
Can I put the result of dbms_output to a table as a string?
If not, can I get the sqlcode,sqlerrm without using dbms_output?
Thank you !!
From the documentation,
A SQL statement cannot invoke SQLCODE or SQLERRM. To use their values
in a SQL statement, assign them to local variables first
Also,
Oracle recommends using DBMS_UTILITY.FORMAT_ERROR_STACK except when using the FORALL statement with its SAVE EXCEPTIONS clause
So, for SQLCODE or SQLERRM, you should assign them into variables and use them.
DECLARE
v_errcode NUMBER;
v_errmsg VARCHAR2(1000);
BEGIN
--some other statements that may raise exception.
EXCEPTION
WHEN OTHERS THEN
v_errcode := SQLCODE;
v_errmsg := SQLERRM;
insert into ERROR_TABLE (ERROR_MSG_ID,ERROR_MSG) --change your table name
values (ERROR_MSG_ID_SEQ.NEXTVAL,
v_errcode||':'||v_errmsg);
END;
/
Preferably use insert like this instead, as per Oracle's recommendation.
insert into ERROR_TABLE (ERROR_MSG_ID,ERROR_MSG) values (ERROR_MSG_ID_SEQ.NEXTVAL,
DBMS_UTILITY.FORMAT_ERROR_STACK);
Demo
Technically what others are suggesting is correct: the "insert" operation executed in the "exception when others" block will actually insert a new row in the log table.
the problem is that such insert statement will be part of the same transaction of the main procedure and, since you had an error while executing it, you are very likely to rollback that transaction, and this will rollback also the insert in your log table
I suppose the problem you are facing is not that you aren't successfully logging the error message: it is that you are rolling it back immediately afterwards, along with all the other writes you did in the same transaction.
Oracle gives you a way of executing code in a SEPARATE transaction, by using "autonomous transaction" procedures.
you need to create such a procedure:
create or replace procedure Write_Error_log(
arg_error_code number,
arg_error_msg varchar2,
arg_error_backtrace varchar2) is
PRAGMA AUTONOMOUS_TRANSACTION;
begin
INSERT INTO error_msg (
error_msg_id,
error_code,
error_msg,
error_stack)
VALUES (
error_msg_id_seq.NEXTVAL,
arg_error_code,
arg_error_msg,
arg_error_backtrace);
commit; -- you have to commit or rollback always, before exiting a
-- pragma autonomous_transaction procedure
end;
What this procedure does is to write a new record in the log table using a totally separate and independent transaction: the data will stay in the log table even if you execute a roll back in your calling procedure. You can also use such a procedure to create a generic log (not only errors).
All you have to do now is to call the procedure above whenever you need to log something, so your code becomes:
DECLARE
v_errcode NUMBER;
v_errmsg VARCHAR2(1000);
BEGIN
--some other statements that may raise exception.
EXCEPTION WHEN OTHERS THEN
Write_Error_log(SQLCODE, SQLERRM, dbms_utility.format_error_backtrace);
END;
/
P.S: there might be some typos in my code: I can't test it right now since I can't reach an oracle server in this moment.
I need to write a PL/SQL procedure, within this procedure I need to call another procedure within its own transaction bounds, and commit it regardless of failure or commit of main transaction. In other words I need something like REQUIRES NEW transaction propagation.
Something like:
procedure mainProcedure(arugements) is
begin
// some statements
nestedProcedure(someArguments);
// some other statements
end;
procedure nestedProcedure(arguments) is
begin
// start a new transaction
// some statements, lock some objects!
// commit the new transaction and release locked objects
end;
How can I achieve this?
Have a look at Autonomous transation. Also see demo
CREATE TABLE t (
test_value VARCHAR2(25));
CREATE OR REPLACE PROCEDURE child_block IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO t
(test_value)
VALUES
('Child block insert');
COMMIT;
END child_block;
/
CREATE OR REPLACE PROCEDURE parent_block IS
BEGIN
INSERT INTO t
(test_value)
VALUES
('Parent block insert');
child_block;
ROLLBACK;
END parent_block;
/
Execution:
-- empty the test table
TRUNCATE TABLE t;
-- run the parent procedure
exec parent_block;
-- check the results
SELECT * FROM t;
You may use pragma autonomous_transaction. It does the same what you need. But don't forget that in the sub transaction you will not see any updates from above transactions.
procedure mainProcedure(arugements) is
begin
// some statements
nestedProcedure(someArguments);
// some other statements
end;
procedure nestedProcedure(arguments) is
pragma autonomous_transaction;
begin
// start a new transaction
// some statements, lock some objects!
// commit the new transaction and release locked objects
commit;
end;
Create a (Commit single transaction Procedure), you can create the procedure and use it as illustrated in the comments.
CREATE OR REPLACE PROCEDURE SWD_AUTON_DML (p_dmlstat VARCHAR2)
AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN -- Main transaction suspends here.
EXECUTE IMMEDIATE p_dmlstat; -- Autonomous transaction begins here.
COMMIT; -- Autonomous transaction ends here.
END; -- Main transaction resumes here.
--------------------- How to USE -------------------------------------
----------------------------------------------------------------------
---EXECUTE AUTON_DML(q'[UPDATE staging_pm_schedule_table SET NOTE = 'TEST_Variable']');
--OR
---EXECUTE AUTON_DML('UPDATE staging_pm_schedule_table SET NOTE = '''TEST_Variable'''');
for more information:
https://brainsdazed.blogspot.com/2018/09/oracle-procedure-to-commit-per-dml.html
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.
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;
/