How to Monitor SQL Server Failed Jobs and Get Alerts via Email

Managing and monitoring SQL Server jobs is crucial for maintaining the health and performance of your SQL Server environment. One of the key aspects is to ensure that failed jobs are promptly reported and addressed. 

Below is a script that creates a report of failed SQL Server jobs and sends it via email. This script can be particularly useful for DBAs to automate the monitoring process.

CREATE TABLE #Failed_Jobs (

    Status VARCHAR(10) NOT NULL,

    [Job Name] VARCHAR(100) NOT NULL,

    [Step ID] VARCHAR(5) NOT NULL,

    [Step Name] VARCHAR(30) NOT NULL,

    [Start Date Time] VARCHAR(30) NOT NULL,

    Message NVARCHAR(MAX) NOT NULL

);

-- Insert failed job details

INSERT INTO #Failed_Jobs

SELECT 

    'FAILED' AS Status, 

    CAST(sj.name AS VARCHAR(100)) AS "Job Name",

    CAST(sjs.step_id AS VARCHAR(5)) AS "Step ID",

    CAST(sjs.step_name AS VARCHAR(30)) AS "Step Name",

    CAST(

        REPLACE(CONVERT(VARCHAR, CONVERT(DATETIME, CONVERT(VARCHAR, sjh.run_date)), 102), '.', '-') + ' ' +

        STUFF(STUFF(RIGHT('000000' + CONVERT(VARCHAR, sjh.run_time), 6), 3, 0, ':'), 6, 0, ':')

    AS VARCHAR(30)) AS "Start Date Time",

    sjh.message AS "Message"

FROM msdb.dbo.sysjobs sj

JOIN msdb.dbo.sysjobsteps sjs ON sj.job_id = sjs.job_id

JOIN msdb.dbo.sysjobhistory sjh ON sj.job_id = sjh.job_id AND sjs.step_id = sjh.step_id

WHERE sjh.run_status <> 1

  AND CAST(sjh.run_date AS FLOAT) * 1000000 + sjh.run_time > 

      CAST(CONVERT(VARCHAR(8), GETDATE() - 1, 112) AS FLOAT) * 1000000 + 70000  -- yesterday at 7 AM

UNION

SELECT 

    'FAILED', 

    CAST(sj.name AS VARCHAR(100)) AS "Job Name",

    'MAIN' AS "Step ID",

    'MAIN' AS "Step Name",

    CAST(

        REPLACE(CONVERT(VARCHAR, CONVERT(DATETIME, CONVERT(VARCHAR, sjh.run_date)), 102), '.', '-') + ' ' +

        STUFF(STUFF(RIGHT('000000' + CONVERT(VARCHAR, sjh.run_time), 6), 3, 0, ':'), 6, 0, ':')

    AS VARCHAR(30)) AS "Start Date Time",

    sjh.message AS "Message"

FROM msdb.dbo.sysjobs sj

JOIN msdb.dbo.sysjobhistory sjh ON sj.job_id = sjh.job_id

WHERE sjh.run_status <> 1 AND sjh.step_id = 0

  AND CAST(sjh.run_date AS FLOAT) * 1000000 + sjh.run_time > 

      CAST(CONVERT(VARCHAR(8), GETDATE() - 1, 112) AS FLOAT) * 1000000 + 70000  -- yesterday at 7 AM

-- Check if there are failed jobs

DECLARE @cnt INT;  

SELECT @cnt = COUNT(1) FROM #Failed_Jobs;    

IF (@cnt > 0)

BEGIN

    DECLARE @strsubject VARCHAR(100);

    SELECT @strsubject = 'Check the following failed jobs on ' + @@SERVERNAME;

    DECLARE @tableHTML NVARCHAR(MAX);

    SET @tableHTML = 

        N'<H1>Failed Jobs Listing - ' + @@SERVERNAME + '</H1>' + 

        N'<table border="1" cellspacing="0" cellpadding="4">' +

        N'<tr><th>Status</th><th>Job Name</th><th>Step ID</th>' +

        N'<th>Step Name</th><th>Start Date</th><th>Message</th></tr>' +

        CAST((

            SELECT 

                N'<tr><td>' + Status + 

                N'</td><td>' + [Job Name] + 

                N'</td><td>' + [Step ID] + 

                N'</td><td>' + [Step Name] + 

                N'</td><td>' + [Start Date Time] + 

                N'</td><td>' + Message + N'</td></tr>'

            FROM #Failed_Jobs

            FOR XML PATH(''), TYPE

        ) AS NVARCHAR(MAX)) + 

        N'</table>';

    EXEC msdb.dbo.sp_send_dbmail

        @from_address = 'BANDARUAJEYUDU@OUTLOOK.com',

        @recipients = 'BANDARUAJEYUDU@OUTLOOK.com',

        @subject = @strsubject,

        @body = @tableHTML,

        @body_format = 'HTML',

        @profile_name = 'SQLMail';

END

-- Drop temp table

DROP TABLE #Failed_Jobs;

By automating the reporting of failed jobs, DBAs can quickly address issues and ensure the smooth operation of SQL Server jobs.

SQL Server Failover Notification via Database Mail

When a failover occurs in a SQL Server Always On Availability Group or a Failover Cluster Instance (FCI), both the SQL Server and SQL Server Agent services restart on the new node. To ensure timely awareness of failovers, we can set up an automated email alert using Database Mail.


Below is a SQL script that sends an email notification whenever a failover happens:

DECLARE @importance AS VARCHAR(6)

DECLARE @body AS NVARCHAR(1000)

DECLARE @Subject  AS NVARCHAR(1000)

DECLARE @InstanceName AS VARCHAR(100)DECLARE @NodeName AS NVARCHAR(100)

DECLARE @recipients VARCHAR(100)

SELECT  @InstanceName =@@SERVERNAME

SELECT  @NodeName = CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS NVARCHAR(200))

SELECT  @Subject  = 'Fail over Happened for Instance '+@InstanceName

SELECT  @body = 'Fail over Happened for Instance '+@InstanceName + '. This instance is currently running on the node '+@NodeName

SELECT  @importance ='High'

EXEC msdb.dbo.sp_send_dbmail

     @profile_name ='sqlmail',

         @recipients ='bajeyudu@SQLDBANOW.com;',

     --@recipients=@recipientsList,

     @subject = @subject ,

     @body = @body,

     @body_format = 'HTML' ,

     @importance=@importance

SQL Server Alert: Trigger to Notify Job Enable/Disable Status Changes

As a SQL Server DBA, keeping track of job status changes is crucial, especially when jobs are disabled or enabled without prior notice. To enhance monitoring, I’ve created a SQL Server Trigger that automatically sends an email notification whenever a job's enabled/disabled status changes.

Here's the complete trigger script:

USE [msdb]
GO

CREATE TRIGGER [dbo].[JobStatusAlert]
   ON [dbo].[sysjobs]
   AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Check if job is enabled/disabled
    DECLARE @MailBody VARCHAR(300)
    
    -- Check if job status is changed (enabled/disabled)
    IF (SELECT TOP 1 CASE WHEN d.enabled = i.enabled THEN 0 ELSE 1 END
        FROM deleted d INNER JOIN inserted i
        ON d.job_id = i.job_id) = 1
    BEGIN
        -- Get session detail and create a message
        SELECT TOP 1 @MailBody = 'Job "'
            + i.name
            + '" is recently '
            + CASE WHEN i.enabled = 0 THEN ' DISABLED ' ELSE ' ENABLED ' END
            + ' by user '
            + login_name
            + ' with session id '
            + CAST(c.session_id AS VARCHAR(3))
            + ' and host name '
            + host_name + ' at '
            + CONVERT(VARCHAR(50), last_request_end_time, 109)
        FROM sys.dm_exec_connections c
        INNER JOIN sys.dm_exec_sessions s ON c.session_id = s.session_id
        CROSS APPLY sys.dm_exec_sql_text(most_recent_sql_handle)
        CROSS APPLY inserted i
        WHERE text LIKE '%exec msdb.dbo.sp_help_job%'
        AND text NOT LIKE '%SELECT c.session_id'
        ORDER BY last_read DESC;
        
        -- Send mail to DBA Team
        EXEC msdb.dbo.sp_send_dbmail
            @recipients='bajeyudu@SQLDBANOW.com', -- Change mail address accordingly
            @subject = 'Job Status Changed at SQLDBANOWDB01 Server',
            @profile_name = 'Sqlmail', -- Change profile name accordingly
            @body = @MailBody;
    END
END
GO