A server can accumulate scheduled jobs faster than its operational notes are updated. Documenting SQL Agent jobs in one consistent inventory exposes who owns them, what they execute, and when they are expected to run.

Define the Grain Before Documenting SQL Agent Jobs
A job can have multiple steps and multiple schedules. Joining them produces one row for each step-and-schedule combination, rather than one row per job. That duplication is deliberate in the main inventory below. Keep job_id, step_id, and schedule_id so a consumer can group the repeated fields correctly.
I describe that grain before sending an inventory to an operations owner. Otherwise repeated job names can look like duplicate jobs, and a summed duration can count the same last run several times. The query documents configuration and retained history, not the amount of unique work performed across the displayed rows.
Run the read-only inventory in msdb with an identity allowed to see the intended job population. Agent role permissions and metadata visibility affect coverage. An inventory from a restricted login should state its scope rather than claim to describe every job on the instance.
Collect Owners, Steps, Schedules, and Last Outcomes
The main query keeps disabled jobs, jobs without schedules, and jobs without retained overall history visible. OUTER APPLY selects the latest job-summary history row, where step_id is zero. Step rows expose their execution subsystem, database context, and command instead of relying on a descriptive step name.
USE msdb;
GO
SELECT j.job_id,j.name AS JobName,SUSER_SNAME(j.owner_sid) AS OwnerName,
j.enabled AS JobEnabled,cat.name AS CategoryName,
st.step_id,st.step_name,st.subsystem,st.database_name,st.command,
sc.schedule_id,sc.name AS ScheduleName,sc.enabled AS ScheduleEnabled,
CASE sc.freq_type
WHEN 1 THEN N'One time'
WHEN 4 THEN CONCAT(N'Every ',sc.freq_interval,N' day(s)')
WHEN 8 THEN CONCAT(N'Every ',sc.freq_recurrence_factor,N' week(s): ',
CASE WHEN sc.freq_interval&1=1 THEN N'Sun ' ELSE N'' END,
CASE WHEN sc.freq_interval&2=2 THEN N'Mon ' ELSE N'' END,
CASE WHEN sc.freq_interval&4=4 THEN N'Tue ' ELSE N'' END,
CASE WHEN sc.freq_interval&8=8 THEN N'Wed ' ELSE N'' END,
CASE WHEN sc.freq_interval&16=16 THEN N'Thu ' ELSE N'' END,
CASE WHEN sc.freq_interval&32=32 THEN N'Fri ' ELSE N'' END,
CASE WHEN sc.freq_interval&64=64 THEN N'Sat ' ELSE N'' END)
WHEN 16 THEN CONCAT(N'Day ',sc.freq_interval,N' every ',
sc.freq_recurrence_factor,N' month(s)')
WHEN 32 THEN CONCAT(
CASE sc.freq_relative_interval
WHEN 1 THEN N'First ' WHEN 2 THEN N'Second '
WHEN 4 THEN N'Third ' WHEN 8 THEN N'Fourth '
WHEN 16 THEN N'Last ' ELSE N'Unknown position ' END,
CASE sc.freq_interval
WHEN 1 THEN N'Sunday' WHEN 2 THEN N'Monday'
WHEN 3 THEN N'Tuesday' WHEN 4 THEN N'Wednesday'
WHEN 5 THEN N'Thursday' WHEN 6 THEN N'Friday'
WHEN 7 THEN N'Saturday' WHEN 8 THEN N'day'
WHEN 9 THEN N'weekday' WHEN 10 THEN N'weekend day'
ELSE N'unknown day' END,
N' every ',sc.freq_recurrence_factor,N' month(s)')
WHEN 64 THEN N'At Agent startup'
WHEN 128 THEN N'When the computer is idle'
ELSE N'No recognized attached schedule' END AS FrequencyText,
CASE sc.freq_subday_type
WHEN 1 THEN N'At the start time'
WHEN 2 THEN CONCAT(N'Every ',sc.freq_subday_interval,N' second(s)')
WHEN 4 THEN CONCAT(N'Every ',sc.freq_subday_interval,N' minute(s)')
WHEN 8 THEN CONCAT(N'Every ',sc.freq_subday_interval,N' hour(s)')
ELSE N'Not applicable' END AS WithinDayText,
STUFF(STUFF(RIGHT(N'000000'+CONVERT(nvarchar(6),sc.active_start_time),6),
3,0,N':'),6,0,N':') AS ActiveStartTime,
STUFF(STUFF(RIGHT(N'000000'+CONVERT(nvarchar(6),sc.active_end_time),6),
3,0,N':'),6,0,N':') AS ActiveEndTime,
sc.active_start_date,sc.active_end_date,
h.run_date AS LastRunDate,h.run_time AS LastRunTime,
CASE h.run_status
WHEN 0 THEN N'Failed' WHEN 1 THEN N'Succeeded'
WHEN 2 THEN N'Retry' WHEN 3 THEN N'Canceled'
WHEN 4 THEN N'In progress' ELSE N'No retained overall outcome'
END AS LastOutcome,
(h.run_duration/10000)*3600
+ ((h.run_duration%10000)/100)*60
+ h.run_duration%100 AS LastDurationSeconds,h.message AS LastMessage
FROM dbo.sysjobs AS j
LEFT JOIN dbo.syscategories AS cat ON cat.category_id=j.category_id
LEFT JOIN dbo.sysjobsteps AS st ON st.job_id=j.job_id
LEFT JOIN dbo.sysjobschedules AS js ON js.job_id=j.job_id
LEFT JOIN dbo.sysschedules AS sc ON sc.schedule_id=js.schedule_id
OUTER APPLY
(
SELECT TOP (1) x.run_date,x.run_time,x.run_status,x.run_duration,x.message
FROM dbo.sysjobhistory AS x
WHERE x.job_id=j.job_id AND x.step_id=0
ORDER BY x.instance_id DESC
) AS h
ORDER BY j.name,st.step_id,sc.schedule_id;Keep the raw date values with the readable frequency. Agent date integers use a year-month-day representation, while run_time encodes hour, minute, and second. The duration conversion handles durations whose hour component exceeds twenty-four instead of trying to cast them to a clock time.
Explain Schedule State Without Promising Execution
A job and its attached schedule have independent enabled states. Both matter, but they are still configuration rather than proof that a run happened. Manual starts, startup schedules, idle schedules, and external orchestration can create execution patterns beyond a simple daily calendar interpretation.
Weekly frequency uses a bit mask for the selected days. Monthly-relative frequency combines an ordinal position with a day category. Preserve those decoded details so a phrase such as monthly does not conceal whether the job means the first weekday or the fifteenth calendar day.
If you add next-run values from sysjobschedules, document that this table refreshes periodically, typically every twenty minutes. They are not a real-time promise. A schedule can be edited while the cached prediction is waiting to refresh, so inspect current definitions and actual Agent state when investigating a missed run.
Interpret the Last Outcome Within Its History Window
The latest retained summary describes the latest overall outcome visible in history. No summary can mean history was purged, the job has no qualifying retained completion, or your review lacks access. Label it as unavailable evidence rather than asserting that the job has never run.
A successful outcome also needs interpretation. A job can be configured to finish successfully after a failed step or to route around expected errors. Review step success and failure actions for important jobs. The last summary is a useful starting point, but the step flow decides what that summary means.
I inspect recent repeated failures separately from the last outcome. One successful retry can make the latest summary look healthy while hiding a recurring operational problem. Retain the relevant historical interval and include the notification policy with the documentation for jobs whose timely execution matters.

Flag Owners That Need Investigation
Compare the owner SID with the current server principal catalog and flag disabled or unresolved owners. This is a candidate review, not a reliable directory-employee status check. Windows and group-based identities require validation through the organization's accepted identity process as well.
SELECT j.name AS JobName,j.owner_sid,p.name AS OwnerName,p.is_disabled,
CASE WHEN p.principal_id IS NULL THEN N'Owner not resolved locally'
WHEN p.is_disabled=1 THEN N'Owner login disabled' END AS ReviewReason
FROM msdb.dbo.sysjobs AS j
LEFT JOIN sys.server_principals AS p ON p.sid=j.owner_sid
WHERE p.principal_id IS NULL OR p.is_disabled=1;Confirm departed owners with the responsible team and assign an accepted operational identity through a reviewed change. Different step subsystems and proxies can have different execution contexts, so an ownership change needs a real execution-permission test. Do not simply move every job to one highly privileged login because it makes the report quiet.
Documenting SQL Agent Jobs With Full Command Text
Export the accepted result to a local text or CSV file using SSMS's supported result-saving options, then organize it into a review document grouped by job. Include capture time, instance identity, permission scope, and row grain. Keep the raw export beside the readable document when the review needs exact command evidence.
Check the result settings for maximum displayed text length before exporting commands. A truncated command is not complete documentation, even when the visible portion looks plausible. Preserve line breaks and quoting, and confirm that the exported command matches the catalog value for representative long steps.
Job commands can contain sensitive connection details or operational data. Restrict the document to its accepted audience and avoid publishing raw commands as a casual inventory. This article supplies the method; the real job export still needs the owner's information-handling rules.
Add the Purpose That Metadata Cannot Infer
A command reveals implementation but not always business purpose. Ask each owner for the reason the job exists, its expected input and output, its failure impact, and the accepted recovery action. Record dependencies on other jobs or external readiness conditions that are not represented by an attached SQL schedule.
Which downstream process notices if this job does not finish? Include that answer and the escalation destination in the document. A job named DailyTask has admirable confidence and very little explanatory value. Keep the business purpose separate from the technical step text so both remain useful when implementation changes.
Keep Documenting SQL Agent Jobs as Configuration Changes
Documenting SQL Agent jobs works best as a repeated operational review rather than a one-time export. Refresh after ownership changes, schedule edits, and important deployments. Retain dated snapshots so a future incident can distinguish current configuration from the configuration that applied at the time.
Use the main query as the consistent collection boundary, then verify exceptions with owners. Documenting SQL Agent jobs is complete when the inventory's scope, current commands, schedule interpretation, and responsible people agree, rather than when a large spreadsheet happens to exist.
Related reading on this blog: T-SQL Script to Check SQL Server Job History and Displaying SQL Agent Jobs Running at a Specific Time.

A job inventory is not an ownership decision, it is evidence that needs an accepted purpose and responsible operator beside it.
Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.





2 Comments. Leave new
I am using server 2012 but getting error while creating tables and error is This backend version is not supported to design database diagrams or tables. (MS Visual Database Tools)
Thanks Pinal Dave! This post was very useful. You are a true inspiration.