Trigger to RAISE_APPLICATION_ERROR but execute inner commands - oracle

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;
/

Related

how to insert the result of dbms_output.put_line to a table for error catch?

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.

How to commit individual transaction in Oracle PLSQL

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

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.

Trigger that selects from table that is being deleted

This trigger ends up raising an error regardless of the if statement values. I'm basically selecting from the same table the delete is occurring on and it's not liking it.
CREATE OR REPLACE TRIGGER delete_schedules
AFTER DELETE
ON SCHEDULES
FOR EACH ROW
DECLARE
lParentCond schedules.cond_code%type;
lParentActive schedules.active_flag%type;
lError exception;
BEGIN
if :OLD.thread is not null then
select cond_code, active_flag
into lParentCond, lParentActive
from schedules where schedule_seq = :old.thread;
if lParentCond = 'OK' and lParentActive in ('*', 'F') then
raise lError;
end if;
end if;
EXCEPTION
when lError then
raise;
WHEN OTHERS THEN RAISE;
END delete_schedules;
Any ideas of a workaround?
You're probably getting the dreaded 'MUTATING TABLE' error. Oracle doesn't allow us to fetch data from the table on which the trigger is defined in an AFTER trigger - but in this case you don't need to because the 'old' values are already available. Rewrite your trigger as:
CREATE OR REPLACE TRIGGER delete_schedules
AFTER DELETE
ON SCHEDULES
FOR EACH ROW
BEGIN
if :OLD.thread is not null AND
:OLD.COND_CODE = 'OK' and
:OLD.ACTIVE_FLAG in ('*', 'F')
then
RAISE_APPLICATION_ERROR(-20100, 'Invalid combination of COND_CODE and ACTIVE_FLAG');
end if;
END delete_schedules;
This assumes (based on the use of a singleton SELECT in the question) that there's only one row in SCHEDULES for the given THREAD value. If that's not the case there are other work-arounds, including using a COMPOUND TRIGGER.
Best of luck.

Trigger not working for stopping table insert

I am using the following code for stopping null value insert into table using trigger. But when I pass null value, the inserting is happening fine. Any idea what am I doing wrong here?
create table test
(col1 number,
col2 varchar2(40)
)
create or replace trigger test_trg
after insert on test
for each row
declare
excp exception;
pragma autonomous_transaction;
begin
if :new.col2 is null then
RAISE excp;
end if;
exception
when excp then
dbms_output.put_line('error');
rollback;
end;
(Please note, I do accept that using a not null or a check constraint on the col2 is a better solution. I just want to find out the reason behind the error in this seemingly correct code)
Don't rollback in trigger, just re-raise excpetion after logging it:
create or replace trigger test_trg
after insert on test
for each row
declare
excp exception;
pragma autonomous_transaction;
begin
if :new.col2 is null then
RAISE excp;
end if;
exception
when excp then
dbms_output.put_line('error');
raise; -- propagate error
end;
When you put "exception ... end;" block in code you say to PL/SQL that managing consequences of this error is on your responsibility. So, if you don't raise any error from a code which handles original error, for PL/SQL it means that all actions regarding this error already done in your code, all went OK and record must be inserted.
You can try it in this SQLFiddle.
you have to define the trigger as BEFORE INSERT to fire before the insert is executed, remove the pragma autonomouse_transaction and the rollback (they have no sense here, because you do not any DML), then reraise the exception in the exception handler

Resources