why it is working without PRAGMA AUTONOMOUS_TRANSACTION - oracle

I have a misunderstanding regarding to
PRAGMA AUTONOMOUS_TRANSACTION
directive.
As far as I know, it is used in logging or auditing procedure, to run independently by the main program (autonomous, procedure, function or trigger).
I have an UPDATE on a table which generated DUP_VAL_ON_INDEX. In this exception, I call a logging procedure which logs the error into a table. In the logging procedure I didn't specified PRAGMA AUTONOMOUS_TRANSACTION directive but it still makes the insert in my logging table.
Here is my code:
create table TEST_PRAGMA
( COL_1 number primary key
, COL_2 number
);
--
insert into TEST_PRAGMA values (1, 200);
insert into TEST_PRAGMA values (2, 200);
--
create table T_LOG
( msg_num number primary key
, MSG_DATE timestamp(6)
, INFO_MSG varchar2(10)
, LONG_MSG varchar2(100)
);
--
create sequence SEQ_TEST start with 1 increment by 1 nocache nocycle;
Package:
create or replace package pkg_logging as
procedure PRC_LOG ( P_MSG_NUM number
, P_MSG_DATE timestamp
, P_INFO_MSG varchar2
, p_long_msg varcahr2);
end PKG_LOGGING;
--
create or replace package body pkg_logging as
procedure PRC_LOG ( P_MSG_NUM number
, P_MSG_DATE timestamp
, P_INFO_MSG varchar2
, P_LONG_MSG VARCHAR2)
as
begin
insert into T_LOG
( MSG_NUM
, MSG_DATE
, INFO_MSG
, LONG_MSG
)
values
( P_MSG_NUM
, P_MSG_DATE
, P_INFO_MSG
, P_LONG_MSG
);
commit;
EXCEPTION
when OTHERS then
rollback;
RAISE_APPLICATION_ERROR(-20000, 'other error has occured: ' || sqlcode || ' - ' || sqlerrm);
end PRC_LOG;
end PKG_LOGGING;
--
set SERVEROUTPUT on;
begin
update TEST_PRAGMA set COL_1 = 1 where COL_2 = 200;
commit;
EXCEPTION
when DUP_VAL_ON_INDEX then
dbms_output.put_line ('DUP_VAL_ON_INDEX error has occured');
PKG_LOGGING.PRC_LOG(SEQ_TEST.NEXTVAL, systimestamp, 'error', 'test de logging');
rollback;
end;
Because I didn't specified the PRAGMA directive, I was expecting not to log the error even if the logic is correct.
Can anyone explain me why it is still logs my error and provide a sample where it does not log the code if I do not specify the PRAGMA AUTONOMOUS_TRANSACTION directive, please?
Thank you,

Can anyone explain me why it is still logs my error and provide a
sample where it does not log the code if I do not specify the PRAGMA
AUTONOMOUS_TRANSACTION directive, please?
The error is being Inserted in Log table since you are handling it as an Exception handling. You need to understand the behavior of AUTONOMOUS transaction as being an Independent piece of code which executes even if the main calling proc/pkg fails. It's not being handled as a part of Exception Handling. As shown in below demo you can see proc marked as AUTONOMOUS is called in BEGIN block directly rather than in Exception block to understand the behavior.
DECLARE
l_salary NUMBER;
--Private Proc marking as Autonomous transaction
procedure nested_block ‬
as
pragma AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE emp
SET salary=salary+15000
WHERE emp_no=1002;
COMMIT;
END;‭
--Main Block ‬
BEGIN
SELECT salary
INTO l_salary
FROM emp
WHERE emp_no=1001;
Dbms_output.put_line('Before Salary of 1001 is'||l_salary);
SELECT salary
INTO l_salary
FROM emp WHERE emp_no=1002;
Dbms_output.put_line('Before Salary of 1002 is '|| 1_salary);
UPDATE emp
SET
salary = salary + 5000
WHERE emp_no = 1001;
--Calling Autonomous transaction
nested_block;
--And rolling back previous updates.
ROLLBACK;
SELECT salary INTO
l_salary
FROM emp
WHERE emp_no = 1001;
dbms_output.put_line('After Salary of 1001 is'|| l_salary);
SELECT salary
INTO l_salary
FROM emp
WHERE emp_no = 1002;
dbms_output.put_line('After Salary of 1002 is ' || l_salary);
end;
Output:
The output will have the Update done in Autonomous transaction. Updates done in the main block will be rolledback but not the one which is done in private proc marked as Autonomous
Before Salary of 1001 is 15000
Before Salary of 1002 is 10000
After Salary of 1001 is 15000
After Salary of 1002 is 25000

PKG_LOGGING.PRC_LOG() has a commit statement so it will commit.
Suppose your code looked liked this:
set SERVEROUTPUT on;
begin
insert into TEST_PRAGMA values (3, 300);
PKG_LOGGING.PRC_LOG(SEQ_TEST.NEXTVAL, systimestamp, 'info', 'inserted a record');
update TEST_PRAGMA set COL_1 = 1 where COL_2 = 200;
commit;
EXCEPTION
when DUP_VAL_ON_INDEX then
dbms_output.put_line ('DUP_VAL_ON_INDEX error has occured');
PKG_LOGGING.PRC_LOG(SEQ_TEST.NEXTVAL, systimestamp, 'error', 'test de logging');
rollback;
end;
How many records would you have in TEST_PRAGMA? Three. Because the insert was committed when we called PKG_LOGGING.PRC_LOG(), and consequently the rollback in the exception handler had no effect. And that's why we should use PRAGMA AUTONOMOUS_TRANSACTION in audit and logging routines: so we can successfully persist our logging messages without affecting the broader transaction.
So you should add PRAGMA AUTONOMOUS_TRANSACTION to PKG_LOGGING.PRC_LOG().
Incidentally, I think you should be careful with an error handler like this in a logging package:
EXCEPTION
when OTHERS then
rollback;
RAISE_APPLICATION_ERROR(-20000, 'other error has occured: ' || sqlcode || ' - ' || sqlerrm);
end PRC_LOG;
In some situations we definitely would want to stop our process if we can't log vital information. But other times we want logging to fail gracefully. For instance I need the overnight batch run to abend if it can't record errors, because that log is my only way of knowing what - if anything - went wrong, and it's better for the whole thing not to run that for it to run incompletely and me not to know that some things failed. But if I'm just writing some trace messages in Test I might prefer the long running process to conclude without a complete set of trace rather than abend because the logging table has run out of space.
Also, using raise_application_error() is not necessary. Just issue raise; after the rollback and be done with it.

Related

PL/SQL exception handling - log errors

How do I record the oracle error in a pl/sql script? I have been to the oracle error handling documentation and I see the built in exceptions, but what if I do not know what the exception is? How can I log this in an exception block?
I want to do something like the below.
exception
when others then DBMS_OUTPUT.PUT_LINE(the error)
It's easier to create error logging database trigger with conditional logging, for example my actual error_logging trigger: https://github.com/xtender/xt_scripts/blob/master/error_logging/on_database.sql
create table ERROR_LOG
(
id NUMBER,
username VARCHAR2(30),
errcode INTEGER,
seq INTEGER,
tmstmp TIMESTAMP(6),
msg VARCHAR2(4000),
sql_text CLOB
)
/
create sequence err_seq
/
create or replace trigger trg_error_logging
after servererror
on database
disable
declare
v_id number := err_seq.nextval();
v_tmstmp timestamp:= systimestamp;
n int;
sql_text dbms_standard.ora_name_list_t;
v_sql_text clob;
begin
-- only if plsql_debug is set to TRUE:
for r in (select * from v$parameter p where p.name='plsql_debug' and upper(p.value)='TRUE') loop
v_sql_text:=null;
n := ora_sql_txt(sql_text);
for i in 1..n loop
v_sql_text := v_sql_text || sql_text(i);
end loop;
for i in 1.. ora_server_error_depth
loop
if i=1 then
insert into error_log(id,seq,tmstmp,username,errcode,msg,sql_text)
values( v_id, i, v_tmstmp, user, ora_server_error(i), ora_server_error_msg(i), v_sql_text);
else
insert into error_log(id,seq,tmstmp,username,errcode,msg)
values( v_id, i, v_tmstmp, user, ora_server_error(i), ora_server_error_msg(i) );
end if;
end loop;
commit;
end loop;
END;
/
select object_name,object_type,status from user_objects o where object_name='TRG_ERROR_LOGGING'
/
alter trigger trg_error_logging enable
/
As you can see it logs all errors into the table ERROR_LOG, but only if session parameter plsql_debug is set to true. Obviously, you can change it to own parameters or conditions.

subquery in oracle trigger

I want include condition in my trigger, based on data from a table. As oracle to do allow subquery in trigger, how it can be achived. Please find my code below. Trans_code_master holds the list of valid code, which can be changed.
Thanks in advance for all your help.
CREATE OR REPLACE TRIGGER CUST_TRG
BEFORE INSERT OR UPDATE ON CUST_ALL_TRANS
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
WHEN (NEW.TRANSACTION_CODE IN(SELECT TRANS_CODE FROM TRANS_CODE_MASTER))
BEGIN
INSERT INTO CUST_DEPO_TRANS
(
CUST_ID
,AC_ID
,TRANSACTION_CODE
)
VALUES(
:NEW.CUST_ID
,:NEW.AC_ID
,:NEW.TRANSACTION_CODE
)
EXCEPTION
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
RAISE;
END;
/
Its not possible in write query in when clause. Try the below way
CREATE OR REPLACE TRIGGER CUST_TRG
BEFORE INSERT OR UPDATE ON CUST_ALL_TRANS
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
V_CNT NUMBER;
BEGIN
SELECT COUNT(1) INTO V_CNT
FROM TRANS_CODE_MASTER
WHERE TRANS_CODE = NEW.TRANSACTION_CODE;
IF V_CNT > 0 THEN
INSERT INTO CUST_DEPO_TRANS
(
CUST_ID
,AC_ID
,TRANSACTION_CODE
)
VALUES(
:NEW.CUST_ID
,:NEW.AC_ID
,:NEW.TRANSACTION_CODE
)
END IF;
EXCEPTION
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
RAISE;
END;
/

Committing concurrent transactions only if all of them succeed

What I want is to execute a procedure ( that will run on Oracle 11g) concurrently, and to commit the whole operation if an only if all the concurrent transactions succeeded.
Two ways of parallel execution that I thought of were DBMS_PARALLEL_EXECUTE and dbms_job.submit(), but as I understand it, in both cases the processes created are running in their separate sessions and each process commits the changes upon termination (or in case of an error can rollback its own changes).
What I would like, is to start parallel processes, wait until each one of them is finished, check if they all were successful and only then commit the changes (or rollback if at least one process failed).
Is the above scenario possible? (And how can it be implemented)
Thanks.
I am curious why this requirement came about; I would probably question whether it was really necessary if this came to me. But if you cannot question the requirement, this is how I would go about it.
-- We need to have a common persistent location for all of the jobs to read
CREATE TABLE test_tab
(
job_name VARCHAR2(30),
status VARCHAR2(30)
)
/
-- The procedure writing to our table must be autonomous so that updates occur
-- without committing the rest of the work
CREATE OR REPLACE PROCEDURE test_log
(
i_job_name IN VARCHAR2,
i_status IN VARCHAR2
) IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
MERGE INTO test_tab tgt
USING dual
ON (tgt.job_name = i_job_name)
WHEN MATCHED THEN
UPDATE SET status = i_status
WHEN NOT MATCHED THEN
INSERT VALUES (i_job_name, i_status);
COMMIT;
END test_log;
/
CREATE OR REPLACE PROCEDURE test_proc(i_job_name IN VARCHAR2) IS
l_complete_cnt INTEGER;
l_error_cnt INTEGER;
l_waiting BOOLEAN := TRUE;
BEGIN
-- !!! Your code here !!!
/* -- Uncomment this block to prove the rollback scenario.
IF i_job_name LIKE '%8' THEN
raise_application_error(-20001, 'Throwing an error to prove rollback.');
END IF;*/
test_log(i_job_name, 'COMPLETE');
WHILE l_waiting LOOP
SELECT SUM(CASE WHEN status IN ('COMPLETE', 'COMMITTED') THEN 1 ELSE 0 END)
,SUM(CASE WHEN status = 'ERROR' THEN 1 ELSE 0 END)
INTO l_complete_cnt
,l_error_cnt
FROM test_tab
WHERE REGEXP_LIKE(job_name, 'TEST_JOB_\d');
IF l_complete_cnt = 8 THEN
COMMIT;
test_log(i_job_name, 'COMMITTED');
l_waiting := FALSE;
ELSIF l_error_cnt > 0 THEN
ROLLBACK;
test_log(i_job_name, 'ROLLBACK');
l_waiting := FALSE;
ELSE
dbms_lock.sleep(seconds => 5);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
test_log(i_job_name, 'ERROR');
RAISE;
END;
/
-- Begin test section
BEGIN
FOR i IN 1..8 LOOP
dbms_scheduler.create_job('TEST_JOB_'||i
,'PLSQL_BLOCK'
,'BEGIN test_proc(''TEST_JOB_'||i||'''); END;'
,start_date => SYSDATE
,enabled => TRUE);
END LOOP;
END;
/
SELECT * FROM test_tab;
TRUNCATE TABLE test_tab; --the table should be cleared between tests

Trigger to disable insert into a table for a specific day in oracle

How can i correctly write a trigger for this task:
create or replace trigger M_t2
after insert on emp
begin
if ( to_char(sysdate,'DY' ) = 'TUE' ) then
dbms_output.put_line('cannot insert into emp on tuesday');
end if;
end;
/
This does not work as i am still able to insert like this:
insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values('7935','BOLT','ANALYST',7698,sysdate,900,100,10);
dbms_output doesn't stop you doing anything, and you won't even see that message if your client isn't set up to show the output.
To prevent an action you'd need to raise an exception:
if ( to_char(sysdate,'DY' ) = 'TUE' ) then
raise_application_error(-20001, 'cannot insert into emp on tuesday');
end if;
The DY value is NLS-dependent so this could be circumvented by having a session with a different language, so you should take that into account using the optional third parameter to to_char(). It might as well be a before-insert trigger too:
create or replace trigger M_t2
before insert on emp
begin
if ( to_char(sysdate, 'DY', 'NLS_DATE_LANGUAGE=ENGLISH' ) = 'TUE' ) then
raise_application_error(-20001, 'cannot insert into emp on tuesday');
end if;
end;
/
insert into emp ...
ERROR at line 1:
ORA-20001: cannot insert into emp on tuesday
ORA-06512: at "<schema>.N_T2", line 3
ORA-04088: error during execution of trigger '<schema>.M_T2'
Please look at this: http://www.techonthenet.com/oracle/triggers/after_insert.php
I think you must use :new clause and use variable

DBMS Pl/SQL -- What would be output ... please explain?

SQL> DESC hostel;
Name Null? Type
------------------------------------ -------- -------------------
HOSTELID NOT NULL VARCHAR2(4)
ROOMSAVAILABLE NUMBER(3)
HOSTELTYPE VARCHAR2(1)
HOSTELFEE NUMBER(6)
SQL> SELECT * FROM hostel;
HOST ROOMSAVAILABLE H HOSTELFEE
------- ---------------------- ---- ---------------------
H1 2 M 2000
H2 3 F 3000
Above is shown a table hostel and values in it.
What would be the output of following pl/sql program?
please explain in detail.
CREATE OR REPLACE PROCEDURE sp_validatehostelid
(p_hostelid IN hostel.hostelid%TYPE,
p_hostelfee OUT hostel.hostelfee%TYPE
)
IS
v_count NUMBER;
v_hostelfee hostel.hostelfee%TYPE;
BEGIN
SELECT COUNT(*) INTO v_count FROM hostel WHERE hostelid=p_hostelid;
IF v_count=0 THEN
RAISE_APPLICATION_ERROR(-20000,'Invalid Hostel id');
ELSE
SELECT hostelfee INTO v_hostelfee FROM hostel WHERE hostelid=p_hostelid;
DBMS_OUTPUT.PUT_LINE('Hostel Fee:'||v_hostelfee);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No data found');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Other Errors in Procedure');
END sp_validatehostelid;
Procedure created.
DECLARE
g_hostelfee hostel.hostelfee%TYPE;
BEGIN
sp_validatehostelid('H5',g_hostelfee);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Other Errors in Block');
END;
"What will be the output? given that there is no row having hostelid =
'H5'"
Assuming you run this in a client with serveroutput enabled the output will be
Other Errors in Procedure
PL/SQL procedure successfully completed.
SQL>
Why?
The first select statement is a count, which cannot hurl a NO_DATA_FOUND exception.
The next line raises a user-defined exception, -20000.
This passes control to the exception handler block. -20000 is not NO_DATA_FOUND so the WHEN OTHERS clause is executed, which displays the message above.
The exception handler does not raise an exception itself, which is very bad practice. So the flow returns to the calling block.
Because no exception was found the calling block thinks the called procedure executed successfully, and so processing terminate cleanly. That's why it is bad practice not re-raise exceptions.
Note that if you run this without enabling serveroutput first the output will be:
PL/SQL procedure successfully completed.
SQL>
CREATE OR REPLACE PROCEDURE sp_validatehostelid
(
p_hostelid IN hostel.hostelid%TYPE,
p_hostelfee OUT hostel.hostelfee%TYPE
)
IS
v_count NUMBER;
v_hostelfee hostel.hostelfee%TYPE;
BEGIN
/* Count rows in 'hostel' table for given ID */
SELECT COUNT(*) INTO v_count FROM hostel WHERE hostelid=p_hostelid;
/* If there is noting in the table */
IF v_count=0 THEN
/* raise exception */
RAISE_APPLICATION_ERROR(-20000,'Invalid Hostel id');
ELSE
/* select fee from the 'hostel' table */
SELECT hostelfee INTO v_hostelfee FROM hostel WHERE hostelid=p_hostelid;
/* print the fee */
DBMS_OUTPUT.PUT_LINE('Hostel Fee:'||v_hostelfee);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No data found');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Other Errors in Procedure');
END sp_validatehostelid;
DECLARE
g_hostelfee hostel.hostelfee%TYPE;
BEGIN
sp_validatehostelid('H5',g_hostelfee);
/*
**Here something should be done with 'g_hostelfee' variable
*/
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Other Errors in Block');
END;
If there is a row with hostelid = 'H5' collect the fee for given ID, print it and pass it out.
NOTE: It will work only for one row per ID. If there is more than one. TO_MANY_VALUES exception will be raised.

Resources