The tables
| Table | Holds | Key |
|---|---|---|
sysjobs | One row per job: name, owner, enabled, category, notification settings | job_id |
sysjobsteps | Steps: command, subsystem, database, flow, retries, proxy, output file | job_id, step_id |
sysschedules | Schedule definitions (can be shared) | schedule_id |
sysjobschedules | Job ↔ schedule link plus next_run_date/time | job_id, schedule_id |
sysjobhistory | Run results; step_id = 0 is the job outcome | instance_id |
sysjobactivity | Current/last run per job per Agent session | session_id, job_id |
syssessions | One row per Agent service start | session_id |
sysjobstepslogs | Step output when "Log to table" is set | step_uid |
syscategories | Job, alert and operator categories | category_id |
sysoperators | Operators (email/pager/net send) | id |
sysalerts | Alerts: severity, message id, performance condition, job to run | id |
sysnotifications | Which operator gets which alert | alert_id, operator_id |
sysproxies, sysproxysubsystem, sysproxylogin | Proxies, allowed subsystems, who can use them | proxy_id |
syscategories ─┐
sysoperators ──┤
sysjobs ──< sysjobsteps >── sysproxies
│ └──< sysjobschedules >── sysschedules
├──< sysjobhistory
└──< sysjobactivity >── syssessions
sysalerts ──< sysnotifications >── sysoperators
Jobs with their steps
SELECT j.name, s.step_id, s.step_name, s.subsystem, s.database_name,
s.on_success_action, s.on_fail_action, s.retry_attempts, s.output_file_name
FROM msdb.dbo.sysjobs j JOIN msdb.dbo.sysjobsteps s ON s.job_id = j.job_id
ORDER BY j.name, s.step_id;
Flow action codes: 1 = quit with success, 2 = quit with failure, 3 = go to next step, 4 = go to step on_*_step_id.
Schedules per job in plain words
sysschedules stores freq_type (1 once, 4 daily, 8 weekly, 16 monthly, 32 monthly relative, 64 on Agent start, 128 when idle), freq_interval (meaning depends on type; for weekly it's a day bitmask: 1 = Sun, 2 = Mon, 4 = Tue ...) and freq_subday_type (1 at time, 2 seconds, 4 minutes, 8 hours).
SELECT j.name, s.name AS schedule, s.freq_type, s.freq_interval,
s.freq_subday_type, s.freq_subday_interval, s.active_start_time,
js.next_run_date, js.next_run_time
FROM msdb.dbo.sysjobs j
JOIN msdb.dbo.sysjobschedules js ON js.job_id = j.job_id
JOIN msdb.dbo.sysschedules s ON s.schedule_id = js.schedule_id;
For readable descriptions, see the schedule report post.
Running right now
SELECT j.name, a.start_execution_date,
DATEDIFF(MINUTE, a.start_execution_date, GETDATE()) AS minutes_running,
a.last_executed_step_id
FROM msdb.dbo.sysjobactivity a
JOIN msdb.dbo.sysjobs j ON j.job_id = a.job_id
WHERE a.session_id = (SELECT MAX(session_id) FROM msdb.dbo.syssessions)
AND a.start_execution_date IS NOT NULL
AND a.stop_execution_date IS NULL;
Always filter on the latest session_id, or you'll see "running" jobs left over from before an Agent restart.
Last outcome of every step
WITH last AS (
SELECT h.*, ROW_NUMBER() OVER (PARTITION BY h.job_id, h.step_id ORDER BY h.instance_id DESC) rn
FROM msdb.dbo.sysjobhistory h)
SELECT j.name, l.step_id, l.step_name, l.run_status, l.run_date, l.run_time, LEFT(l.message, 300) AS message
FROM last l JOIN msdb.dbo.sysjobs j ON j.job_id = l.job_id
WHERE l.rn = 1 ORDER BY j.name, l.step_id;
Alerts and who they notify
SELECT a.name, a.severity, a.message_id, a.enabled, a.delay_between_responses,
o.name AS operator, o.email_address
FROM msdb.dbo.sysalerts a
LEFT JOIN msdb.dbo.sysnotifications n ON n.alert_id = a.id
LEFT JOIN msdb.dbo.sysoperators o ON o.id = n.operator_id;
Caution
Read these tables freely, but change jobs only through the
sp_add_*/sp_update_*procedures. DirectUPDATEs on msdb tables bypass Agent's cache, so Agent may not see the change until it restarts.