I have some Oracle jobs running. In each job I have set up a PL/SQL exception handler that sends an email to developers if there is an error encountered in the code the job runs. Is there a way for me to know in the exception handler the job number so I can include that in the email?
Something like:
BEGIN
Run_This_Plsql();
EXCEPTION WHEN OTHERS DO
DECLARE
job_number VARCHAR2(64);
BEGIN
job_number := --This would be the job number of the currently running job
Send_email(job_number, subject, recipient, from);
END;
END;
While searching, I found this post in OraFAQs forum highlighting BG_JOB_ID parameter of sys_context function - give it a try.
BEGIN
Run_This_Plsql();
EXCEPTION WHEN OTHERS DO
DECLARE
job_number VARCHAR2(64);
BEGIN
job_number := sys_context('userenv', 'BG_JOB_ID');
Send_email(job_number, subject, recipient, from);
END;
END;
If you are talking about dbms_job then you can simply use job parameter. Let's say you have following procedure
procedure do_the_job(p_job_no in number) is
begin
...
exception
when others then
send_email(p_job_no, ...);
end;
then you can pass job number to the procedure like this:
declare
l_job_no number;
begin
dbms_job.submit(job => l_job_no, what => 'do_the_job(job);');
end;
/
To build on your suggested solution.
I would store the oracle Job number in a table and read this number when the process starts
you can then create logging, status, email detail tables that all relate to the job number.
here's some pseudocode:
BEGIN
SELECT oracle_job_no INTO v_job_no FROM Myjobs WHERE jobName='Daily doodah';
job_start(v_job_no); -- log job start
Run_This_Plsql(v_job_no);
job_end(v_job_no); -- log job end
EXCEPTION WHEN OTHERS DO
DECLARE
BEGIN
Job_fail(v_job_no); -- log job failure, send email, etc
END;
END;
or just use an off the shelf scheduling system that does all of this and more e.g autosys
If you want session info (session id for example, or any other session info), then you might try (choose columns you wish to report/log):
select * from v$session where sid=(select sys_context('USERENV','SID') from dual);
Hope that helps
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 have procedure like below, but when block is run it does not shows message for error if data is not found.
CREATE OR REPLACE
PROCEDURE DDPROJ_SP
(P_IDPROJ IN DD_PROJECT.IDPROJ%TYPE,
P_INFO OUT VARCHAR2,
p_check OUT VARCHAR2)
IS
CURSOR cur_ddproj IS
SELECT *
FROM dd_project
WHERE idproj = p_idproj;
lv_projinfo_txt VARCHAR2(100);
BEGIN
FOR rec_ddproj IN cur_ddproj LOOP
lv_projinfo_txt := (rec_ddproj.idproj||', '||rec_ddproj.projname||
', '||rec_ddproj.projstartdate||', '||rec_ddproj. projenddate||
', '||rec_ddproj.projfundgoal||', '||rec_ddproj.p rojcoord);
END LOOP;
P_INFO := LV_PROJINFO_TXT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
P_CHECK :='Please select another project';
DBMS_OUTPUT.PUT_LINE(P_CHECK);
END;
And block:
DECLARE
LV_INFO_TXT VARCHAR2(100);
LV_CHECK_TXT VARCHAR2(30);
BEGIN
DDPROJ_SP(00,lv_info_txt,lv_check_txt);
DBMS_OUTPUT.PUT_LINE(LV_INFO_TXT);
END;
After RUNNING BLOCK IF id provided is correct I would receive requested information but if ID is not found exception will not show message on print.
Firstly, as has been pointed out, your exception handler doesn't do anything really visible except call dbms_output, the results of which you'll only see if you set serverout on or otherwise access the results from dbms_output.
Secondly and more importantly, when you use a FOR loop to process the results of a cursor, the NO_DATA_FOUND exception will never be raised.
If you want to detect no rows found, you have a few options:
After the loop, check if the variable was set, e.g.:
...
end loop;
if lv_projinfo_txt is null then
raise no_data_found;
end if;
If you don't expect more than 1 record to be found by the query (which is suggested by your predicate on an "id"), you can avoid the FOR loop and use a simple select into:
PROCEDURE DDPROJ_SP
(P_IDPROJ IN DD_PROJECT.IDPROJ%TYPE,
P_INFO OUT VARCHAR2,
p_check OUT VARCHAR2)
IS
rec_ddproj dd_project%rowtype;
lv_projinfo_txt VARCHAR2(100);
BEGIN
SELECT *
into rec_ddproj
FROM dd_project
WHERE idproj = p_idproj;
lv_projinfo_txt := (rec_ddproj.idproj||', '||rec_ddproj.projname||
', '||rec_ddproj.projstartdate||', '||rec_ddproj.projenddate||
', '||rec_ddproj.projfundgoal||', '||rec_ddproj.projcoord);
P_INFO := LV_PROJINFO_TXT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
P_CHECK :='Please select another project';
DBMS_OUTPUT.PUT_LINE(P_CHECK);
END;
Notes:
A select into may raise NO_DATA_FOUND or TOO_MANY_ROWS.
Good practice is to never handle errors without re-raising the exception, unless your code actually handles the exception. In your case, your code merely sends a signal back to the calling process via the p_check parameter, which moves responsibility for handling the error to the caller. This might be ok in some circumstances but it assumes the caller actually heeds the signal. It would be better to raise an exception which forces the caller to handle it appropriately.
Good practice is to alias all columns and parameters in a query; having a SQL predicate like idproj = p_idproj makes the assumption that the table will never have a column called p_idproj in the future. Instead, it's good practice to deliberately alias all columns and parameters, e.g.
SELECT x.*
into rec_ddproj
FROM dd_project x
WHERE x.idproj = ddproj_sp.p_idproj;
Is there a way to monitor stored procedure execution time?
Also to do some operations if execution time takes more than some fixed time
For this is dbms_profiler package. Previously it should be set up for use. It creates a table service.
More details can be found in the documentation:
https://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_profil.htm#BJEFDBBC
I don't know a direct way but you can use PL/SQL package DBMS_APPLICATION_INFO
Procedure MY_Procedure is
begin
DBMS_APPLICATION_INFO.SET_MODULE('MY_Procedure', 'Starting');
...
DBMS_APPLICATION_INFO.SET_ACTION('Still working, please be patient');
...
DBMS_APPLICATION_INFO.SET_ACTION('Finished');
END;
While the procedure is running you can query (and perform some actions if needed) by this:
SELECT SID, serial#, username, module, action, sql_exec_start
FROM v$session;
If needed you can also put a timestamp or execute time, e.g. DBMS_APPLICATION_INFO.SET_ACTION('Started at '||systimestamp)
In case you are working with Scheduler jobs you can monitor and stop the jobs directly:
BEGIN
DBMS_SCHEDULER.SET_ATTRIBUTE(
NAME => 'MY_JOB',
ATTRIBUTE => 'MAX_RUN_DURATION',
VALUE => INTERVAL '10' MINUTE);
END;
and the call this frequently:
DECLARE
CURSOR Jobs IS
SELECT JOB_NAME, LAST_START_DATE, MAX_RUN_DURATION
FROM USER_SCHEDULER_JOBS
WHERE JOB_NAME = 'MY_JOB'
AND STATE = 'RUNNING'
AND SYSTIMESTAMP - LAST_START_DATE > MAX_RUN_DURATION;
BEGIN
FOR aJob IN Jobs LOOP
DBMS_SCHEDULER.STOP_JOB('MY_JOB', FORCE => TRUE);
END LOOP;
END;
for example I have created a table with table name 'aaa' with four columns act_num, clear_balance, available_balance, total_balance and I have inserted some values.
The function
deb_amount withdraws money from a bank account. It accepts an account
number and an amount of money as parameters. It uses the account number to
retrieve the account balance from the database, then computes the new balance. If this
new balance is less than zero then the function jumps to an error routine; otherwise,
it updates the bank account.
create or replace function deb_amount(p_act_num VARCHAR2, p_amount number )
return number as
declare
v_old_amount number;
v_new_amount number;
e_over_drawn exception;
begin
select clear_balance into v_old_amount from aaa where act_num=p_act_num;
v_new_amount:=v_old_amount-p_amount;
if v_old_amount<p_amount then
raise e_over_drawn;
else
update aaa set clear_balance=v_new_amount,available_balance=v_new_amount,total_balance=v_new_amount where act_num=p_act_num;
end if;
commit;
return clear_balance;
exception
when e_over_drawn then
rollback;
end;
it will compile, but with warnings.
If I want to execute the 'select * from deb_amount(1,100)' it show error.
sql command not ended properly.
Thank you.
You need to call function using dual. Ex:
select deb_amount(1,100) from dual;
or using a variable in plsql block
declare
l_return number;
begin
l_return:=deb_amount(1,100);
end;
It looks like you might be running several commands as a scipt, but haven't ended the function properly. The / after the function creation has to be on a line on its own, and at the start of the line:
create or replace function deb_amount(p_act_num VARCHAR2,
p_amount number)
return number as
declare
v_old_amount number;
v_new_amount number;
e_over_drawn exception;
begin
select clear_balance into v_old_amount
from aaa where act_num=p_act_num;
v_new_amount:=v_old_amount-p_amount;
if v_old_amount<p_amount then
raise e_over_drawn;
else
update aaa set clear_balance=v_new_amount,
available_balance=v_new_amount,
total_balance=v_new_amount
where act_num=p_act_num;
end if;
commit;
return clear_balance;
exception
when e_over_drawn then
rollback;
end;
/
show errors
select deb_account('1', 1) from dual;
The show errors will tell what actual compilation errors you got. It looks like it will complain about the return as you don't have a local clear_balance variable, but you can use v_new_amount instead here. You need to return something after the rollback too, or raise an exception which might be more useful.
As Manjunatha said, your query then needs to call the function properly, with the from clause referencing a table, rather than the function itself.
You have a bigger problem with the concept though; you can't call a function that does DML (insert, update, delete) from SQL, only from a PL/SQL block. Generally DML should be done from a procedure rather than a function, if it has to be done in PL/SQL at all.
I am using Oracle 10g and using following script to create the job
CREATE OR REPLACE PROCEDURE archtemp AS
BEGIN
UPDATE ARCH_TEMP SET ARCH_DATE = SYSDATE;
COMMIT;
END archtemp;
VAR jobno NUMBER;
BEGIN
DBMS_JOB.SUBMIT(:jobno, 'archtemp;', SYSDATE, 'sysdate + 1/1440');
COMMIT;
END;
The job never executes automatically (though it runs manually) with following error in alert_sid.log
ORA-12012: error on auto execute of job 26
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 8
I am unable to link the ORA-01422 error with any of my code. I'm not doing any fetch here.
Assuming this is a script for SQL*Plus, there are two / misssing, so it does nothing at all:
CREATE OR REPLACE PROCEDURE archtemp AS
BEGIN
UPDATE ARCH_TEMP SET ARCH_DATE = SYSDATE;
COMMIT;
END archtemp;
/
VAR jobno NUMBER;
BEGIN
DBMS_JOB.SUBMIT(:jobno, 'archtemp;', SYSDATE, 'sysdate + 1/1440');
COMMIT;
END;
/
I guess it's another job failing, not yours.
You don't do any data fetch here, but I guess some ON UPDATE trigger on ARCH_TEMP table might. Check it.
I'd use a SERVERERROR trigger (as described here) to try to catch the statement that is failing. But first, you could check the alert log. If recursive SQL is erroring, there may be a problem in the data dictionary.
Try putting in an explicit PL/SQL block as the WHAT parameter.
dbms_job.submit(v_jobno, 'begin archtemp; end;', sysdate, 'sysdate+1/1440');
Here's my test case, which seems to work fine:
create table arch_temp (
arch_date date
);
-- create row to test update
insert into arch_temp (arch_date) values (null);
create or replace procedure archtemp as
begin
update arch_temp set arch_date = sysdate;
commit;
end archtemp;
/
-- test everything works in isoloation
begin
archtemp;
end;
/
select * from arch_temp;
-- arch_date = 10:49:34
select * from user_jobs;
-- no rows returned
declare
v_jobno number;
begin
dbms_job.submit(v_jobno, 'begin archtemp; end;', sysdate, 'sysdate+1/1440');
commit;
dbms_output.put_line('v_jobno: ' || to_char(v_jobno));
end;
/
-- dbms_output...
-- v_jobno: 50520
select * from user_jobs;
-- JOB 50520 returned
-- LAST_DATE = 10:51:11
select * from arch_temp;
-- ARCH_DATE = 10:51:11
I tried solution by Nick Pierpoint as well but it didn't work for me
It looks something is wrong with LUCK because i tried the same thing on another machine having Oracle 9i and it failed!!!
Thank you all for your replies.
Regards