====== SQL agent job history ====== The SQL Server agent job history can be hard to use -- the a picture of a job's history and current status is scattered across various ''msdb'' tables, and although information is gathered together in the job history GUI, it's still hard to see when a running step started or to look at several jobs simultaneously. This script gives you a picture of recent and running jobs' steps, times and durations. Note that the script can't read the //currently// executing job step from ''sysjobactivity'', so it tries to infer it based on what happened in the previous step, and if there isn't one assumes the current step is 1. This produces some misleading results when a job is started at a later step which is still running. ;WITH history AS ( -- convenience view of sysjobhistory presenting start/end dates as DATETIME SELECT job_id , step_name -- take step_name directly rather than looking it up later - step_id is mutable... , step_id AS StepSeqNo -- ...so let's call it what it is! , CAST( CAST(run_date AS VARCHAR(8)) + ' ' + SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(6)), 6), 1, 2) + ':' + SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(6)), 6), 3, 2) + ':' + SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(6)), 6), 5, 2) AS DATETIME) AS StartTime , DATEADD ( SECOND , SUBSTRING(RIGHT('000000' + CAST(run_duration AS VARCHAR(6)), 6), 1, 2) * 3600 + SUBSTRING(RIGHT('000000' + CAST(run_duration AS VARCHAR(6)), 6), 3, 2) * 60 + SUBSTRING(RIGHT('000000' + CAST(run_duration AS VARCHAR(6)), 6), 5, 2) , CAST( CAST(run_date AS VARCHAR(8)) + ' ' + SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(6)), 6), 1, 2) + ':' + SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(6)), 6), 3, 2) + ':' + SUBSTRING(RIGHT('000000' + CAST(run_time AS VARCHAR(6)), 6), 5, 2) AS DATETIME) ) AS EndTime , run_status FROM msdb.dbo.sysjobhistory WHERE run_date > CAST(CONVERT(VARCHAR(8), GETDATE() - 28, 112) AS INT) ), running AS ( -- information about running jobs SELECT ja.job_id , ja.start_execution_date AS JobStartTime -- Determine current step. This isn't reported directly, only the *previous* -- step - so we infer the current step based on what happened in the previous. , CASE -- no previous step? This is step 1 WHEN ja.last_executed_step_id IS NULL THEN 1 -- previous step succeeded? on_success_action 3 = "go to next step"; 4 = "go to step n" WHEN h.run_status = 1 AND js.on_success_action = 3 THEN ja.last_executed_step_id + 1 WHEN h.run_status = 1 AND js.on_success_action = 4 THEN js.on_success_step_id -- previous step failed WHEN h.run_status = 0 AND js.on_fail_action = 3 THEN ja.last_executed_step_id + 1 WHEN h.run_status = 0 AND js.on_fail_action = 4 THEN js.on_fail_step_id -- previous step was retried WHEN h.run_status = 2 THEN ja.last_executed_step_id END AS CurrentStep , COALESCE(h.EndTime, ja.start_execution_date) AS CurrentStepStartTime FROM msdb.dbo.sysjobactivity ja LEFT JOIN history h ON ja.job_id = h.job_id AND ja.last_executed_step_id = h.StepSeqNo AND h.StartTime >= ja.start_execution_date LEFT JOIN msdb.dbo.sysjobsteps js ON js.job_id = ja.job_id AND js.step_id = ja.last_executed_step_id -- A step_id based join should be OK here because set of steps stored -- for the job (hopefully!) match the set of steps in the running job. WHERE ja.start_execution_date IS NOT NULL AND ja.job_history_id IS NULL AND ja.session_id = ( SELECT MAX(session_id) FROM msdb.dbo.syssessions ) ), jobs AS ( -- all overarching job run periods with a unique RunId -- finished jobs SELECT job_id , ROW_NUMBER() OVER ( ORDER BY StartTime ) AS RunId , StartTime AS JobStartTime , EndTime AS JobEndTime , run_status AS JobStatus FROM history WHERE StepSeqNo = 0 UNION ALL -- running jobs SELECT job_id , -ROW_NUMBER() OVER ( ORDER BY JobStartTime ) AS RunId -- number down from zero to avoid collisions with completed RunId , JobStartTime , '29991231' , -1 FROM running ), steps AS ( -- all individual step runs with start and end dates -- finished steps SELECT job_id , step_name , StartTime AS StepStartTime , EndTime AS StepEndTime , run_status AS StepStatus FROM history WHERE StepSeqNo > 0 UNION ALL -- running steps SELECT r.job_id , js.step_name , r.CurrentStepStartTime , '29991231' , -1 FROM running r INNER JOIN msdb.dbo.sysjobsteps js -- join to get step_name for running steps ON js.job_id = r.job_id AND js.step_id = r.CurrentStep ) -- put it all together SELECT sj.name AS JobName , j.RunId AS JobRunId , ROW_NUMBER() OVER ( PARTITION BY j.RunId ORDER BY s.StepStartTime ) AS StepSeqNo , s.step_name AS StepName , s.StepStartTime , CASE WHEN s.StepEndTime <= GETDATE() THEN s.StepEndTime END StepEndTime , DATEADD( SECOND , DATEDIFF( SECOND , s.StepStartTime , CASE WHEN s.StepEndTime > GETDATE() THEN GETDATE() ELSE s.StepEndTime END ) , CAST('00:00:00' AS TIME(0)) ) AS StepDuration , CASE s.StepStatus WHEN -1 THEN 'Running' WHEN 1 THEN 'Success' WHEN 0 THEN 'Failed' WHEN 3 THEN 'Cancelled' ELSE CAST(s.StepStatus AS VARCHAR) END AS StepStatus FROM jobs j INNER JOIN steps s ON s.job_id = j.job_id AND s.StepStartTime BETWEEN j.JobStartTime AND j.JobEndTime INNER JOIN msdb.dbo.sysjobs sj ON sj.job_id = j.job_id {{tag> tsql admin housekeeping agent}}