Can a JOB be created dynamically inside a trigger? - oracle

The execution of this trigger fails (it compiles but once I do the specified insert -> error)
create or replace
TRIGGER AFT_INSERT_TMP_TBL
AFTER INSERT ON TMP_TBL
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
V_SQL VARCHAR2(1000);
A_NAME VARCHAR2(100);
BEGIN
A_NAME:='ANY_NAME';
V_SQL:='BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => '''||A_NAME||''',
job_type => ''PLSQL_BLOCK'',
job_action => ''BEGIN DBMS_OUTPUT.PUT_LINE('||A_NAME||'); END;'',
start_date => TIMESTAMP''2011-12-4 10:30:00'',
repeat_interval => ''FREQ=MINUTELY;INTERVAL=2'',
auto_drop => FALSE,
comments => '''||A_NAME||''');
END;';
DBMS_OUTPUT.PUT_LINE('SCHEDULER :'||V_SQL);
EXECUTE IMMEDIATE V_SQL;
END AFT_INSERT_TMP_TBL;
-----------------------
Printed SCHEDULER creation code is totally valid.
I am getting a ORA-04092 'cannot in a trigger... A trigger attempted to commit or rollback. Rewrite the trigger so it doesn't commit or rollback'.
Is this a 'commit'? So a JOB cannot be created inside of a trigger?
I know I've used triggers with inserts into different tables, and that is also a "commit"
and Oracle didn't complaint.

Calling DBMS_SCHEDULER.CREATE_JOB implicitly commits so you cannot create a DBMS_SCHEDULER job in a trigger. This is one of the situations that still call for using the old DBMS_JOB package since DBMS_JOB.SUBMIT does not implicitly commit.
This trigger should create the job you want using the DBMS_JOB package rather than DBMS_SCHEDULER.
create or replace
TRIGGER AFT_INSERT_TMP_TBL
AFTER INSERT ON TMP_TBL
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
V_SQL VARCHAR2(1000);
A_NAME VARCHAR2(100);
l_jobno NUMBER;
BEGIN
A_NAME:='ANY_NAME';
dbms_job.submit( l_jobno,
'BEGIN dbms_output.put_line( ''' || a_name || ''' ); END;',
sysdate + interval '2' minute,
'sysdate + interval ''2'' minute' );
DBMS_OUTPUT.PUT_LINE('Job Number:'||l_jobno);
END AFT_INSERT_TMP_TBL;

You can also consider autonomous transaction
https://community.oracle.com/thread/2399412?start=0&tstart=0

Yes this is a commit. Oracle is updating a system table, job$ I think, when you create a scheduler or a job etc which means an automatic commit.
Why don't you just insert the information you need into another (driver) table and have a cron job or a dbms_job to create your scheduler jobs?

Related

Monitor stored procedure execution time in Oracle

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;

Find the job ID of the currently running job / allow only a single job to update a table

I have a "star" database. One is doing some work; another two have jobs that pull metadata into their reference tables, from the first database. I want to stop anyone from updating, deleting or inserting any records on the reference tables in the two "slaves"; the tables should just be updated by the scheduled job.
I'm currently doing this with a trigger that checks to see if the current SID is in USER_SCHEDULER_RUNNING_JOBS with the job name I expect to be running. I'd like to change this to use the JOB_ID of the job I'm running from.
This is my current set-up; assume a really simple table:
create table a ( b number );
and the following job:
begin
dbms_scheduler.create_job(
job_name => 'test_job'
, job_type => 'PLSQL_BLOCK'
, job_action => 'begin
merge into a a
using ( select 1 x from dual#db2 ) b
on (1 = 2)
when not matched then
insert values (b.x);
commit;
end;'
, start_date => sysdate
, repeat_interval => 'FREQ = MINUTELY;'
, enabled => true
);
end;
/
I'm using this trigger:
create or replace trigger tr_blah
before insert or update on a
declare
l_ct number;
begin
select count(*) into l_ct
from user_scheduler_running_jobs
where session_id = sys_context('USERENV','SID')
and job_name = 'TEST_JOB'
;
if l_ct = 0 then
raise_application_error(-20000, 'FAIL');
end if;
end;
/
This is inelegant; but, worse, I have to create a separate trigger for each table in each database and change the job name each time. There's no way of dynamically creating the trigger; this gets tiresome and there's a lot of scope for errors creeping in.
SYS_CONTEXT() has the parameters FG_JOB_ID and BG_JOB_ID. Their description, especially that of FG_JOB_ID implies that they might be the JOB_ID of the currently running job. Changing the trigger to the following (I've tried both):
create or replace trigger tr_a
before insert or update or delete on a
declare
l_ct number;
begin
raise_application_error(-20000, sys_context('USER_ENV', 'BG_JOB_ID'));
end;
/
Results in the following
ORA-20000:
ORA-06512: at "REF.TR_A", line 4
ORA-04088: error during execution of trigger 'REF.TR_A'
ORA-06512: at line 2
This implies that both FG_JOB_ID and BG_JOB_ID are null. Is there a method of determining the ID of the job running in the current session so I don't need to use JOB_NAME each time?
Most elegant solution is to use different database users. Make sure the job runs under a user that has update, insert and delete grants on the tables (possible the schema owner of the tables). Don't give these grants to the other users.
No need to mess around with triggers and such.

Is it possible to create a job from within a DBMS_SCHEDULER job?

Creating a job from within a job using DBMS_JOB throws an:
ORA-32317: cannot run a job from a job
Does anyone know if this restriction has been lifted in DBMS_SCHEDULER?
Yes you can:
DECLARE
BEGIN
dbms_scheduler.create_job
(job_name => 'TEST1',
job_type=> 'PLSQL_BLOCK',
job_action=>'BEGIN dbms_scheduler.create_job
(job_name => ''TEST2'',
job_type=> ''PLSQL_BLOCK'',
job_action=>''DECLARE v_result NUMBER; BEGIN SELECT 1 INTO v_result FROM dual; END;'',
start_date=>SYSDATE,
repeat_interval=>''FREQ=DAILY'',
end_date=>SYSDATE+1,
enabled=>true,
auto_drop=>false,
comments=>''Job submitted FROM a job''); end;',
start_date=>SYSDATE,
repeat_interval=>'FREQ=DAILY',
end_date=>SYSDATE+1,
enabled=>true,
auto_drop=>false,
comments=>'Job to submit a job');
END;
This demonstrates the concept. You could call a procedure instead of an inline PL/SQL block to do your job submittal. Clearly you can adjust any or all of the parameters to the create_job procedure. I would guess you could spawn a job that spawns yet another job.

Oracle text scheduling sync on single index

I need to sync an oracle text index. But job fails to create:
declare
v_job_id number(19,0);
begin
dbms_job.submit(
JOB => v_job_id,
WHAT => 'alter index NAME_IDX rebuild parameters (''sync'');',
NEXT_DATE => SYSDATE + (1/24),
INTERVAL => 'SYSDATE + (1/24) + 7'
);
end;
/
Or to run:
declare
v_job_id number(19,0);
begin
dbms_job.submit(
JOB => v_job_id,
WHAT => 'CTX_DDL(''NAME_IDX'');',
NEXT_DATE => SYSDATE + (1/24),
INTERVAL => 'SYSDATE + (1/24) + 7'
);
end;
/
But if I run any of those works:
alter index NAME_IDX rebuild parameters ('sync');
call CTX_DDL('NAME_IDX');
Any idea of the correct syntax?
Thank you.
PD: Ive been searching, but the only answer I found doesnt fit my requirements. I also apologize for my english.
You can run an anonymous block, CALL is not in PL/SQL, ALTER INDEX is DDL, and you need to specify which procedure in CTX_DDL you want to run:
WHAT => 'BEGIN EXECUTE IMMEDIATE ''alter index NAME_IDX rebuild parameters (''''sync'''')''; CTX_DDL.sync_index(''NAME_IDX''); END',
However, personally I prefer to encapsulate it in a procedure (or, even better, a package) and call the procedure from the job:
CREATE PROCEDURE rebuild_name_idx IS
BEGIN
EXECUTE IMMEDIATE 'alter index NAME_IDX rebuild parameters (''sync'')';
CTX_DDL.sync_index('NAME_IDX');
END;
/
declare
v_job_id number(19,0);
begin
dbms_job.submit(
JOB => v_job_id,
WHAT => 'rebuild_name_idx;',
NEXT_DATE => SYSDATE + (1/24),
INTERVAL => 'SYSDATE + (1/24) + 7'
);
end;
/
Also, I'm pretty sure you don't actually need to rebuild the index - you only need to call CTX_DDL.sync_index to refresh it from any DML on the table.

Oracle scheduled job fails

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

Resources