04 November 2011

Passing Parameters to Scheduler Programs in Chains


One of my previous post(Parallel Processing with Oracle Scheduler Chains), i have given some information about Scheduler Chains. As i mentioned in the post, it is not possible to set Scheduler Programs Arguments in Scheduler Chains on-the-fly. There is a restriction of Oracle that does not allow you to pass parameters via a pre-step before program execution by using DBMS_SCHEDULER.set_job_argument_value routine. If you do this, you will get “ORA-27457”. I will show how to get rid of tihs error with a simple workaround. The workaround is using DBMS_ALERT package. A communication channel will be established between scheduler programs(sessions) in order send/receive messages. Following demonstration will show you, how to serialize scheduler programs.

I will use following scenairo:
Business Scenario : We are in the telco sector and we are implementing something that should be run before montly billing process. Our telco operator has over 30M subs. So, we need to implement the something enough fast…
Implementaion : We have one process that should be run first in order to find the subscirbers who we are interested in. After the subs have been found, we can run some business rules over them.
Design : We will create a table(CUTOFF_INTERIM_TABLE) in order to hold interested-in-subscribers. A simple routine(FillInterimTable procedure) will fill this table. Then we will use a routine(ProcessInterimTable procedure) to get the subscribers and do something. First procedure(FillInterimTable) should take an input parameter(pin_TotalProcessCount) and use a simple MODULO operation(MOD(i, pin_TotalProcessCount) + 1) to seperate the subscirbers into chunks. Second procedure(ProcessInterimTable) can be run (pin_TotalProcessCount) times simultaneously, and every process will take only their chunks(…WHERE PROCESS_NUMBER = pin_ProcessNumber). The important thing is that, second procedures should run if and only if the first procedure succeeded. Otherwise there is no need to run second processes because of there is no subscribers to be processed. Both of these two routines takes an additional input parameter(pid_Cutoff) that shows billing process cutoff date. Of course every billing time, this cutoff date will change. So, we need this parameter somehow configurable by passing parameters. As i said before, it is not possible to set job argument values on-the-fly. Because of this restriction, we will do some tricks . We will create an additional routine(SetJobCutoffParameters procedure) in order to set these cutoff values by communicating between sessions. What we will do in this procedure is, we will send this cutoff parameter from this routine(DBMS_ALERT package is used for this manner). In order to communicate between sessions, every session should register themselves to DBMS_ALERT package. FillInterimTable and ProcessInterimTable procedures should be started (for registering) before SetJobCutoffParameters procedure. And these two procedures should wait in a predefined interval to get the cutoff message. After FillInterimTable procedure receives the cutoff parameter, it will start to fill interim table. But ProcessInterimTable procedure will not start when it receives cutoff message. It will also wait FillInterimTable procedure to finish its job. Again, we will send a message from FillInterimTable to ProcessInterimTable procedure to start its job. We will use 5 parallel processes.



CREATE TABLE CUTOFF_INTERIM_TABLE
(
  CUTOFF           DATE,
  CO_ID            NUMBER,
  STATUS           VARCHAR2(1),
  CREATE_DATE      DATE,
  REMARK           VARCHAR2(1024),
  PROCESS_NUMBER   NUMBER
);

CREATE TABLE CUTOFF_PROCESS_EXECUTION_LOG
(
  CUTOFF           DATE,
  PROCESS_NUMBER   NUMBER,
  LOG_MESSAGE      VARCHAR2(1024),
  LOG_TIMESTAMP    TIMESTAMP
);

--create proceduress
CREATE OR REPLACE PROCEDURE CreateProcessExecutionLog
(
  pid_Cutoff        IN CUTOFF_PROCESS_EXECUTION_LOG.CUTOFF%TYPE,
  pin_ProcessNumber IN CUTOFF_PROCESS_EXECUTION_LOG.PROCESS_NUMBER%TYPE,
  pis_LogMessage    IN CUTOFF_PROCESS_EXECUTION_LOG.LOG_MESSAGE%TYPE
) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN

  INSERT INTO CUTOFF_PROCESS_EXECUTION_LOG
    (CUTOFF, PROCESS_NUMBER, LOG_MESSAGE, LOG_TIMESTAMP)
  VALUES
    (pid_Cutoff, pin_ProcessNumber, pis_LogMessage, SYSTIMESTAMP);

  COMMIT;

END CreateProcessExecutionLog;
/

CREATE OR REPLACE PROCEDURE FillInterimTable
(
  pid_Cutoff            IN CUTOFF_INTERIM_TABLE.CUTOFF%TYPE,
  pin_TotalProcessCount IN NUMBER
) IS
  vn_ProcessIndependent      CUTOFF_INTERIM_TABLE.PROCESS_NUMBER%TYPE;
  vd_Cutoff                  CUTOFF_INTERIM_TABLE.CUTOFF%TYPE;
  vs_AlertMessage_SetParams  VARCHAR2(100);
  vs_AlertMessage_FillTable  VARCHAR2(100);
  vs_AlertStatus             PLS_INTEGER;
  vs_AlertName_SetParams     VARCHAR2(100);
  vs_AlertName_FillTable     VARCHAR2(100);
  vs_LogMessage              CUTOFF_PROCESS_EXECUTION_LOG.LOG_MESSAGE%TYPE;
  vn_AlertTimeout_SetParams  PLS_INTEGER;
BEGIN
 
  vn_ProcessIndependent      := 0;
  vs_AlertName_SetParams     := 'CUTOFF__FILL_PRG__PARAM1';
  vs_AlertName_FillTable     := 'CUTOFF__FILL_PRG__FINISHED';
  vn_AlertTimeout_SetParams  := 30;
  vs_AlertMessage_FillTable  := 'FINISHED';
 
  vs_LogMessage := 'FillInterimTable started. Total process count : ' || pin_TotalProcessCount;
  CreateProcessExecutionLog(vd_Cutoff, vn_ProcessIndependent, vs_LogMessage);

  IF pid_Cutoff IS NULL THEN
    vs_LogMessage := 'pid_Cutoff parameter is NULL.';
    CreateProcessExecutionLog(NULL, vn_ProcessIndependent,vs_LogMessage );

    vs_LogMessage := 'Registering DBMS_ALERT to be informed about ' || vs_AlertName_SetParams || ' message...';
    CreateProcessExecutionLog(NULL, vn_ProcessIndependent,vs_LogMessage );
    DBMS_ALERT.REGISTER(vs_AlertName_SetParams);

    vs_LogMessage := 'Waiting for DBMS_ALERT ' || vs_AlertName_SetParams || ' message...';
    CreateProcessExecutionLog(NULL, vn_ProcessIndependent,vs_LogMessage );
    DBMS_ALERT.WAITONE(vs_AlertName_SetParams, vs_AlertMessage_SetParams, vs_AlertStatus, vn_AlertTimeout_SetParams);
    IF vs_AlertStatus = 0 THEN
      vd_Cutoff := TO_DATE(vs_AlertMessage_SetParams, 'DD.MM.YYYY');
      vs_LogMessage := vs_AlertName_SetParams || ' is taken from alert message:'|| vs_AlertMessage_SetParams;
      CreateProcessExecutionLog(vd_Cutoff, vn_ProcessIndependent,vs_LogMessage );
    ELSE
      vs_LogMessage := 'Unable to get ' || vs_AlertName_SetParams || ' alert message:' || vs_AlertStatus;
      CreateProcessExecutionLog(vd_Cutoff, vn_ProcessIndependent, vs_LogMessage);
      RAISE_APPLICATION_ERROR( -20011, vs_LogMessage );
    END IF;
    vs_LogMessage := 'Removing DBMS_ALERT for ' || vs_AlertName_SetParams;
    CreateProcessExecutionLog(vd_Cutoff, vn_ProcessIndependent,vs_LogMessage );
    DBMS_ALERT.remove(vs_AlertName_SetParams);
  ELSE
    vd_Cutoff := pid_Cutoff;
  END IF;

  --some business checks etc
  ---
  ----
  EXECUTE IMMEDIATE 'TRUNCATE TABLE CUTOFF_INTERIM_TABLE';
  FOR i IN 1 .. 100000 LOOP
    INSERT INTO CUTOFF_INTERIM_TABLE
      (CUTOFF, CO_ID, STATUS, CREATE_DATE, REMARK,  PROCESS_NUMBER )
    VALUES
      (vd_Cutoff, i, 'N', SYSDATE, 'Successfully created.', MOD(i, pin_TotalProcessCount) + 1);
  END LOOP;
  COMMIT;
 
  vs_LogMessage := 'CUTOFF_INTERIM_TABLE insertion is finished.';
  CreateProcessExecutionLog(vd_Cutoff, vn_ProcessIndependent, vs_LogMessage);
 
  IF NOT( pin_TotalProcessCount = 1 ) THEN
    vs_LogMessage := 'Signalling DBMS_ALERT for ' || vs_AlertName_FillTable;
    CreateProcessExecutionLog(vd_Cutoff, vn_ProcessIndependent, vs_LogMessage);
    DBMS_ALERT.SIGNAL(vs_AlertName_FillTable, vs_AlertMessage_FillTable);
    COMMIT;
  END IF;


  vs_LogMessage := 'FillInterimTable finished...';
  CreateProcessExecutionLog(vd_Cutoff, vn_ProcessIndependent, vs_LogMessage);

  COMMIT;

END FillInterimTable;
/

CREATE OR REPLACE PROCEDURE ProcessInterimTable
(
  pid_Cutoff            IN CUTOFF_INTERIM_TABLE.CUTOFF%TYPE,
  pin_ProcessNumber     IN CUTOFF_INTERIM_TABLE.PROCESS_NUMBER%TYPE,
  pin_TotalProcessCount IN NUMBER
) IS
  vd_Cutoff                  CUTOFF_INTERIM_TABLE.CUTOFF%TYPE;
  vs_AlertMessage_SetParams  VARCHAR2(100);
  vs_AlertMessage_FillTable  VARCHAR2(100);
  vs_AlertStatus             PLS_INTEGER;
  vs_AlertName_SetParams     VARCHAR2(100);
  vs_AlertName_FillTable     VARCHAR2(100);
  vs_LogMessage              CUTOFF_PROCESS_EXECUTION_LOG.LOG_MESSAGE%TYPE;
  vn_AlertTimeout_SetParams  PLS_INTEGER;
  vn_AlertTimeout_FillTable  PLS_INTEGER;
BEGIN
  vs_LogMessage := 'ProcessInterimTable started for process ' || pin_ProcessNumber;
  CreateProcessExecutionLog(vd_Cutoff, pin_ProcessNumber, vs_LogMessage );
 
  vs_AlertName_SetParams     := 'CUTOFF__EXEC' || pin_ProcessNumber || '__PARAM1';
  vs_AlertName_FillTable     := 'CUTOFF__FILL_PRG__FINISHED';
  vn_AlertTimeout_SetParams  := 30;
  vn_AlertTimeout_FillTable  := 60 * 60 * 4;--4 hours
 
  IF NOT(pin_TotalProcessCount = 1 AND pin_ProcessNumber = 1) THEN
    vs_LogMessage := 'Registering DBMS_ALERT to be informed about ' || vs_AlertName_FillTable || ' message...';
    CreateProcessExecutionLog(NULL, pin_ProcessNumber,vs_LogMessage );
    DBMS_ALERT.REGISTER(vs_AlertName_FillTable);
  END IF;

  IF pid_Cutoff IS NULL THEN
    vs_LogMessage := 'pid_Cutoff parameter is NULL.';
    CreateProcessExecutionLog(NULL, pin_ProcessNumber,vs_LogMessage );
   
    vs_LogMessage := 'Registering DBMS_ALERT to be informed about ' || vs_AlertName_SetParams || ' message...';
    CreateProcessExecutionLog(NULL, pin_ProcessNumber,vs_LogMessage );
    DBMS_ALERT.REGISTER(vs_AlertName_SetParams);
   
    vs_LogMessage := 'Waiting for DBMS_ALERT ' || vs_AlertName_SetParams || ' message...';
    CreateProcessExecutionLog(NULL, pin_ProcessNumber,vs_LogMessage );
    DBMS_ALERT.WAITONE(vs_AlertName_SetParams, vs_AlertMessage_SetParams, vs_AlertStatus, vn_AlertTimeout_SetParams);
    IF vs_AlertStatus = 0 THEN
      vd_Cutoff := TO_DATE(vs_AlertMessage_SetParams, 'DD.MM.YYYY');
      vs_LogMessage :=  vs_AlertName_SetParams || ' is taken from alert message:'|| vs_AlertMessage_SetParams;
      CreateProcessExecutionLog(vd_Cutoff, pin_ProcessNumber, vs_LogMessage);
    ELSE
      vs_LogMessage := 'Unable to get ' || vs_AlertName_SetParams || ' from alert message:' || vs_AlertStatus;
      CreateProcessExecutionLog(NULL, pin_ProcessNumber, vs_LogMessage);
      RAISE_APPLICATION_ERROR( -20011, vs_LogMessage );
    END IF;
    vs_LogMessage := 'Removing DBMS_ALERT for ' || vs_AlertName_SetParams;
    CreateProcessExecutionLog(vd_Cutoff, pin_ProcessNumber,vs_LogMessage );
    DBMS_ALERT.remove(vs_AlertName_SetParams);
  ELSE
    vd_Cutoff := pid_Cutoff;
  END IF;


  IF pin_TotalProcessCount = 1 AND pin_ProcessNumber = 1 THEN
    -- there is only one process, no need parallel processing
    FillInterimTable(vd_Cutoff, pin_TotalProcessCount);
  ELSE
    vs_LogMessage := 'Waiting for DBMS_ALERT ' || vs_AlertName_FillTable || ' message in order to start processing...';
    CreateProcessExecutionLog(vd_Cutoff, pin_ProcessNumber,vs_LogMessage );
   
    DBMS_ALERT.WAITONE(vs_AlertName_FillTable, vs_AlertMessage_FillTable, vs_AlertStatus, vn_AlertTimeout_FillTable);
    IF vs_AlertStatus = 0 THEN
      vs_LogMessage :=  vs_AlertName_FillTable || ' is taken from alert message:'|| vs_AlertMessage_FillTable;
      CreateProcessExecutionLog(vd_Cutoff, pin_ProcessNumber, vs_LogMessage);
    ELSE
      vs_LogMessage := 'Unable to get ' || vs_AlertName_FillTable || ' from alert message:' || vs_AlertStatus;
      CreateProcessExecutionLog(vd_Cutoff, pin_ProcessNumber, vs_LogMessage);
      RAISE_APPLICATION_ERROR( -20012, vs_LogMessage );
    END IF;
   
    vs_LogMessage := 'Removing DBMS_ALERT for ' || vs_AlertName_FillTable;
    CreateProcessExecutionLog(vd_Cutoff, pin_ProcessNumber,vs_LogMessage );
    DBMS_ALERT.remove(vs_AlertName_FillTable);
  END IF;

  FOR rec_InterimTable IN (
    SELECT T.CO_ID, T.REMARK, T.ROWID AS RD
      FROM CUTOFF_INTERIM_TABLE T
     WHERE STATUS = 'N'
       AND PROCESS_NUMBER = pin_ProcessNumber
    ) LOOP
    --some business processes
    ---
    ----
    UPDATE CUTOFF_INTERIM_TABLE
       SET STATUS = 'S',
           REMARK = 'Successfully processed..'
     WHERE ROWID = rec_InterimTable.RD;
    
    COMMIT;
  END LOOP;


  vs_LogMessage := 'ProcessInterimTable finished for process ' || pin_ProcessNumber;
  CreateProcessExecutionLog(vd_Cutoff, pin_ProcessNumber, vs_LogMessage );

END ProcessInterimTable;
/

CREATE OR REPLACE PROCEDURE SetJobCutoffParameters IS
  vs_CutoffString       VARCHAR2(100);
  vd_Cutoff             DATE;
  vs_LogMessage         CUTOFF_PROCESS_EXECUTION_LOG.LOG_MESSAGE%TYPE;
  vs_AlertName          VARCHAR2(100);
BEGIN
  vs_CutoffString       := '20.09.2011';
  vd_Cutoff             := TO_DATE(vs_CutoffString, 'DD.MM.YYYY');

  vs_LogMessage := 'SetJobCutoffParameters started for cutoff ' || vs_CutoffString;
  CreateProcessExecutionLog(vd_Cutoff, -1, vs_LogMessage);

  vs_AlertName := 'CUTOFF__FILL_PRG__PARAM1';
  vs_LogMessage := 'Signalling DBMS_ALERT for ' || vs_AlertName;
  CreateProcessExecutionLog(vd_Cutoff, -1, vs_LogMessage);
  DBMS_ALERT.SIGNAL(vs_AlertName, vs_CutoffString);
  COMMIT;

  FOR vn_ProcessNumber IN 1 .. 5 LOOP
    vs_AlertName := 'CUTOFF__EXEC' || vn_ProcessNumber || '__PARAM1';
    vs_LogMessage := 'Signalling DBMS_ALERT for ' || vs_AlertName;
    CreateProcessExecutionLog(vd_Cutoff, -1, vs_LogMessage);
    DBMS_ALERT.SIGNAL( vs_AlertName, vs_CutoffString);
    COMMIT;
  END LOOP;

  vs_LogMessage := 'SetJobCutoffParameters finished for cutoff ' || vs_CutoffString;
  CreateProcessExecutionLog(vd_Cutoff, -1, vs_LogMessage);

  COMMIT;

END SetJobCutoffParameters;
/

DECLARE
  vn_TotalProcessCount   PLS_INTEGER;
BEGIN
  vn_TotalProcessCount := 5;
  DBMS_SCHEDULER.create_program(program_name        => 'CUTOFF__FILL_PRG',
                                program_type        => 'STORED_PROCEDURE',
                                program_action      => 'FillInterimTable',
                                number_of_arguments => 2,
                                enabled             => FALSE,
                                comments            => 'Program for FillInterimTable(5 parallel processes)');
  DBMS_SCHEDULER.define_program_argument(program_name      => 'CUTOFF__FILL_PRG',
                                         argument_position => 1,
                                         argument_type     => 'DATE',
                                         default_value     => NULL
                                         );
  DBMS_SCHEDULER.define_program_argument(program_name      => 'CUTOFF__FILL_PRG',
                                         argument_position => 2,
                                         argument_type     => 'NUMBER',
                                         default_value     => vn_TotalProcessCount);
  DBMS_SCHEDULER.create_job(job_name     => 'CUTOFF__FILL_JOB',
                            program_name => 'CUTOFF__FILL_PRG',
                            start_date   => NULL,
                            enabled      => FALSE,
                            auto_drop    => FALSE,
                            comments     => 'Job for FillInterimTable(5 parallel processes)');
END;
/

BEGIN
  DBMS_SCHEDULER.enable(name => 'CUTOFF__FILL_PRG' );
  DBMS_SCHEDULER.enable(name => 'CUTOFF__FILL_JOB' );
END;
/



DECLARE
  vn_TotalProcessCount   PLS_INTEGER;
BEGIN
  vn_TotalProcessCount := 5;
  FOR vn_ProcessNumber IN 1..vn_TotalProcessCount LOOP

    DBMS_SCHEDULER.create_program(program_name        => 'CUTOFF__EXEC' || vn_ProcessNumber || '_PRG',
                                  program_type        => 'STORED_PROCEDURE',
                                  program_action      => 'ProcessInterimTable',
                                  number_of_arguments => 3,
                                  enabled             => FALSE,
                                  comments            => 'Program ProcessInterimTable ' || vn_ProcessNumber || ' of 5 process');
      DBMS_SCHEDULER.define_program_argument(program_name      => 'CUTOFF__EXEC' || vn_ProcessNumber || '_PRG',
                                             argument_position => 1,
                                             argument_type     => 'DATE',
                                             default_value     => NULL);
      DBMS_SCHEDULER.define_program_argument(program_name      => 'CUTOFF__EXEC' || vn_ProcessNumber || '_PRG',
                                             argument_position => 2,
                                             argument_type     => 'NUMBER',
                                             default_value     => vn_ProcessNumber);
      DBMS_SCHEDULER.define_program_argument(program_name      => 'CUTOFF__EXEC' || vn_ProcessNumber || '_PRG',
                                             argument_position => 3,
                                             argument_type     => 'NUMBER',
                                             default_value     => vn_TotalProcessCount);
      DBMS_SCHEDULER.create_job(job_name     => 'CUTOFF__EXEC' || vn_ProcessNumber || '_JOB',
                                program_name => 'CUTOFF__EXEC' || vn_ProcessNumber || '_PRG',
                                start_date   => NULL,
                                enabled      => FALSE,
                                auto_drop    => FALSE,
                                comments     => 'Job for ProcessInterimTable ' || vn_ProcessNumber || ' of 5 process');
  END LOOP;
 

END;
/

DECLARE
  vn_TotalProcessCount   PLS_INTEGER;
BEGIN
  vn_TotalProcessCount := 5;
  FOR vn_ProcessNumber IN 1..vn_TotalProcessCount LOOP
    DBMS_SCHEDULER.enable(name => 'CUTOFF__EXEC' || vn_ProcessNumber || '_JOB');
    DBMS_SCHEDULER.enable(name => 'CUTOFF__EXEC' || vn_ProcessNumber || '_PRG');
  END LOOP;
END;
/




BEGIN
  DBMS_SCHEDULER.create_program(program_name        => 'CUTOFF__SETPARAMS_PRG',
                                program_type        => 'STORED_PROCEDURE',
                                program_action      => 'SetJobCutoffParameters',
                                enabled             => TRUE,
                                comments            => 'Program for SetJobCutoffParameters');
  DBMS_SCHEDULER.create_job(job_name     => 'CUTOFF__SETPARAMS_JOB',
                            program_name => 'CUTOFF__SETPARAMS_PRG',
                            start_date   => NULL,
                            enabled      => TRUE,
                            auto_drop    => FALSE,
                            comments     => 'Job for SetJobCutoffParameters');
END;
/



BEGIN
  DBMS_SCHEDULER.create_chain(chain_name          => 'CUTOFF__CHAIN',
                              rule_set_name       => NULL,
                              evaluation_interval => NULL,
                              comments            => 'Chain for CUTOFF( 5 processes)');
                             
                             
  DBMS_SCHEDULER.define_chain_step(chain_name   => 'CUTOFF__CHAIN',
                                   step_name    => 'SET_PARAMS_STEP',
                                   program_name => 'CUTOFF__SETPARAMS_PRG');
                                  
  DBMS_SCHEDULER.define_chain_step(chain_name   => 'CUTOFF__CHAIN',
                                   step_name    => 'FILL_INTERIM_TABLE_STEP',
                                   program_name => 'CUTOFF__FILL_PRG');

  DBMS_SCHEDULER.define_chain_step(chain_name   => 'CUTOFF__CHAIN',
                                   step_name    => 'EXECUTE_1OF5_STEP',
                                   program_name => 'CUTOFF__EXEC1_PRG');
  DBMS_SCHEDULER.define_chain_step(chain_name   => 'CUTOFF__CHAIN',
                                   step_name    => 'EXECUTE_2OF5_STEP',
                                   program_name => 'CUTOFF__EXEC2_PRG');
  DBMS_SCHEDULER.define_chain_step(chain_name   => 'CUTOFF__CHAIN',
                                   step_name    => 'EXECUTE_3OF5_STEP',
                                   program_name => 'CUTOFF__EXEC3_PRG');
  DBMS_SCHEDULER.define_chain_step(chain_name   => 'CUTOFF__CHAIN',
                                   step_name    => 'EXECUTE_4OF5_STEP',
                                   program_name => 'CUTOFF__EXEC4_PRG');
  DBMS_SCHEDULER.define_chain_step(chain_name   => 'CUTOFF__CHAIN',
                                   step_name    => 'EXECUTE_5OF5_STEP',
                                   program_name => 'CUTOFF__EXEC5_PRG');

END;
/

BEGIN

  DBMS_SCHEDULER.define_chain_rule(chain_name => 'CUTOFF__CHAIN',
                                   condition  => 'TRUE',
                                   action     => 'AFTER 00:00:10 START SET_PARAMS_STEP',
                                   rule_name  => 'SET_PARAMS_RULE',
                                   comments   => 'Start CUTOFF - Set parameters.');
                                
  DBMS_SCHEDULER.define_chain_rule(chain_name => 'CUTOFF__CHAIN',
                                   condition  => 'TRUE',
                                   action     => 'AFTER 00:00:05 START FILL_INTERIM_TABLE_STEP',
                                   rule_name  => 'FILL_INTERIM_TABLE_RULE',
                                   comments   => 'Start CUTOFF - Fill Interim Table.');

  DBMS_SCHEDULER.define_chain_rule(chain_name => 'CUTOFF__CHAIN',
                                   condition  => 'TRUE',
                                   action     => 'START EXECUTE_1OF5_STEP',
                                   rule_name  => 'EXECUTE_1OF5_RULE',
                                   comments   => 'Execute CUTOFF - 1 of 5 process.');
  DBMS_SCHEDULER.define_chain_rule(chain_name => 'CUTOFF__CHAIN',
                                   condition  => 'TRUE',
                                   action     => 'START EXECUTE_2OF5_STEP',
                                   rule_name  => 'EXECUTE_2OF5_RULE',
                                   comments   => 'Execute CUTOFF - 2 of 5 process.');
  DBMS_SCHEDULER.define_chain_rule(chain_name => 'CUTOFF__CHAIN',
                                   condition  => 'TRUE',
                                   action     => 'START EXECUTE_3OF5_STEP',
                                   rule_name  => 'EXECUTE_3OF5_RULE',
                                   comments   => 'Execute CUTOFF - 3 of 5 process.');
  DBMS_SCHEDULER.define_chain_rule(chain_name => 'CUTOFF__CHAIN',
                                   condition  => 'TRUE',
                                   action     => 'START EXECUTE_4OF5_STEP',
                                   rule_name  => 'EXECUTE_4OF5_RULE',
                                   comments   => 'Execute CUTOFF - 4 of 5 process.');
  DBMS_SCHEDULER.define_chain_rule(chain_name => 'CUTOFF__CHAIN',
                                   condition  => 'TRUE',
                                   action     => 'START EXECUTE_5OF5_STEP',
                                   rule_name  => 'EXECUTE_5OF5_RULE',
                                   comments   => 'Execute CUTOFF - 5 of 5 process.');                                  
                                  

  DBMS_SCHEDULER.define_chain_rule(chain_name => 'CUTOFF__CHAIN',
                                   condition  => '(SET_PARAMS_STEP COMPLETED) AND (FILL_INTERIM_TABLE_STEP COMPLETED) AND (EXECUTE_1OF5_STEP COMPLETED AND EXECUTE_2OF5_STEP COMPLETED AND EXECUTE_3OF5_STEP COMPLETED AND EXECUTE_4OF5_STEP COMPLETED AND EXECUTE_5OF5_STEP COMPLETED)',
                                   action     => 'END',
                                   rule_name  => 'EXECUTE_END_RULE',
                                   comments   => 'Execute CUTOFF - finished.');

END;
/

BEGIN
  DBMS_SCHEDULER.ENABLE('CUTOFF__CHAIN');
END;
/





BEGIN
  DBMS_SCHEDULER.create_job(job_name     => 'CUTOFF__CHAIN_JOB',
                            job_type     => 'CHAIN',
                            job_action   => 'CUTOFF__CHAIN',
                            start_date   => NULL,
                            enabled      => FALSE,
                            auto_drop    => FALSE,
                            comments     => 'Job for CUTOFF__CHAIN ');

END;
/


SELECT * FROM CUTOFF_PROCESS_EXECUTION_LOG where process_number in(-1,0,1)ORDER BY LOG_TIMESTAMP ;
PROCESS_NUMBER
LOG_MESSAGE
LOG_TIMESTAMP
1
ProcessInterimTable started for process 1
04/11/2011 23:14:27,459185
1
Registering DBMS_ALERT to be informed about CUTOFF__FILL_PRG__FINISHED message...
04/11/2011 23:14:27,459645
1
pid_Cutoff parameter is NULL.
04/11/2011 23:14:27,699741
1
Registering DBMS_ALERT to be informed about CUTOFF__EXEC1__PARAM1 message...
04/11/2011 23:14:27,699929
1
Waiting for DBMS_ALERT CUTOFF__EXEC1__PARAM1 message...
04/11/2011 23:14:27,700386
0
FillInterimTable started. Total process count : 5
04/11/2011 23:14:32,357325
0
pid_Cutoff parameter is NULL.
04/11/2011 23:14:32,357890
0
Registering DBMS_ALERT to be informed about CUTOFF__FILL_PRG__PARAM1 message...
04/11/2011 23:14:32,358121
0
Waiting for DBMS_ALERT CUTOFF__FILL_PRG__PARAM1 message...
04/11/2011 23:14:32,492771
-1
SetJobCutoffParameters started for cutoff 20.09.2011
04/11/2011 23:14:37,313504
-1
Signalling DBMS_ALERT for CUTOFF__FILL_PRG__PARAM1
04/11/2011 23:14:37,314077
-1
Signalling DBMS_ALERT for CUTOFF__EXEC1__PARAM1
04/11/2011 23:14:37,321452
-1
Signalling DBMS_ALERT for CUTOFF__EXEC2__PARAM1
04/11/2011 23:14:37,322110
-1
Signalling DBMS_ALERT for CUTOFF__EXEC3__PARAM1
04/11/2011 23:14:37,323255
-1
Signalling DBMS_ALERT for CUTOFF__EXEC4__PARAM1
04/11/2011 23:14:37,333059
-1
Signalling DBMS_ALERT for CUTOFF__EXEC5__PARAM1
04/11/2011 23:14:37,333820
-1
SetJobCutoffParameters finished for cutoff 20.09.2011
04/11/2011 23:14:37,334397
0
CUTOFF__FILL_PRG__PARAM1 is taken from alert message:20.09.2011
04/11/2011 23:14:37,403548
0
Removing DBMS_ALERT for CUTOFF__FILL_PRG__PARAM1
04/11/2011 23:14:37,403763
1
CUTOFF__EXEC1__PARAM1 is taken from alert message:20.09.2011
04/11/2011 23:14:37,542666
1
Removing DBMS_ALERT for CUTOFF__EXEC1__PARAM1
04/11/2011 23:14:37,542877
1
Waiting for DBMS_ALERT CUTOFF__FILL_PRG__FINISHED message in order to start processing...
04/11/2011 23:14:37,544055
0
CUTOFF_INTERIM_TABLE insertion is finished.
04/11/2011 23:14:42,942366
0
Signalling DBMS_ALERT for CUTOFF__FILL_PRG__FINISHED
04/11/2011 23:14:42,942525
1
CUTOFF__FILL_PRG__FINISHED is taken from alert message:FINISHED
04/11/2011 23:14:42,948634
1
Removing DBMS_ALERT for CUTOFF__FILL_PRG__FINISHED
04/11/2011 23:14:42,948759
0
FillInterimTable finished...
04/11/2011 23:14:42,951359
1
ProcessInterimTable finished for process 1
04/11/2011 23:14:54,417806


To rollback everything:
DROP TABLE CUTOFF_PROCESS_EXECUTION_LOG;
DROP TABLE CUTOFF_INTERIM_TABLE;
DROP PROCEDURE CreateProcessExecutionLog;
DROP PROCEDURE FillInterimTable;
DROP PROCEDURE ProcessInterimTable;
DROP PROCEDURE SetJobCutoffParameters;


BEGIN
  DBMS_SCHEDULER.drop_chain(chain_name => 'CUTOFF__CHAIN',force => true);
END;
/
BEGIN
  DBMS_SCHEDULER.purge_log(job_name => 'CUTOFF__CHAIN_JOB');
  DBMS_SCHEDULER.drop_job(job_name => 'CUTOFF__CHAIN_JOB');
END;
/

BEGIN
  DBMS_SCHEDULER.purge_log(job_name => 'CUTOFF__FILL_JOB');
  DBMS_SCHEDULER.drop_job(job_name => 'CUTOFF__FILL_JOB');
  DBMS_SCHEDULER.drop_program(program_name => 'CUTOFF__FILL_PRG');
END;
/

DECLARE
  vn_TotalProcessCount   PLS_INTEGER;
BEGIN
  vn_TotalProcessCount := 5;
  FOR vn_ProcessNumber IN 1..vn_TotalProcessCount LOOP
    DBMS_SCHEDULER.purge_log(job_name        => 'CUTOFF__EXEC' || vn_ProcessNumber || '_JOB');
    DBMS_SCHEDULER.drop_job(job_name         => 'CUTOFF__EXEC' || vn_ProcessNumber || '_JOB');
    DBMS_SCHEDULER.drop_program(program_name => 'CUTOFF__EXEC' || vn_ProcessNumber || '_PRG');
  END LOOP;
END;
/

Useful queries to check Scheduler objects:
SELECT * FROM USER_SCHEDULER_JOBS WHERE JOB_NAME LIKE 'CUTOFF%';
SELECT * FROM USER_SCHEDULER_JOB_ARGS WHERE JOB_NAME LIKE 'CUTOFF%';
SELECT * FROM USER_SCHEDULER_PROGRAMS WHERE PROGRAM_NAME LIKE 'CUTOFF%';
SELECT * FROM USER_SCHEDULER_PROGRAM_ARGS WHERE PROGRAM_NAME LIKE 'CUTOFF%';
SELECT * FROM USER_SCHEDULER_CHAINS WHERE CHAIN_NAME LIKE 'CUTOFF%';
SELECT * FROM USER_SCHEDULER_CHAIN_STEPS WHERE CHAIN_NAME LIKE 'CUTOFF%';
SELECT * FROM USER_SCHEDULER_CHAIN_RULES WHERE CHAIN_NAME LIKE 'CUTOFF%';
SELECT * FROM USER_SCHEDULER_RUNNING_JOBS WHERE JOB_NAME LIKE 'CUTOFF%';
SELECT * FROM USER_SCHEDULER_RUNNING_CHAINS WHERE CHAIN_NAME = 'CUTOFF__CHAIN' order by start_date;
SELECT * FROM USER_SCHEDULER_JOB_LOG WHERE JOB_NAME LIKE 'CUTOFF%' ORDER BY 1 DESC;
SELECT * FROM USER_SCHEDULER_JOB_RUN_DETAILS WHERE JOB_NAME LIKE 'CUTOFF%' ORDER BY log_id DESC;
SELECT * FROM SYS.SCHEDULER$_EVENT_LOG /*WHERE LOG_ID = 333551*/;

No comments: