Five Features You Ought to Know About the Oracle Scheduler

By: Eddie Awad Web: Twitter: eddieawad

5 Features You Ought to Know About the Oracle Scheduler Introduction Why DBMS_SCHEDULER and not DBMS_JOB? What about cron? Basic Jobs The Calendar Introduction Examples Start Dates and Repeat Intervals Calendar String Evaluation Resource Manager Introduction The Scheduler and the Resource Manager Example Chains Introduction Privileges Example Manipulating running chains External jobs Introduction privileges Credentials 11g and above Defaults The Remote Scheduler Agent Example On Windows On Unix Events Introduction Raising Events Scheduler Event Queue Event-Based Jobs Example Event-based chaining Sniper Job File Watcher Resources

The Oracle Scheduler is a free job scheduling utility that is included in the Oracle database. You manipulate the Oracle Scheduler through the provided PL/SQL package DBMS_SCHEDULER1.

DBMS_SCHEDULER superseded DBMS_JOB starting with Oracle 10gR1. DBMS_JOB was made available with Oracle 7.2 and although it still exists in 10g and 11g, no new features are being added to it. Oracle recommends you use DBMS_SCHEDULER in releases 10g and up. DBMS_SCHEDULER is more robust and fully-featured than DBMS_JOB. It includes the following features that DBMS_JOB does not have: ● ● ● ● ● ● ● ● ● ● ● ● ● Extensive logging of job runs including logging run history Simple yet very powerful scheduling syntax Running of jobs outside of the database on the operating system Running of jobs on remote machines Resource management between different classes of jobs Use of job arguments including passing of objects into stored procedures Privilege-based security model for jobs Naming of jobs and comments in jobs Stored, reusable schedules Dependencies between job units (Job chains) Event based jobs which run when an event is received E-mail notifications on job events of interest Starting a job based on arrival of a file

Note: Some of the above features were introduced in 11g.

What about cron?
cron is a job scheduling utility that is ubiquitous in Linux/Unix environments. The following are some of the benefits that DBMS_SCHEDULER has over cron, in other words, features that cron does not have: ● ● ● ● ● Can make the execution of a job dependent on the completion of another job Has robust resource balancing and flexible scheduling features Can run jobs based on a database event Has syntax that works the same regardless of the operating system Can run status reports using the data dictionary

Basic Jobs

There are multiple ways you can create Scheduler jobs depending on your environment and the tools available to you. For example, Oracle Enterprise Manager provides a complete front end user interface to DBMS_SCHEDULER and related objects. Oracle SQL Developer and TOAD also provide options to create and maintain scheduler jobs. However, in order to understand the basics and have a solid foundation in Oracle Scheduler you need to get your hands dirty working with the package DBMS_SCHEDULER in PL/SQL. Let’s create a simple job that executes a PL/SQL block. First, we create a new user named john and grant CREATE JOB to him. This privilege is required to be able to create jobs via DBMS_SCHEDULER. Note: Because we are going to use dbms_lock.sleep in our examples below, we need to grant execute on DBMS_LOCK to the user. Connect as a user with DBA privileges and execute: CREATE USER john IDENTIFIED BY john; GRANT CREATE SESSION TO john; GRANT CREATE JOB TO john; GRANT EXECUTE ON DBMS_LOCK TO john; ALTER USER john QUOTA UNLIMITED ON users; Then connect as the user john and execute the following: BEGIN sys.DBMS_SCHEDULER.create_job ( job_name => 'MY_JOB', job_type => 'PLSQL_BLOCK', job_action => 'dbms_lock.sleep(3);', start_date => SYSTIMESTAMP AT TIME ZONE 'US/Pacific', end_date => NULL, repeat_interval => NULL, job_class => 'DEFAULT_JOB_CLASS', comments => 'This is a sample job.', auto_drop => FALSE, enabled => TRUE); END; / As its name implies the procedure create_job creates a new Scheduler job. Because this procedure is overloaded it can be used to create all kinds of jobs. In the example above, it creates a one time job that executes the PL/SQL code in the job_action parameter.

When a Scheduler job is created, a record of it is also created in the data dictionary table [DBA | ALL | USER]_SCHEDULER_JOBS. The following is a list of Scheduler specific data dictionary tables: ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ALL_SCHEDULER_CHAINS: All scheduler chains in the database visible to current user ALL_SCHEDULER_CHAIN_RULES: All rules from scheduler chains visible to the current user ALL_SCHEDULER_CHAIN_STEPS: All steps of scheduler chains visible to the current user ALL_SCHEDULER_CREDENTIALS: All scheduler credentials visible to the user ALL_SCHEDULER_DB_DESTS: User-visible destination objects in the database pointing to remote databases ALL_SCHEDULER_DESTS: All destination objects for jobs in the database visible to current user ALL_SCHEDULER_EXTERNAL_DESTS: User-visible destination objects in the database pointing to remote agents ALL_SCHEDULER_FILE_WATCHERS: Scheduler file watch requests visible to the current user ALL_SCHEDULER_GLOBAL_ATTRIBUTE: All scheduler global attributes ALL_SCHEDULER_GROUPS: All scheduler object groups visible to current user ALL_SCHEDULER_GROUP_MEMBERS: Members of all scheduler object groups visible to current user ALL_SCHEDULER_JOBS: All scheduler jobs visible to the user ALL_SCHEDULER_JOB_ARGS: All arguments with set values of all scheduler jobs in the database ALL_SCHEDULER_JOB_CLASSES: All scheduler classes visible to the user ALL_SCHEDULER_JOB_DESTS: State of all jobs visible to current user at each of their destinations ALL_SCHEDULER_JOB_LOG: Logged information for all scheduler jobs ALL_SCHEDULER_JOB_RUN_DETAILS: The details of a job run ALL_SCHEDULER_NOTIFICATIONS: All job e-mail notifications visible to the current user ALL_SCHEDULER_PROGRAMS: All scheduler programs visible to the user ALL_SCHEDULER_PROGRAM_ARGS: All arguments of all scheduler programs visible to the user ALL_SCHEDULER_REMOTE_DATABASES: ALL_SCHEDULER_REMOTE_JOBSTATE: Remote state of all jobs originating from this database visible to current user ALL_SCHEDULER_RUNNING_CHAINS: All job steps of running job chains visible to the user ALL_SCHEDULER_SCHEDULES: All schedules in the database ALL_SCHEDULER_WINDOWS: All scheduler windows in the database ALL_SCHEDULER_WINDOW_DETAILS: The details of a window ALL_SCHEDULER_WINDOW_GROUPS: All scheduler window groups in the database

● ●

ALL_SCHEDULER_WINDOW_LOG: Logged information for all scheduler windows ALL_SCHEDULER_WINGROUP_MEMBERS: Members of all scheduler window groups in the database

The Calendar
One of the powerful features of the Oracle Scheduler is its calendaring syntax2. This syntax is used whenever you want to define a recurring job or create a named schedule. You control when and how often a job repeats by setting the repeat_interval attribute of the job itself or of the named schedule that the job references. The result of evaluating the repeat_interval is a set of timestamps. The Scheduler runs the job at each timestamp.

Here are some repeat_interval examples3 that demonstrate the versatility and flexibility of the calendaring syntax: Run at 10:00 pm daily from Monday to Friday: FREQ=DAILY; BYDAY=MON,TUE,WED,THU,FRI; BYHOUR=22; BYMINUTE=0; BYSECOND=0; Run every hour: FREQ=HOURLY;INTERVAL=1; Run every 5 minutes: FREQ=MINUTELY;INTERVAL=5; Run every Friday at 9:00 am (All three examples are equivalent): FREQ=DAILY; BYDAY=FRI; BYHOUR=9; BYMINUTE=0; BYSECOND=0; FREQ=WEEKLY; BYDAY=FRI; BYHOUR=9; BYMINUTE=0; BYSECOND=0; FREQ=YEARLY; BYDAY=FRI; BYHOUR=9; BYMINUTE=0; BYSECOND=0; Run every other Friday: FREQ=WEEKLY; INTERVAL=2; BYDAY=FRI; Run on Monday of weeks 5, 10 and 15 every year: FREQ=YEARLY; BYWEEKNO=5,10,15; BYDAY=MON Run on the last day of every month.
2 3

FREQ=MONTHLY; BYMONTHDAY=-1; Run on the next to last day of every month: FREQ=MONTHLY; BYMONTHDAY=-2; Run on March 10th (Both examples are equivalent): FREQ=YEARLY; BYMONTH=MAR; BYMONTHDAY=10; FREQ=YEARLY; BYDATE=0310; Run every January 10, 11, 12, 13 and 14 (Both examples are equivalent): FREQ=YEARLY; BYDATE=0110,0111,0112,0113,0114 FREQ=YEARLY; BYDATE=0110+SPAN:5D; Run every 10 days: FREQ=DAILY; INTERVAL=10; Run daily at 4:15, 5:15, and 6:15PM: FREQ=DAILY; BYHOUR=16,17,18; BYMINUTE=15; BYSECOND=0; Run on the 15th day of every other month: FREQ=MONTHLY; INTERVAL=2; BYMONTHDAY=15; Run on the 29th day of every month: FREQ=MONTHLY; BYMONTHDAY=29; Run on the second Wednesday of each month: FREQ=MONTHLY; BYDAY=2WED; Run on the last Friday of the year: FREQ=YEARLY; BYDAY=-1FRI; Run every 50 hours: FREQ=HOURLY; INTERVAL=50; Run on the last day of every other month: FREQ=MONTHLY; INTERVAL=2; BYMONTHDAY=-1; Run hourly for the first three days of every month: FREQ=HOURLY; BYMONTHDAY=1,2,3; Run on the 60th, 120th and 180th days of the year: FREQ=YEARLY; BYYEARDAY=60,120,180; Run on the last workday of every month (assuming that workdays are Monday through Friday):

FREQ=MONTHLY; BYDAY=MON,TUE,WED,THU,FRI; BYSETPOS=-1 Here are some more complex examples which reference the following named schedules: BEGIN sys.DBMS_SCHEDULER.create_schedule ( schedule_name => 'COMPANY_HOLIDAYS', repeat_interval => 'FREQ=YEARLY; BYDATE=0530,0704,0905,1124,1125,1225;'); sys.DBMS_SCHEDULER.create_schedule ( schedule_name => 'JUL4', repeat_interval => 'FREQ=YEARLY; BYMONTH=JUL; BYMONTHDAY=4;'); sys.DBMS_SCHEDULER.create_schedule ( schedule_name => 'MEM', repeat_interval => 'FREQ=YEARLY; BYMONTH=MAY; BYMONTHDAY=30;'); sys.DBMS_SCHEDULER.create_schedule ( schedule_name => 'LAB', repeat_interval => 'FREQ=YEARLY; BYMONTH=SEP; BYMONTHDAY=5;'); sys.DBMS_SCHEDULER.create_schedule ( schedule_name => 'LAST_SAT', repeat_interval => 'FREQ=MONTHLY; BYDAY=SAT; BYSETPOS=-1;'); sys.DBMS_SCHEDULER.create_schedule ( schedule_name => 'END_QTR', repeat_interval => 'FREQ=YEARLY; BYDATE=0331,0630,0930,1231;'); sys.DBMS_SCHEDULER.create_schedule ( schedule_name => 'FISCAL_YEAR', repeat_interval => 'FREQ=YEARLY;BYDATE=0301,0601,0901,1201;PERIODS=4;'); END; / Run on the last workday of every month, excluding company holidays: FREQ=MONTHLY; BYDAY=MON,TUE,WED,THU,FRI; EXCLUDE=COMPANY_HOLIDAYS; BYSETPOS=-1 Run at noon every Friday and on company holidays: FREQ=YEARLY; BYDAY=FRI;BYHOUR=12; INCLUDE=COMPANY_HOLIDAYS Run on these three holidays: July 4th, Memorial Day, and Labor Day: JUL4,MEM,LAB Run on the last day of the month that is either a Saturday or last day of a quarter: FREQ=MONTHLY; BYMONTHDAY=-1; INTERSECT=LAST_SAT,END_QTR Run on the last Wednesday of every quarter of the fiscal year: FREQ=FISCAL_YEAR;BYDAY=-1WED

Run on the last work day of the 2nd and 4th quarters of the fiscal year (assuming that workdays are Monday through Friday): FREQ=FISCAL_YEAR;BYDAY=MON,TUE,WED,THU,FRI;BYPERIOD=2,4;BYSETPOS=-1

Start Dates and Repeat Intervals
When constructing your calendar expression it is important to note the following about start dates and repeat Intervals4: The Scheduler retrieves the date and time from the start date of the job or named schedule and incorporates them as defaults into the repeat_interval. For example, if the specified frequency is yearly and there is no BYMONTH or BYMONTHDAY clause in the repeat interval, then the month and day that the job runs on are retrieved from the start date. Similarly, if frequency is monthly but there is no BYMONTHDAY clause in the repeat interval, then the day of the month that the job runs on is retrieved from the start date. BYHOUR, BYMINUTE, and BYSECOND defaults are also retrieved from the start date, and used if those clauses are not specified. For example: start_date: 4/15/05 9:20:00 repeat_interval: freq=yearly is expanded internally to: FREQ=YEARLY;BYMONTH=4;BYMONTHDAY=15;BYHOUR=9;BYMINUTE=20;BYSECOND= 0

Calendar String Evaluation
To test the definition of the calendar string without having to actually schedule the job use the very handy procedure DBMS_SCHEDULER.evaluate_calendar_string. This procedure evaluates the calendar expression and tells you what the next execution date and time of a job will be. Here is an example5 of how you could use DBMS_SCHEDULER.evaluate_calendar_string: CREATE OR REPLACE PROCEDURE print_schedule_dates ( schedule_i IN VARCHAR2, start_date_i IN TIMESTAMP WITH TIME ZONE DEFAULT NULL, number_of_dates_i IN PLS_INTEGER DEFAULT 10) AUTHID CURRENT_USER IS l_date_after TIMESTAMP WITH TIME ZONE; l_next_date TIMESTAMP WITH TIME ZONE;
4 5

l_actual_start_date TIMESTAMP WITH TIME ZONE; BEGIN IF start_date_i IS NULL THEN $IF sys.DBMS_DB_VERSION.ver_le_10_1 $THEN l_actual_start_date := SYSTIMESTAMP; $ELSE -- 10gR2 or higher l_actual_start_date := sys.DBMS_SCHEDULER.stime; $END ELSE l_actual_start_date := start_date_i; END IF; l_date_after := l_actual_start_date - INTERVAL '3' SECOND; FOR i IN 1 .. number_of_dates_i LOOP sys.DBMS_SCHEDULER.evaluate_calendar_string (schedule_i, l_actual_start_date, l_date_after, l_next_date); DBMS_OUTPUT.put_line ( TO_CHAR (l_next_date, 'DY YYYY-MM-DD (DDD-IW) HH24:MI:SS TZH:TZM TZR')); l_date_after := l_next_date; END LOOP; END; / You can then call the above procedure passing to it the repeat_interval string and optionally the start date and the number of timestamps you want to be returned. For example: EXEC print_schedule_dates('FREQ=MONTHLY; INTERVAL=2; BYMONTHDAY=15;');

Resource Manager
The Database Resource Manager (Resource Manager) controls how resources are allocated among database sessions. With the Resource Manager, you can6:

● ● ● ● ● ● ● ● ●

Guarantee certain sessions a minimum amount of processing resources regardless of the load on the system and the number of users. Distribute available processing resources by allocating percentages of CPU time to different users and applications. Limit the degree of parallelism of any operation performed by members of a group of users. Manage the order of parallel statements in the parallel statement queue. Limit the number of parallel servers that a group of users can use. Create an active session pool. An active session pool consists of a specified maximum number of user sessions allowed to be concurrently active within a group of users. Prevent the execution of operations that the optimizer estimates will run for a longer time than a specified limit. Limit the amount of time that a session can be idle. Configure an instance to use a particular scheme for allocating resources.

The elements of the Resource Manager are described in the following table. Element Resource consumer group Description A group of sessions that are grouped together based on resource requirements. The Resource Manager allocates resources to resource consumer groups, not to individual sessions. A container for directives that specify how resources are allocated to resource consumer groups. You specify how the database allocates resources by activating a specific resource plan. Associates a resource consumer group with a particular plan and specifies how resources are to be allocated to that resource consumer group.

Resource plan

Resource plan directive

Note: The Resource Manager is active only when CPU utilization approaches 100%. Details about the resource manager are beyond the scope of this paper. However, what does the resource manager have to do with the Oracle Scheduler?

The Scheduler and the Resource Manager
For Scheduler jobs, resources are allocated by first assigning each job to a job class, and then associating a job class with a consumer group. Resources are then distributed among the Scheduler jobs and other sessions within the consumer group. You can also assign relative priorities to the jobs in a job class, and resources are distributed to those jobs accordingly.

You can manually change the current resource plan at any time. Another way to change the current resource plan is by creating Scheduler windows. Windows have a resource plan attribute. When a window opens, the current plan is switched to the window's resource plan. The Scheduler tries to limit the number of jobs that are running simultaneously so that at least some jobs can complete, rather than running a lot of jobs concurrently but without enough resources for any of them to complete. The Scheduler and the Resource Manager are tightly integrated. The job coordinator obtains database resource availability from the Resource Manager. Based on that information, the coordinator determines how many jobs to start. It will only start jobs from those job classes that will have enough resources to run. The coordinator will keep starting jobs in a particular job class that maps to a consumer group until the Resource Manager determines that the maximum resource allocated for that consumer group has been reached. This means that it is possible that there will be jobs in the job table that are ready to run but will not be picked up by the job coordinator because there are no resources to run them. Therefore, there is no guarantee that a job will run at the exact time that it was scheduled. The coordinator picks up jobs from the job table on the basis of which consumer groups still have resources available. The Resource Manager continues to manage the resources that are assigned to each running job based on the specified resource plan. Keep in mind that the Resource Manager can only manage database processes. The active management of resources does not apply to external jobs. 7

The following example can help to understand how resources are allocated for jobs. Assume that the active resource plan is called "Night Plan" and that there are three job classes: JC1, which maps to consumer group DW; JC2, which maps to consumer group OLTP; and JC3, which maps to the default consumer group:

7 2470

This resource plan clearly gives priority to jobs that are part of job class JC1. Consumer group DW gets 60% of the resources, thus jobs that belong to job class JC1 will get 60% of the resources. Consumer group OLTP has 30% of the resources, which implies that jobs in job class JC2 will get 30% of the resources. The consumer group Other specifies that all other consumer groups will be getting 10% of the resources. This means that all jobs that belong in job class JC3 will share 10% of the resources and can get a maximum of 10% of the resources. Note that resources that remain unused by one consumer group are available for use by the other consumer groups. Let’s implement the above example using PL/SQL APIs. First, we create a simple plan. We give the user john the system privilege ADMINISTER_RESOURCE_MANAGER. It is required to allow the user to execute DBMS_RESOURCE_MANAGER.create_simple_plan: BEGIN sys.DBMS_RESOURCE_MANAGER_PRIVS.GRANT_SYSTEM_PRIVILEGE ( grantee_name => 'john', privilege_name => 'ADMINISTER_RESOURCE_MANAGER', admin_option => FALSE); END; / BEGIN sys.DBMS_RESOURCE_MANAGER.create_simple_plan ( simple_plan => 'NIGHT_PLAN', consumer_group1 => 'DW', group1_percent => 60, consumer_group2 => 'OLTP', group2_percent => 30); END; /

Then we create a Scheduler Window and associate it with NIGHT_PLAN. The user needs to have the MANAGE SCHEDULER privilege to be able to create windows: BEGIN sys.DBMS_SCHEDULER.create_window ( window_name => 'NIGHTLY_WINDOW',

resource_plan => 'NIGHT_PLAN', start_date => SYSTIMESTAMP AT TIME ZONE 'America/Los_Angeles', duration => NUMTODSINTERVAL (360, 'minute'), repeat_interval => 'FREQ=DAILY;BYHOUR=21;BYMINUTE=0;BYSECOND=0'); END; / Here is how the plan looks like in OEM:

and the window:

Now, let’s create the job classes and associate each job class with its corresponding consumer group: BEGIN sys.DBMS_SCHEDULER.create_job_class (job_class_name => 'JC1', resource_consumer_group => 'DW'); sys.DBMS_SCHEDULER.create_job_class (job_class_name => 'JC2', resource_consumer_group => 'OLTP'); sys.DBMS_SCHEDULER.create_job_class (job_class_name => 'JC3', resource_consumer_group => 'DEFAULT_CONSUMER_GROUP'); END; / Next, let’s create three jobs and associate each with a job class: BEGIN sys.DBMS_SCHEDULER.create_job ( job_name => 'JOB1', job_class => 'JC1', job_type => 'PLSQL_BLOCK', job_action => 'begin /* Insert PL/SQL code here */ null; end;', repeat_interval => 'FREQ=DAILY;BYHOUR=21', start_date => SYSTIMESTAMP AT TIME ZONE 'America/Los_Angeles', enabled => TRUE); sys.DBMS_SCHEDULER.create_job ( job_name => 'JOB2', job_class => 'JC2', job_type => 'PLSQL_BLOCK', job_action => 'begin /* Insert PL/SQL code here */ null; end;', repeat_interval => 'FREQ=DAILY;BYHOUR=22', start_date => SYSTIMESTAMP AT TIME ZONE 'America/Los_Angeles', enabled => TRUE); sys.DBMS_SCHEDULER.create_job ( job_name => 'JOB3', job_class => 'JC3', job_type => 'PLSQL_BLOCK', job_action => 'begin /* Insert PL/SQL code here */ null; end;', repeat_interval => 'FREQ=DAILY;BYHOUR=23', start_date => SYSTIMESTAMP AT TIME ZONE 'America/Los_Angeles', enabled => TRUE); END; /

The three jobs run within the NIGHTLY_WINDOW during which the NIGHT_PLAN is in effect. Since JOB1 is in job class JC1 and job class JC1 is in consumer group DW, when the job executes, it will not consume more than 60% of CPU. Since JOB2 is in job class JC2 and job class JC2 is in consumer group OLTP, when the job executes, it will not consume more than 30% of CPU. Since JOB3 is in job class JC3 and job class JC3 is in the default consumer group, when the job executes, it will not consume more than 10% of CPU plus whatever resources that remain unused by the other two consumer groups. Note again: The Resource Manager is active only when CPU utilization approaches 100%.

Simple jobs that have no relation to other jobs are scheduled individually. However, if you want to combine multiple logically-connected jobs or tasks, you should build a job chain. A job chain is a named series of tasks that are linked together. Chains are the means by which you can implement dependency based scheduling, in which jobs are started depending on the outcomes of one or more previous jobs. In a job chain that consists of multiple steps, you describe the relationship between these steps with rules. The rules in a job chain define the order in which the job steps are executed. Execution depends on the status of the previous job steps. To create and use a chain, you complete these tasks in order: 1. Create Scheduler program objects for each step of the chain (If steps point to programs) 2. Create a chain object 3. Define the steps in the chain 4. Define the rules for the chain 5. Enable the chain 6. Create a job (the "chain job") that points to the chain

To create a chain in your own schema, you must have the CREATE JOB privilege in addition to the Rules Engine privileges required to create rules, rule sets, and evaluation contexts in your own schema. These can be granted by issuing the following statement:

GRANT CREATE RULE, CREATE RULE SET, CREATE EVALUATION CONTEXT TO user; To create a chain in a different schema, you issue the same GRANTS above but with the CREATE ANY privilege.

Let’s go through an example of creating a chain where my_program1 runs before my_program2 and my_program3. my_program2 and my_program3 run in parallel after my_program1 has completed.8 First, you create program objects to be used later by the steps of the chain. BEGIN sys.DBMS_SCHEDULER.create_program ( program_name => 'my_program1', program_type => 'PLSQL_BLOCK', program_action => 'begin /* Insert your PL/SQL code here */ null; end;', enabled => TRUE); sys.DBMS_SCHEDULER.create_program ( program_name => 'my_program2', program_type => 'PLSQL_BLOCK', program_action => 'begin /* Insert your PL/SQL code here */ null; end;', enabled => TRUE); sys.DBMS_SCHEDULER.create_program ( program_name => 'my_program3', program_type => 'PLSQL_BLOCK', program_action => 'begin /* Insert your PL/SQL code here */ null; end;', enabled => TRUE); END; / You create a chain by using the DBMS_SCHEDULER.CREATE_CHAIN procedure: BEGIN sys.DBMS_SCHEDULER.create_chain (chain_name => 'my_chain1', rule_set_name => NULL, evaluation_interval => NULL, comments => 'My first chain'); END; /
8 N12069

The rule_set_name and evaluation_interval arguments are typically left NULL. rule_set_name refers to a rule set as defined within Oracle Streams. evaluation_interval can define a repeating interval at which chain rules get evaluated. The Scheduler evaluates all chain rules at the start of the chain job and at the end of each chain step. You can configure a chain to also have its rules evaluated at a repeating time interval, such as once per hour. This capability is useful if you want to start chain steps based on time of day or based on occurrences external to the chain. After creating a chain object, you define the chain steps. Each step can point to one of the following: ● ● ● A Scheduler program object (program) Another chain (a nested chain) An event schedule, inline event, or file watcher

You define a step that waits for an event to occur by using the DBMS_SCHEDULER.DEFINE_CHAIN_EVENT_STEP procedure. Procedure arguments can point to an event schedule, can include an inline queue specification and event condition, or can include a file watcher name. You define a step that points to a program or nested chain by using the DBMS_SCHEDULER.DEFINE_CHAIN_STEP procedure. In this example, you’ll point to the programs we created earlier: BEGIN sys.DBMS_SCHEDULER.define_chain_step (chain_name => 'my_chain1', step_name => 'step1', program_name => 'my_program1'); sys.DBMS_SCHEDULER.define_chain_step (chain_name => 'my_chain1', step_name => 'step2', program_name => 'my_program2'); sys.DBMS_SCHEDULER.define_chain_step (chain_name => 'my_chain1', step_name => 'step3', program_name => 'my_program3'); END; / After a database recovery, by default steps that were running are marked as STOPPED and the chain continues. You can specify the chain steps to restart automatically after a database recovery by using DBMS_SCHEDULER.ALTER_CHAIN to set the restart_on_recovery attribute to TRUE for those steps.

You then define chain rules with the DBMS_SCHEDULER.DEFINE_CHAIN_RULE procedure. You call this procedure once for each rule that you want to add to the chain. BEGIN sys.DBMS_SCHEDULER.define_chain_rule (chain_name => 'my_chain1', condition => 'TRUE', action => 'START step1'); sys.DBMS_SCHEDULER.define_chain_rule (chain_name => 'my_chain1', condition => 'step1 COMPLETED', action => 'Start step2, step3'); sys.DBMS_SCHEDULER.define_chain_rule ( chain_name => 'my_chain1', condition => 'step2 COMPLETED AND step3 COMPLETED', action => 'END'); END; / Scheduler chain condition syntax takes one of the following two forms: stepname [NOT] {SUCCEEDED|FAILED|STOPPED|COMPLETED} stepname ERROR_CODE {comparision_operator|[NOT] IN} {integer|list_of_integers} You can combine conditions with boolean operators AND, OR, and NOT() to create conditional expressions. You can employ parentheses in your expressions to determine order of evaluation. ERROR_CODE can be set with the RAISE_APPLICATION_ERROR PL/SQL statement within the program assigned to the step. At least one rule must have a condition that always evaluates to TRUE so that the chain can start when the chain job starts. At least one chain rule must contain an action of 'END'. A chain job does not complete until one of the rules containing the END action evaluates to TRUE. You enable a chain with the DBMS_SCHEDULER.ENABLE procedure: BEGIN sys.DBMS_SCHEDULER.enable (name => 'my_chain1'); END; /

Create a job that is going to run the chain, daily at 1:00 pm in this example: BEGIN sys.DBMS_SCHEDULER.create_job ( job_name => 'chain_job_1', job_type => 'CHAIN', job_action => 'my_chain1', repeat_interval => 'freq=daily;byhour=13;byminute=0;bysecond=0', enabled => TRUE); END; / Here is how the chain looks like in Enterprise Manager:

With a nice topology view:

To run the chain job: BEGIN sys.DBMS_SCHEDULER.run_job (job_name => 'chain_job_1', use_current_session => FALSE); END; / Another option is to use DBMS_SCHEDULER.RUN_CHAIN procedure. It is especially useful if you want to run the chain starting at a specific step. This procedure immediately runs a chain or part of a chain by automatically creating a run-once job for you. When the job is running, check the contents of the user_scheduler_running_jobs and user_scheduler_running_chains views.

Manipulating running chains
DBMS_SCHEDULER.EVALUATE_RUNNING_CHAIN forces reevaluation of the rules of a running chain to trigger any rules for which the conditions have been satisfied. DBMS_SCHEDULER.ALTER_RUNNING_CHAIN alters an attribute of the specified steps of a chain. This affects only steps of the instance of the chain for the specified running chain job. For example you could use this procedure to change the state of a step (in a running chain) to SUCCEEDED.

External jobs

An external job is a scheduler job that runs an operating system executable. The OS executable runs outside the database as some operating system user.9 There are two types of external jobs: local external jobs and remote external jobs. A local external job runs its external executable on the same computer as the database that schedules the job. A remote external job runs its executable on a remote host. The remote host does not need to have an Oracle database; you need only install and register a Scheduler agent. Running remote external jobs via a remote agent is a new feature added to the Scheduler in Oracle Database 11g.

Both the CREATE JOB and CREATE EXTERNAL JOB privileges are required to create local or remote external jobs.

11g and above
Starting in 11g, you can create a credential object which is basically a stored user name and password pair. An external job (local or remote) uses a credential to authenticate itself with the operating system so that it can run. The credential contains a host operating system user name and password. The external executable of the job then runs with this user name and password. To create a credential you call the DBMS_SCHEDULER.CREATE_CREDENTIAL procedure. You can query the *_SCHEDULER_CREDENTIALS views to see a list of credentials in the database. Credential passwords are stored obfuscated, and are not displayed in the *_SCHEDULER_CREDENTIALS views.

When credentials are not present, default credentials are used. In this case, external jobs run as the following operating system user:10 ● On UNIX systems, in releases including and after there is a file $ORACLE_HOME/rdbms/admin/externaljob.ora . All external jobs not in the SYS
9 13359 10

schema and with no credential run as the user and group specified in this file. This should be nobody:nobody by default. On UNIX systems, in releases prior to there was no "externaljob.ora" file. All external jobs not in the SYS schema and with no credential run as the owner and group of the $ORACLE_HOME/bin/extjob file which should be setuid and setgid. By default extjob is owned by nobody:nobody except for oracle-xe where it is owned by oracle:oraclegroup and is not setuid/setgid. On Windows, external jobs not in the SYS schema and with no credential run as the user that the OracleJobScheduler Windows service runs as and must have the "Log on as batch job" Windows privilege. This service must be started before these jobs can run. Note that you must manually enable and start this service. For improved security, Oracle recommends using a named user instead of the Local System account. In all releases (including 11g) on both Windows and UNIX systems, external jobs in the SYS schema without a credential run as the user who installed Oracle Database, usually the oracle user.

In 11g, default credentials are included for compatibility with previous releases of Oracle Database, and may be deprecated in a future release. It is, therefore, best to explicitly assign a credential to every external job.

The Remote Scheduler Agent
To enable remote external jobs to run on a specific remote host, you must install a Scheduler agent on the remote host and register it with the local database. The Oracle Scheduler Agent software can be found on the Oracle Database Gateways disk, which can be downloaded from OTN or My Oracle Support. Installing the remote scheduler agent, preparing the database for remote agent usage and configuring the remote agent are beyond the scope of this paper.11

On Windows
Let’s create a job that creates a new directory on the file system. It executes the DOS built-in command mkdir.12 To execute an external job on Windows, we have to call cmd.exe and have it call our command or script.

11 DHJDH 12 13384

First, we need to enable (Startup type = Automatic or Manual) the OracleJobScheduler<sid> Windows service and start it:

BEGIN DBMS_SCHEDULER.create_credential ( credential_name => 'EDDIE_CREDENTIAL', username => 'eddie', password => 'my_os_password'); END; / BEGIN DBMS_SCHEDULER.create_job ( job_name => 'MKDIR_JOB', job_type => 'EXECUTABLE', number_of_arguments => 3, job_action => '\windows\system32\cmd.exe', enabled => FALSE, auto_drop => TRUE, credential_name => 'EDDIE_CREDENTIAL');

DBMS_SCHEDULER.set_job_argument_value ('mkdir_job', 1, '/c'); DBMS_SCHEDULER.set_job_argument_value ('mkdir_job', 2, 'mkdir'); DBMS_SCHEDULER.set_job_argument_value ( 'mkdir_job', 3, '\temp\extjob_test_dir'); DBMS_SCHEDULER.enable ('MKDIR_JOB'); END; / When the above job is executed, we end up with a new directory named extjob_test_dir in c:\temp.

On Unix
Let’s create a job that retrieves the list of files in a given directory.

BEGIN DBMS_SCHEDULER.create_credential ('ORACLE_CREDENTIAL', 'oracle', 'oracle'); END; / BEGIN DBMS_SCHEDULER.create_job ( job_name => 'LSDIR', job_type => 'EXECUTABLE', job_action => '/bin/ls', number_of_arguments => 1, enabled => FALSE, auto_drop => TRUE, credential_name => 'ORACLE_CREDENTIAL'); DBMS_SCHEDULER.set_job_argument_value ('LSDIR', 1, '/tmp'); DBMS_SCHEDULER.enable ('LSDIR'); END; / After the job executes, you can check its run details using this query: SELECT log_id, job_name, status, error#,
13 13093

actual_start_date, additional_info FROM user_scheduler_job_run_details WHERE job_name = 'LSDIR'; For local external jobs, stdout and stderr output files are stored in a log file in ORACLE_HOME/scheduler/log. In the user_scheduler_job_run_details.additional_info column, we get EXTERNAL_LOG_ID="job_79314_266"... The corresponding log file that is generated has the format job_79314_266_stdout or job_79314_266_stderr depending if the job finished successfully or failed with an error. In our example, the job finished successfully. It generated a log file named job_79314_266_stdout in ORACLE_HOME/scheduler/log. We can log in to the server and cd to ORACLE_HOME/scheduler/log and view the file there. Another option is to use the get_file procedure in dbms_scheduler. We use the EXTERNAL_LOG_ID from the additional_info column to formulate the log file name and retrieve the output. It is not necessary to supply the full source_file path to get_file. In the get_file call, we need to specify a credential that specifies the same operating system account as the one that we used for the job. SET SERVEROUT ON DECLARE my_clob CLOB; log_id VARCHAR2 (50); BEGIN SELECT REGEXP_SUBSTR (additional_info, 'job[_0-9]*') INTO log_id FROM user_scheduler_job_run_details WHERE job_name = 'LSDIR'; DBMS_LOB.createtemporary (my_clob, FALSE); DBMS_SCHEDULER.get_file ( source_file => log_id || '_stdout', credential_name => 'ORACLE_CREDENTIAL', file_contents => my_clob, source_host => NULL); DBMS_OUTPUT.put_line (my_clob); END;


In addition to time-based scheduling, i.e. trigger jobs based on some kind of calendar, the Oracle scheduler also has the capability to do event-based scheduling, i.e. trigger jobs based on real-time events. Moreover, a job can raise events, for example, when it ends or when it does not end within the expected time.

Raising Events
You can set up a job so that the Scheduler raises an event when the job changes state. Here is the list of event types raised by the Scheduler:14 Event Type Description Not an event, but a constant that provides an easy way for you to enable all events The job has been disabled and has changed to the BROKEN state because it exceeded the number of failures defined by the max_failures job attribute A job running a chain was put into the CHAIN_STALLED state. A running chain becomes stalled if there are no steps running or scheduled to run and the chainevaluation_interval is set to NULL. No progress will be made in the chain unless there is manual intervention. The job completed because it reached its max_runs or end_date The job was disabled by the Scheduler or by a call to

job_all_events job_broken


job_completed job_disabled job_failed job_over_max_dur

The job failed, either by throwing an error or by abnormally terminating The job exceeded the maximum run duration specified by its max_run_duration attribute. (Note: you do not need to enable this event with the raise_eventsjob attribute; it is always enabled.)

14 C

job_run_completed job_sch_lim_reached

A job run either failed, succeeded, or was stopped The job's schedule limit was reached. The job was not started because the delay in starting the job exceeded the value of the schedule_limit job attribute. The job started The job was stopped by a call to STOP_JOB The job completed successfully

job_started job_stopped job_succeeded

Each event type has a DBMS_SCHEDULER package constant corresponding to it. You instruct the job to raise an event by setting its raise_events attribute. You must first create the job and then alter the job with the SET_ATTRIBUTE procedure. For example: BEGIN DBMS_SCHEDULER.SET_ATTRIBUTE( 'my_job', 'raise_events', DBMS_SCHEDULER.JOB_FAILED + DBMS_SCHEDULER.JOB_SCH_LIM_REACHED); END; / After you enable job state change events for a job, the Scheduler raises these events by enqueuing messages onto the Scheduler event queue SYS.SCHEDULER$_EVENT_QUEUE. Events raised by the Scheduler expire (deleted from the queue) in 24 hours by default. You can change this expiry time by setting the event_expiry_time Scheduler attribute with the SET_SCHEDULER_ATTRIBUTE procedure.

Scheduler Event Queue
The Scheduler event queue SYS.SCHEDULER$_EVENT_QUEUE is of type scheduler$_event_info. Here is the description of its attributes: Attribute event_type Description One of "JOB_STARTED", "JOB_SUCCEEDED", "JOB_FAILED", "JOB_BROKEN", "JOB_COMPLETED", "JOB_STOPPED", "JOB_SCH_LIM_REACHED", "JOB_DISABLED", "JOB_CHAIN_STALLED", "JOB_OVER_MAX_DUR". Owner of the job that raised the event.


object_name event_timestamp error_code error_msg event_status

Name of the job that raised the event. Time at which the event occurred. Applicable only when an error is thrown during job execution. Contains the top-level error code. Applicable only when an error is thrown during job execution. Contains the entire error stack. Adds further qualification to the event type. If event_type is "JOB_STARTED," a status of 1 indicates that it is a normal start, and a status of 2 indicates that it is a retry. If event_type is "JOB_FAILED," a status of 4 indicates that it was a failure due to an error that was thrown during job execution, and a status of 8 indicates that it was an abnormal termination of some kind. If event_type is "JOB_STOPPED," a status of 16 indicates that it was a normal stop, and a status of 32 indicates that it was a stop with the FORCE option set to TRUE. Points to the ID in the scheduler job log from which additional information can be obtained. Note that there need not always be a log entry corresponding to an event. In such cases, log_id is NULL. Run count for the job when the event was raised. Failure count for the job when the event was raised. Retry count for the job when the event was raised.


run_count failure_count retry_count

Event-Based Jobs
A job started based on an event is called an event-based job. To create an event-based job, you must set two attributes: 1. queue_spec: includes the name of the queue where your application enqueues messages to raise job start events, or in the case of a secure queue, the queue name followed by a comma and the agent name. 1. event_condition: A conditional expression based on message properties that must evaluate to TRUE for the message to start the job. The expression must have the syntax of an Oracle Streams Advanced Queuing rule.

Event-based Chaining
We have the option to trigger the execution of a job based on an event raised by another job. Here is a simple example of how to do that. We create a job, MY_JOB, that raises the

job_succeeded event. We also create an event-based job, MY_JOB_CONSUMER, that consumes event messages from sys.scheduler$_event_queue, but only for job_succeeded events raised by MY_JOB. First we let the user subscribe to the Scheduler event queue using the ADD_EVENT_QUEUE_SUBSCRIBER procedure. “my_agent”, or whatever name we use, is the name of the Oracle Streams Advanced Queuing (AQ) agent to be used to subscribe to the Scheduler event queue. (If it is NULL, an agent is created whose name is the user name of the calling user.) This call both creates a subscription to the Scheduler event queue and grants the user permission to dequeue using the designated agent. The subscription is rule-based. The rule permits the user to see only events raised by jobs that the user owns, and filters out all other messages. DECLARE agent_already_subscribed_exp EXCEPTION; PRAGMA EXCEPTION_INIT (agent_already_subscribed_exp, -24034); BEGIN sys.DBMS_SCHEDULER.add_event_queue_subscriber ('my_agent'); EXCEPTION WHEN agent_already_subscribed_exp THEN NULL; END; Then we create MY_JOB_CONSUMER: BEGIN sys.DBMS_SCHEDULER.create_job ( job_name => 'MY_JOB_CONSUMER', job_type => 'PLSQL_BLOCK', job_action => '/* Add you code here */ NULL;', event_condition => 'tab.user_data.event_type = ''JOB_SUCCEEDED'' and tab.user_data.object_name = ''MY_JOB''', queue_spec => 'sys.scheduler$_event_queue,my_agent', enabled => TRUE); END; / Now, MY_JOB_CONSUMER is waiting for a message to arrive in sys.scheduler$_event_queue. It will dequeue it only if the condition in event_condition is true. We create a one time job, MY_JOB, set its raise_events attribute to job_succeeded and enable it:

BEGIN sys.DBMS_SCHEDULER.create_job (job_name => 'MY_JOB', job_type => 'PLSQL_BLOCK', job_action => '/* Add you code here */ NULL;', start_date => SYSTIMESTAMP AT TIME ZONE 'US/Pacific', auto_drop => TRUE, enabled => FALSE);

DBMS_SCHEDULER.set_attribute ('MY_JOB', 'raise_events', DBMS_SCHEDULER.job_succeeded); sys.DBMS_SCHEDULER.enable ('MY_JOB'); END; / When MY_JOB executes, it will enqueue a message in sys.scheduler$_event_queue. This will trigger MY_JOB_CONSUMER to start executing.

Sniper Job
To implement timeouts in the Oracle Scheduler, a "sniper" event-based job needs to be created. The job listens to an event raised by a job that has its timeout set (max_run_duration attribute). A “sniper” procedure is then invoked to stop the job that timed out. First we let the user subscribe to the Scheduler event queue using the ADD_EVENT_QUEUE_SUBSCRIBER procedure (See previous example). Next we create the sniper stored procedure: CREATE OR REPLACE PROCEDURE sniper (message_i IN SYS.scheduler$_event_info) AS BEGIN IF message_i.event_type = 'JOB_OVER_MAX_DUR' THEN sys.DBMS_SCHEDULER.stop_job ( '"' || message_i.object_owner || '"."' || message_i.object_name || '"'); END IF; END sniper;

Next, we create a Scheduler program for the sniper stored procedure we have just created. BEGIN sys.DBMS_SCHEDULER.create_program ( program_name => 'sniper_p', program_action => 'util_pkg.sniper', program_type => 'stored_procedure', number_of_arguments => 1, enabled => FALSE); sys.DBMS_SCHEDULER.define_metadata_argument ( 'sniper_p', 'event_message', 1); sys.DBMS_SCHEDULER.enable ('sniper_p'); END; / Next, we create the sniper event-based job. BEGIN sys.DBMS_SCHEDULER.create_job ( job_name => 'sniper_j', program_name => 'sniper_p', event_condition => 'tab.user_data.event_type = ''JOB_OVER_MAX_DUR''', queue_spec => 'sys.scheduler$_event_queue,my_agent', enabled => TRUE); END; / That’s it. To test the sniper job, we create a job that executes for 80 seconds and we set its timeout to 60 seconds. BEGIN sys.DBMS_SCHEDULER.create_job ( job_name => 'MY_JOB', job_type => 'PLSQL_BLOCK', job_action => 'dbms_lock.sleep(80);', start_date => SYSTIMESTAMP AT TIME ZONE 'US/Pacific', auto_drop => TRUE, enabled => FALSE); sys.DBMS_SCHEDULER.set_attribute (

'MY_JOB', 'max_run_duration', INTERVAL '60' SECOND); sys.DBMS_SCHEDULER.enable ('MY_JOB'); END; / You can see the action by querying the tables user_scheduler_running_jobs and user_scheduler_job_run_details. The above method is proven to work great except for one caveat. If more than one job are timing out *at the same time*, only *one* job will be stopped by the sniper. This is a Scheduler limitation in Oracle database 10g. However, in 11gR2 this limitation has been lifted with the introduction of a new job attribute: parallel_instances. If TRUE, then an instance of the (sniper) job is started for every instance of the event, and each job instance is a lightweight job so multiple instances of the same event-based job can run in parallel.

File Watcher
A file watcher is a Scheduler object that defines the location, name, and other properties of a file whose arrival on a system causes the Scheduler to start a job. You create a file watcher and then create any number of event-based jobs or event schedules that reference the file watcher. When the file watcher detects the arrival of the designated file, it raises a file arrival event. The job started by the file arrival event can retrieve the event message to learn about the newly arrived file. The message contains the information required to find the file, open it, and process it.15 Let’s see a file watcher in action. As soon as an image file is deposited into a directory on the database server we want a Scheduler job to be triggered. The job will read the image file and save it into a BLOB column in a table. The image files will be deposited in this directory on the database server: CREATE OR REPLACE DIRECTORY blob_dir AS '/home/oracle/blob'; Create a credential to be used by the file watcher: BEGIN DBMS_SCHEDULER.create_credential ('oracle_credential', 'oracle', 'oracle'); END; / Create a table to store the information about and content of the processed file:
15 13285

CREATE TABLE images ( file_name VARCHAR2 (100), file_size NUMBER, file_content BLOB, uploaded_on TIMESTAMP ); Create the procedure that will process the file and store its content in the table. scheduler_filewatcher_result is the data type of a file arrival event message.16 CREATE OR REPLACE PROCEDURE process_image_files ( payload IN SYS.scheduler_filewatcher_result) IS l_blob BLOB; l_bfile BFILE; BEGIN INSERT INTO images ( file_name, file_size, file_content, uploaded_on) VALUES ( payload.directory_path || '/' || payload.actual_file_name, payload.file_size, EMPTY_BLOB (), payload.file_timestamp) RETURNING file_content INTO l_blob; l_bfile := BFILENAME ('BLOB_DIR', payload.actual_file_name); (l_bfile, DBMS_LOB.lob_readonly); (l_blob, DBMS_LOB.lob_readwrite); DBMS_LOB.loadfromfile ( dest_lob => l_blob, src_lob => l_bfile, amount => DBMS_LOB.getlength (l_bfile)); DBMS_LOB.close (l_blob); DBMS_LOB.close (l_bfile); END process_image_files; /
16 G

Create the program object that maps to the procedure we just created. Also create its event_message metadata argument: BEGIN DBMS_SCHEDULER.create_program ( program_name => 'image_watcher_p', program_type => 'stored_procedure', program_action => 'process_image_files', number_of_arguments => 1, enabled => FALSE); DBMS_SCHEDULER.define_metadata_argument ( program_name => 'image_watcher_p', metadata_attribute => 'event_message', argument_position => 1); DBMS_SCHEDULER.enable ('image_watcher_p'); END; / Now, create the file watcher object. We are only interested in .jpg files: BEGIN DBMS_SCHEDULER.create_file_watcher ( file_watcher_name => 'image_watcher_fw', directory_path => '/home/oracle/blob', file_name => '*.jpg', credential_name => 'oracle_credential', destination => NULL, enabled => FALSE); END; / Create an event-based job that references the file watcher (as indicated in the queue_spec). We are only interested in files that have a size greater than zero (as indicated by the event_condition). We set parallel_instances to true because we want the job to run for each instance of the file arrival event, even if the job is already processing a previous event. In other words, we want the job to have the ability to process multiple files at once: BEGIN DBMS_SCHEDULER.create_job ( job_name => 'image_load_j', program_name => 'image_watcher_p', event_condition => 'tab.user_data.file_size > 0', queue_spec => 'image_watcher_fw',

auto_drop enabled

=> FALSE, => FALSE);

DBMS_SCHEDULER.set_attribute ('image_load_j', 'parallel_instances', TRUE); END; / Enable the file watcher and the job: BEGIN DBMS_SCHEDULER.enable ('image_watcher_fw,image_load_j'); END; / To test the file watcher, save a couple of jpg images to /home/oracle/blob. Wait about 10 minutes, and boom! the images are saved into the database.

The saved image as shown in Oracle SQL Developer.

File watchers check for the arrival of files every 10 minutes by default. To change this interval: Connect to the database as the SYS user and change the REPEAT_INTERVAL attribute of the predefined schedule SYS.FILE_WATCHER_SCHEDULE. For example, to change it to two minutes: BEGIN DBMS_SCHEDULER.SET_ATTRIBUTE( 'FILE_WATCHER_SCHEDULE', 'REPEAT_INTERVAL', 'FREQ=MINUTELY;INTERVAL=2'); END; / You can view information about file watchers by querying the views *_SCHEDULER_FILE_WATCHERS.

● ● ● ● PL/SQL Packages and Types Reference (11.2) - DBMS_SCHEDULER Administrator's Guide (11.2) - Scheduling Jobs with Oracle Scheduler Oracle Scheduler Forum: Book: Mastering Oracle Scheduler in Oracle 11g Databases:

Sign up to vote on this title
UsefulNot useful