/*============================================================================= 

  Création d'un package pour la gestion du monitoring des status des Jobs SQL

  -> Création des tables
  -> Création des FK et index
  -> Deploy de la SP d'initialisation du status des jobs : mon.Initialize_Jobs_Status
  -> Deploy de la SP de mise à jour du status des jobs : mon.Maj_Jobs_Status
  -> Deploy de la fonction du calcul de prochain schedule : GetNextScheduleForJob
  
  Création : 13.07.2023 / FLA
 
  Modifications : 
  17.08.2023 / FLA : Changement de methode d'update, changement du calcul du prochain schedule, add SP to check status
  21.09.2023 / FLA : Improve performance for function GetNextScheduleForJob and for SP Maj_Jobs_Status
				  

=============================================================================*/




/***********************************************************************/
/*                         STRUCTURE                                   */
/*                                                                     */
/***********************************************************************/
USE HCITools;

IF NOT EXISTS (   SELECT *
                FROM sys.objects
               WHERE object_id = OBJECT_ID(N'[mon].[Job_monitoring_config]')
                 AND type IN ( N'U' ))
BEGIN

       CREATE TABLE [mon].[Job_monitoring_config](
					[Job_monitoring_config_ID] [INT] IDENTITY(1,1) NOT NULL,
					[JMC_name] [VARCHAR](255) NOT NULL,
					[JMC_need_history] [BIT] NOT NULL,
					[JMC_caller] [VARCHAR](255) NULL,
					[JMC_ignore] [BIT] NOT NULL,
	     CONSTRAINT [PK_Job_monitoring_config] PRIMARY KEY CLUSTERED 
	     (
		     [Job_monitoring_config_ID] ASC
	     )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
	   ) ON [PRIMARY]
END

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[mon].[DF_JMC_need_history]') AND type = 'D')
BEGIN
ALTER TABLE [mon].[Job_monitoring_config] ADD CONSTRAINT DF_JMC_need_history DEFAULT (1) FOR [JMC_need_history]
END
GO

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[mon].[DF_JMC_ignore]') AND type = 'D')
BEGIN
ALTER TABLE [mon].[Job_monitoring_config] ADD CONSTRAINT DF_JMC_ignore DEFAULT (0) FOR [JMC_ignore]
END
GO


IF NOT EXISTS (SELECT * FROM ::fn_listextendedproperty ('MS_Description', 'Schema', 'mon', 'Table', 'Job_monitoring_config', 'Column', 'JMC_need_history'))
BEGIN
  EXEC sp_addextendedproperty 'MS_Description', '0: same row updated in table Job_monitoring_history, 1:one row for each execution of job', 'Schema', [mon], 'table', [Job_monitoring_config], 'column', [JMC_need_history]
END
GO

IF NOT EXISTS (SELECT * FROM ::fn_listextendedproperty ('MS_Description', 'Schema', 'mon', 'Table', 'Job_monitoring_config', 'Column', 'JMC_caller'))
BEGIN
  EXEC sp_addextendedproperty 'MS_Description', 'Name of job If job called by another one', 'Schema', [mon], 'table', [Job_monitoring_config], 'column', [JMC_caller]
END
GO

IF NOT EXISTS (SELECT * FROM ::fn_listextendedproperty ('MS_Description', 'Schema', 'mon', 'Table', 'Job_monitoring_config', 'Column', 'JMC_ignore'))
BEGIN
  EXEC sp_addextendedproperty 'MS_Description', '0: To monitor, 1:To don''t monitor', 'Schema', [mon], 'table', [Job_monitoring_config], 'column', [JMC_ignore]
END
GO

IF NOT EXISTS (   SELECT 1
						FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
					   WHERE CONSTRAINT_TYPE = 'PRIMARY KEY'
						 AND TABLE_NAME      = N'Job_monitoring_config')
BEGIN
	ALTER TABLE [mon].[Job_monitoring_config]
	ADD CONSTRAINT [PK_Job_monitoring_config]
		PRIMARY KEY CLUSTERED ([Job_monitoring_config_ID] ASC);
END;


IF NOT EXISTS (   SELECT 1
					FROM sys.indexes
					WHERE object_id = OBJECT_ID(N'[mon].[Job_monitoring_config]')
						AND name      = N'NCIX_Job_monitoring_config_COL_JMC_name')
BEGIN
	CREATE NONCLUSTERED INDEX [NCIX_Job_monitoring_config_COL_JMC_name]
	ON [mon].[Job_monitoring_config] ([JMC_name] ASC)
	INCLUDE (JMC_need_history, JMC_caller, JMC_ignore);
END;

IF NOT EXISTS (   SELECT *
                FROM sys.objects
               WHERE object_id = OBJECT_ID(N'[mon].[Job_monitoring_history]')
                 AND type IN ( N'U' ))
BEGIN
	   CREATE TABLE [mon].[Job_monitoring_history](
					[Job_monitoring_history_ID] [INT] IDENTITY(1,1) NOT NULL,
					[JMH_job_monitoring_config_ID] [INT] NOT NULL,
					[JMH_next_schedule] [DATETIME] NULL,
					[JMH_monitoring_status] [TINYINT] NOT NULL,
					[JMH_job_status] [TINYINT] NULL,
					[JMH_error_message] [NVARCHAR](4000) NULL,
					[JMH_update_status] [DATETIME] NOT NULL,
					[JMH_monitoring_date] [DATETIME] NULL,
		 CONSTRAINT [PK_Job_monitoring_history] PRIMARY KEY CLUSTERED 
		 (
			[Job_monitoring_history_ID] ASC
		 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
	  ) ON [PRIMARY]
END

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[mon].[DF_JMH_monitoring_status]') AND type = 'D')
BEGIN
ALTER TABLE [mon].[Job_monitoring_history] ADD CONSTRAINT DF_JMH_monitoring_status DEFAULT ((0)) FOR [JMH_monitoring_status]
END
GO

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[mon].[DF_JMH_update_status]') AND type = 'D')
BEGIN
ALTER TABLE [mon].[Job_monitoring_history] ADD CONSTRAINT DF_JMH_update_status DEFAULT (GETDATE()) FOR [JMH_update_status]
END
GO

IF NOT EXISTS (SELECT * FROM ::fn_listextendedproperty ('MS_Description', 'Schema', 'mon', 'Table', 'Job_monitoring_history', 'Column', 'JMH_monitoring_status'))
BEGIN
  EXEC sp_addextendedproperty 'MS_Description', '0: No error, 1:Error not checked, 2:Error checked', 'Schema', [mon], 'table', [Job_monitoring_history], 'column', [JMH_monitoring_status]
END
GO

IF NOT EXISTS (SELECT * FROM ::fn_listextendedproperty ('MS_Description', 'Schema', 'mon', 'Table', 'Job_monitoring_history', 'Column', 'JMH_job_status'))
BEGIN
  EXEC sp_addextendedproperty 'MS_Description', '0 = Failed
												 1 = Succeeded
												 2 = No execution', 'Schema', [mon], 'table', [Job_monitoring_history], 'column', [JMH_job_status]
END
GO


IF NOT EXISTS (   SELECT 1
						FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
					   WHERE CONSTRAINT_TYPE = 'PRIMARY KEY'
						 AND TABLE_NAME      = N'Job_monitoring_history')
BEGIN
	ALTER TABLE [mon].[Job_monitoring_history]
	ADD CONSTRAINT [PK_Job_monitoring_history]
		PRIMARY KEY CLUSTERED ([Job_monitoring_history_ID] ASC);
END;
	
IF NOT EXISTS (   SELECT 1
					FROM sys.foreign_keys
					WHERE object_id        = OBJECT_ID(N'mon.FK_Job_monitoring_config_COL_Job_monitoring_config_ID')
						AND parent_object_id = OBJECT_ID(N'[mon].[Job_monitoring_history]'))
BEGIN
	ALTER TABLE [mon].[Job_monitoring_history]
	ADD CONSTRAINT [FK_Job_monitoring_config_COL_Job_monitoring_config_ID]
		FOREIGN KEY ([JMH_Job_monitoring_config_ID])
		REFERENCES [mon].[Job_monitoring_config] ([Job_monitoring_config_ID]) ON DELETE NO ACTION ON UPDATE NO ACTION;
END;
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[mon].[Initialize_Jobs_Status]') AND type IN (N'P', N'PC'))
DROP PROCEDURE [mon].[Initialize_Jobs_Status]
GO

CREATE PROCEDURE [mon].[Initialize_Jobs_Status]
	@in_debug TINYINT = 0,
	@in_JobName NVARCHAR(1000) = NULL
AS
/*============================================================================= 

	Explication du traitement realise par la SP
	-------------------------------------------
	Cette SP sert a initialiser le status des jobs
 
	Contexte d'utilisation
	----------------------
	Appelée à la main en passant le nom du job à initialiser si besoin sinon all jobs  
 
	Parametres  
	----------  
	@in_debug : non utilisé
	@in_JobName : nom du job (si null All jobs)
 
	Creation : 13.07.23 / FLA
   
	Modifications : 
  
=============================================================================*/

SET NOCOUNT ON;

/*------------------- Declaration des variables --------------------*/
DECLARE @JobName     VARCHAR(255),
		@LastStatus  TINYINT,
		@LastMessage NVARCHAR(4000),
		@NextRun     DATETIME,
		@ID          INT;

/*------------ Affectation des parametres aux variables ------------*/

/*-------------------------- Traitement ---------------------------*/
BEGIN TRY

	DECLARE c_Jobs CURSOR FAST_FORWARD READ_ONLY FOR
	SELECT       SJ.name,
					SJH.LastStatus,
					SJH.Lastmessage,
					SJA.NextRun
		FROM       msdb.dbo.sysjobs SJ
		OUTER APPLY (   SELECT TOP 1 NextRun = JA.next_scheduled_run_date
						FROM msdb.dbo.sysjobactivity JA
						WHERE JA.job_id = SJ.job_id
						ORDER BY session_id DESC) AS SJA
		OUTER APPLY (   SELECT TOP 1 LastStatus = MIN(run_status),
									Lastmessage = CASE
														WHEN JH.sql_severity > 0 THEN message
														ELSE NULL END
						FROM msdb.dbo.sysjobhistory JH
						WHERE JH.job_id   = SJ.job_id
						AND JH.run_date = (   SELECT MAX(T.run_date)
												FROM msdb.dbo.sysjobhistory T
												WHERE T.job_id = JH.job_id)
						AND JH.run_time >= (   SELECT MAX(T.run_time)
													FROM msdb.dbo.sysjobhistory T
												WHERE T.job_id    = JH.job_id
													AND JH.run_date = T.run_date
													AND T.step_id   = 0)
						GROUP BY CASE
									WHEN JH.sql_severity > 0 THEN message
									ELSE NULL END
						ORDER BY MIN(run_status)) AS SJH
		WHERE   SJ.enabled = 1
        AND   ((   SJ.name LIKE 'DR[0-9]%'
				OR   SJ.name LIKE 'D[0-9]%'
				OR   SJ.name LIKE 'M[0-9]%'
				OR   SJ.name LIKE 'W[0-9]%'
				OR   SJ.name LIKE '[_]%') AND  @in_JobName IS NULL)
		AND SJ.name NOT LIKE '%Log reader%'
		OR      (SJ.name = @in_JobName AND @in_JobName IS NOT NULL)
		
		ORDER BY SJ.name;


	OPEN c_Jobs;

	FETCH NEXT FROM c_Jobs
		INTO @JobName,
			@LastStatus,
			@LastMessage,
			@NextRun;

	WHILE @@FETCH_STATUS = 0
	BEGIN

		IF NOT EXISTS (   SELECT 1
							FROM mon.Job_monitoring_config
							WHERE JMC_name = @JobName)
		BEGIN

			INSERT INTO mon.Job_monitoring_config (JMC_name,
													JMC_need_history)
			VALUES (@JobName, CASE
									WHEN @JobName LIKE '[_][0-9]%' THEN 0
									WHEN @JobName LIKE 'DR[0-9]%' THEN 0
									ELSE 1 END);
			SELECT @ID = SCOPE_IDENTITY();
			INSERT INTO mon.Job_monitoring_history (JMH_job_monitoring_config_ID,
													JMH_next_schedule,
													JMH_monitoring_status, 
													JMH_job_status,
													JMH_error_message)
			VALUES (@ID, @NextRun, CASE WHEN ISNULL(@LastStatus,1) = 1 THEN 0 ELSE 1 END, @LastStatus, @LastMessage);

		END;
		FETCH NEXT FROM c_Jobs
			INTO @JobName,
				@LastStatus,
				@LastMessage,
				@NextRun;

	    UPDATE JMC
	       SET JMC.JMC_Caller = SJ.name
	      FROM msdb.dbo.sysjobs SJ
    INNER JOIN msdb.dbo.sysjobsteps SJS
	        ON SJ.job_id    = SJS.job_id
	INNER JOIN mon.Job_monitoring_config JMC
	        ON JMC.JMC_name = REPLACE(REPLACE(SUBSTRING(SJS.step_name, 11, 80), '[', ''), ']', '')
	     WHERE SJS.command LIKE '%aps_Launch_Job_SQL_Agent%'
		    OR SJS.command LIKE '%aps_CheckProd_Job%';

	END;

	CLOSE c_Jobs;
	DEALLOCATE c_Jobs;

END TRY
BEGIN CATCH

	/* Traitement des erreurs (sans RaiseError) */
	EXEC dbo.get_Error_Info @in_RaiseError = 0;

END CATCH;

/*------------------ Retour au programme appelant -----------------*/
RETURN (@@error);
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetNextScheduleForJob]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [dbo].[GetNextScheduleForJob]
GO

CREATE FUNCTION [dbo].[GetNextScheduleForJob]
(
	@JobName SYSNAME,
    @startDate DATETIME,
	@endDate DATETIME
)

RETURNS @t TABLE (
		scheduleID INT NOT NULL,
		jobName SYSNAME NOT NULL,
		jobDescription NVARCHAR(512) NOT NULL,
		scheduleName SYSNAME NOT NULL,
		categoryName SYSNAME NOT NULL,
		startDate DATETIME NOT NULL,
		jobEnabled INT NOT NULL,
		scheduleEnabled INT NOT NULL)

AS

BEGIN

-- Create a tally table. If you already have one of your own please use that instead.
DECLARE	@tallyNumbers TABLE
		(
			num SMALLINT PRIMARY KEY CLUSTERED
		)

DECLARE	@index SMALLINT

SET	@index = 1

WHILE @index <= 8640
	BEGIN
		INSERT	@tallyNumbers
			(
				num
			)
		VALUES	(
				@index
			)

		SET	@index = @index + 1
	END

-- Create a staging table for jobschedules
DECLARE	@jobSchedules TABLE
		(
			rowID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
			jobName SYSNAME NOT NULL,
			jobDescription NVARCHAR(512) NOT NULL,
			scheduleName SYSNAME NOT NULL,
			scheduleID INT NOT NULL,
			categoryName SYSNAME NOT NULL,
			freq_type INT NOT NULL,
			freq_interval INT NOT NULL,
			freq_subday_type INT NOT NULL,
			freq_subday_interval INT NOT NULL,
			freq_relative_interval INT NOT NULL,
			freq_recurrence_factor INT NOT NULL,
			startDate DATETIME NOT NULL,
			startTime DATETIME NOT NULL,
			endDate DATETIME NOT NULL,
			endTime DATETIME NOT NULL,
			jobEnabled INT NOT NULL,
			scheduleEnabled INT NOT NULL
		)


-- Populate the staging table for JobSchedules with SQL Server 2005 and SQL Server 2008
INSERT		@JobSchedules
		(
			jobName,
			jobDescription,
			scheduleName,
			scheduleID,
			categoryName,
			freq_type,
			freq_interval,
			freq_subday_type,
			freq_subday_interval,
			freq_relative_interval,
			freq_recurrence_factor,
			startDate,
			startTime,
			endDate,
			endTime,
			jobEnabled,
			scheduleEnabled
		)
SELECT	sj.name,
		COALESCE(sj.description, ''),
		ss.name,
		ss.schedule_id,
		sc.name,
		ss.freq_type,
		ss.freq_interval,
		ss.freq_subday_type,
		ss.freq_subday_interval,
		ss.freq_relative_interval,
		ss.freq_recurrence_factor,
		COALESCE(STR(ss.active_start_date, 8), CONVERT(CHAR(8), GETDATE(), 112)) startDate,
		STUFF(STUFF(REPLACE(STR(ss.active_start_time, 6), ' ', '0'), 3, 0, ':'), 6, 0, ':') startTime,
		STR(ss.active_end_date, 8) endDate,
		STUFF(STUFF(REPLACE(STR(ss.active_end_time, 6), ' ', '0'), 3, 0, ':'), 6, 0, ':') endTime,
		sj.enabled,
		ss.enabled
FROM		msdb..sysschedules AS ss WITH (NOLOCK)
INNER JOIN	msdb..sysjobschedules AS sjs WITH (NOLOCK) ON sjs.schedule_id = ss.schedule_id
INNER JOIN	msdb..sysjobs AS sj WITH (NOLOCK) ON sj.job_id = sjs.job_id
INNER JOIN	msdb..syscategories AS sc WITH (NOLOCK) ON sc.category_id = sj.category_id
WHERE		ss.freq_type IN (1, 4, 8, 16, 32)
  AND       sj.name = @JobName

-- Deal with first, second, third, fourth and last occurence
DECLARE	@tempStart DATETIME,
	@tempEnd DATETIME

SELECT	@tempStart = DATEADD(MONTH, DATEDIFF(MONTH, '19000101', @startDate), '19000101'),
	@TempEnd = DATEADD(MONTH, DATEDIFF(MONTH, '18991231', @endDate), '18991231')

DECLARE	@dayInformation TABLE
		(
			infoDate DATETIME PRIMARY KEY CLUSTERED,
			weekdayName VARCHAR(9) NOT NULL,
			statusCode INT NOT NULL,
			lastDay TINYINT DEFAULT 0
		)

WHILE @tempStart <= @tempEnd
	BEGIN
		INSERT	@dayInformation
			(
				infoDate,
				weekdayName,
				statusCode
			)
		SELECT	@tempStart,
			DATENAME(WEEKDAY, @tempStart),
			CASE
				WHEN DATEPART(DAY, @tempStart) BETWEEN 1 AND 7 THEN 1
				WHEN DATEPART(DAY, @tempStart) BETWEEN 8 AND 14 THEN 2
				WHEN DATEPART(DAY, @tempStart) BETWEEN 15 AND 21 THEN 4
				WHEN DATEPART(DAY, @tempStart) BETWEEN 22 AND 28 THEN 8
				ELSE 0
			END

		SET	@tempStart = DATEADD(DAY, 1, @tempStart)
	END

UPDATE		di
SET		di.statusCode = di.statusCode + 16
FROM		@dayInformation AS di
INNER JOIN	(
			SELECT		DATEDIFF(MONTH, '19000101', infoDate) AS theMonth,
					DATEPART(DAY, MAX(infoDate)) - 6 AS theDay
			FROM		@dayInformation
			GROUP BY	DATEDIFF(MONTH, '19000101', infoDate)
		) AS x ON x.theMonth = DATEDIFF(MONTH, '19000101', di.infoDate)
WHERE		DATEPART(DAY, di.infoDate) >= x.theDay

UPDATE		di
SET		di.lastDay = 16
FROM		@dayInformation AS di
INNER JOIN	(
			SELECT		DATEDIFF(MONTH, '19000101', infoDate) AS theMonth,
					MAX(infoDate) AS theDay
			FROM		@dayInformation
			GROUP BY	DATEDIFF(MONTH, '19000101', infoDate)
		) AS x ON x.theMonth = DATEDIFF(MONTH, '19000101', di.infoDate)
WHERE		di.infoDate = x.theDay

UPDATE	@dayInformation
SET	lastDay = DATEPART(DAY, infoDate)
WHERE	DATEPART(DAY, infoDate) BETWEEN 1 AND 4

-- Stage all individual schedule times
DECLARE	@scheduleTimes TABLE
		(
			rowID INT NOT NULL,
            startDate DATETIME NOT NULL,
			endDate DATETIME NOT NULL,
			waitSeconds INT DEFAULT 0
		)

-- Insert one time only schedules
INSERT	@scheduleTimes
	(
		rowID,
        startDate,
		endDate
	)
SELECT	rowID,
	startDate+startTime,
	startDate+startTime
FROM	@jobSchedules
WHERE	freq_type = 1
	AND startDate+startTime BETWEEN @StartDate AND @EndDate

-- Insert daily schedules
INSERT	@scheduleTimes
		(
			rowID,
			startDate,
            endDate,
			waitSeconds
		)
SELECT	js.rowID,
		di.infoDate+js.startTime,
		di.infoDate+js.endTime,
		CASE js.freq_subday_type
			WHEN 1 THEN 0
			WHEN 2 THEN js.freq_subday_interval
			WHEN 4 THEN 60 * js.freq_subday_interval
			WHEN 8 THEN 3600 * js.freq_subday_interval
		END
FROM		@jobSchedules AS js
INNER JOIN	@dayInformation AS di ON di.infoDate >= js.startDate
			AND di.infoDate <= js.endDate
WHERE		js.freq_type = 4
		AND DATEDIFF(DAY, js.startDate, di.infoDate) % js.freq_interval = 0
       

-- Insert weekly schedules
INSERT		@scheduleTimes
		(
			rowID,
			startDate,
            endDate,
			waitSeconds
		)
SELECT		js.rowID,
		di.infoDate+js.startTime,
		di.infoDate+js.endTime,
		CASE js.freq_subday_type
			WHEN 1 THEN 0
			WHEN 2 THEN js.freq_subday_interval
			WHEN 4 THEN 60 * js.freq_subday_interval
			WHEN 8 THEN 3600 * js.freq_subday_interval
		END
FROM		@jobSchedules AS js
INNER JOIN	@dayInformation AS di ON di.infoDate >= js.startDate
			AND di.infoDate <= js.endDate
WHERE		js.freq_type = 8
		AND 1 =	CASE
				WHEN js.freq_interval & 1 = 1 AND di.weekdayName = 'Sunday' THEN 1
				WHEN js.freq_interval & 2 = 2 AND di.weekdayName = 'Monday' THEN 1
				WHEN js.freq_interval & 4 = 4 AND di.weekdayName = 'Tuesday' THEN 1
				WHEN js.freq_interval & 8 = 8 AND di.weekdayName = 'Wednesday' THEN 1
				WHEN js.freq_interval & 16 = 16 AND di.weekdayName = 'Thursday' THEN 1
				WHEN js.freq_interval & 32 = 32 AND di.weekdayName = 'Friday' THEN 1
				WHEN js.freq_interval & 64 = 64 AND di.weekdayName = 'Saturday' THEN 1
				ELSE 0
			END
		AND (DATEDIFF(DAY, js.startDate, di.infoDate) / 7) % js.freq_recurrence_factor = 0
      
-- Insert monthly schedules
INSERT		@scheduleTimes
		(
			rowID,
			startDate,
            endDate,
			waitSeconds
		)
SELECT		js.rowID,
		di.infoDate+js.startTime,
		di.infoDate+js.endTime,
		CASE js.freq_subday_type
			WHEN 1 THEN 0
			WHEN 2 THEN js.freq_subday_interval
			WHEN 4 THEN 60 * js.freq_subday_interval
			WHEN 8 THEN 3600 * js.freq_subday_interval
		END
FROM		@jobSchedules AS js
INNER JOIN	@dayInformation AS di ON di.infoDate >= js.startDate
			AND di.infoDate <= js.endDate
WHERE		js.freq_type = 16
		AND DATEPART(DAY, di.infoDate) = js.freq_interval
		AND DATEDIFF(MONTH, js.startDate, di.infoDate) % js.freq_recurrence_factor = 0
        
-- Insert monthly relative schedules
INSERT		@scheduleTimes
		(
			rowID,
			startDate,
            endDate,
			waitSeconds
		)
SELECT		js.rowID,
		di.infoDate+js.startTime,
		di.infoDate+js.endTime,
		CASE js.freq_subday_type
			WHEN 1 THEN 0
			WHEN 2 THEN js.freq_subday_interval
			WHEN 4 THEN 60 * js.freq_subday_interval
			WHEN 8 THEN 3600 * js.freq_subday_interval
		END
FROM		@jobSchedules AS js
INNER JOIN	@dayInformation AS di ON di.infoDate >= js.startDate
			AND di.infoDate <= js.endDate
WHERE		js.freq_type = 32
		AND 1 =	CASE
				WHEN js.freq_interval = 1 AND di.weekdayName = 'Sunday' THEN 1
				WHEN js.freq_interval = 2 AND di.weekdayName = 'Monday' THEN 1
				WHEN js.freq_interval = 3 AND di.weekdayName = 'Tuesday' THEN 1
				WHEN js.freq_interval = 4 AND di.weekdayName = 'Wednesday' THEN 1
				WHEN js.freq_interval = 5 AND di.weekdayName = 'Thursday' THEN 1
				WHEN js.freq_interval = 6 AND di.weekdayName = 'Friday' THEN 1
				WHEN js.freq_interval = 7 AND di.weekdayName = 'Saturday' THEN 1
				WHEN js.freq_interval = 8 AND js.freq_relative_interval = di.lastDay THEN 1
				WHEN js.freq_interval = 9 AND di.weekdayName NOT IN ('Sunday', 'Saturday') THEN 1
				WHEN js.freq_interval = 10 AND di.weekdayName IN ('Sunday', 'Saturday') THEN 1
				ELSE 0
			END
		AND di.statusCode & js.freq_relative_interval = js.freq_relative_interval
		AND DATEDIFF(MONTH, js.startDate, di.infoDate) % js.freq_recurrence_factor = 0

-- Get the daily recurring schedule times
INSERT		@scheduleTimes
		(
			rowID,
			startDate,
			endDate,
			waitSeconds
		)
SELECT	st.rowID,
		DATEADD(SECOND, tn.num * st.waitSeconds, st.startDate),
		st.endDate,
		st.waitSeconds
FROM		@scheduleTimes AS st
CROSS JOIN	@tallyNumbers AS tn
WHERE		tn.num * st.waitSeconds <= DATEDIFF(SECOND, st.startDate, st.endDate)
		AND st.waitSeconds > 0

-- Present the result
INSERT @t (scheduleID,
		jobName,
		jobDescription,
		scheduleName,
		categoryName,
		startDate,
		jobEnabled,
		scheduleEnabled)
SELECT	js.scheduleID,
		js.jobName,
		js.jobDescription,
		js.scheduleName,
		js.categoryName,
		st.startDate,
		js.jobEnabled,
		js.scheduleEnabled
FROM		@scheduleTimes AS st
INNER JOIN	@jobSchedules AS js ON js.rowID = st.rowID
WHERE		js.jobEnabled = 1
		AND js.scheduleEnabled = 1
        AND st.startDate >= @startDate
        AND st.startDate <= @endDate
RETURN

END
GO


IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[mon].[Maj_Jobs_Status]') AND type IN (N'P', N'PC'))
DROP PROCEDURE [mon].[Maj_Jobs_Status]
GO

CREATE PROCEDURE [mon].[Maj_Jobs_Status]
	@in_debug   TINYINT = 0,
	@in_JobName NVARCHAR(1000) = NULL,
    @in_JobID   UNIQUEIDENTIFIER = NULL
AS
/*============================================================================= 

	Explication du traitement realise par la SP
	-------------------------------------------
	Cette SP sert a mettre à jour le status des jobs
 
	Contexte d'utilisation
	----------------------
	Appelée depuis le step Empty Step et Send mail KO des jobs
 
	Parametres  
	----------  
	@in_debug : non utilisé
	@in_JobName : nom du job (si null All jobs)
 
	Creation : 03.08.23 / FLA
   
	Modifications : 
	22.08.23 : Get directly the job_id in job instead of put job name in parameter
	21.09.23 : Call the new version of function [GetNextScheduleForJob]
  
=============================================================================*/


SET NOCOUNT ON;

/*------------------- Declaration des variables --------------------*/
DECLARE @LastMessage  NVARCHAR(4000) = NULL,
		@JMC_ID       INT = 0,
        @JMH_ID       INT = NULL,
        @NeedHistory  BIT = 1,
        @NextRun      DATETIME

/*------------ Affectation des parametres aux variables ------------*/

/*-------------------------- Traitement ---------------------------*/
BEGIN TRY

	IF @in_JobID IS NOT NULL
		SELECT @in_JobName = name FROM msdb.dbo.sysjobs WHERE job_id = @in_JobID

    SELECT @LastMessage = jh.message
      FROM msdb.dbo.sysjobhistory jh
INNER JOIN msdb.dbo.sysjobactivity ja
        ON ja.job_id = jh.job_id
INNER JOIN msdb.dbo.sysjobs j
        ON j.job_id  = ja.job_id
     WHERE j.name  = @in_JobName
       AND run_status = 0 --and ja.stop_execution_date is null--
       AND ja.session_id = (SELECT TOP 1 session_id
                              FROM msdb.dbo.syssessions
                          ORDER BY agent_start_date DESC)
      AND start_execution_date IS NOT NULL
    --AND stop_execution_date is null
      AND msdb.dbo.agent_datetime(run_date, run_time) >= start_execution_date;

    /* Gestion des Query Timeout Error */
    IF EXISTS (SELECT 1
                 FROM msdb..sysjobhistory jh
           INNER JOIN msdb..sysjobactivity ja
                   ON ja.job_id = jh.job_id
           INNER JOIN msdb..sysjobs j
                   ON j.job_id  = ja.job_id
                WHERE j.name = @in_JobName
                  AND run_status = 1 --and ja.stop_execution_date is null--
                  AND ja.session_id = (SELECT TOP 1 session_id
                                         FROM msdb.dbo.syssessions
                                     ORDER BY agent_start_date DESC)
                  AND start_execution_date IS NOT NULL
                  AND jh.sql_message_id = 7412
                  AND msdb.dbo.agent_datetime(run_date, run_time) >= start_execution_date)
    BEGIN

        SELECT @LastMessage = jh.message
          FROM msdb..sysjobhistory jh
    INNER JOIN msdb..sysjobactivity ja
            ON ja.job_id = jh.job_id
    INNER JOIN msdb..sysjobs j
            ON j.job_id  = ja.job_id
         WHERE j.name = @in_JobName
           AND run_status = 1 --and ja.stop_execution_date is null--
           AND ja.session_id = (SELECT TOP 1 session_id
                                  FROM msdb.dbo.syssessions
                              ORDER BY agent_start_date DESC)
           AND start_execution_date IS NOT NULL
           AND jh.sql_message_id                           = 7412
           AND msdb.dbo.agent_datetime(run_date, run_time) >= start_execution_date;
    END;

    SELECT @JMC_ID = JMC.Job_monitoring_config_ID,
           @NeedHistory = JMC.JMC_need_history,
           @JMH_ID = JMH.Job_monitoring_history_ID
      FROM [mon].[Job_monitoring_config] JMC
LEFT OUTER JOIN [mon].[Job_monitoring_history] JMH
        ON JMC.Job_monitoring_config_ID = JMH.JMH_job_monitoring_config_ID
     WHERE JMC_name = @in_JobName
       AND JMC_ignore = 0  

    IF @JMC_ID <> 0
    BEGIN
        
        SELECT @NextRun = Job.startDate FROM
        (SELECT TOP 1 startDate
           FROM [dbo].[GetNextScheduleForJob](@in_JobName,GETDATE(), GETDATE() + 1)
       ORDER BY startDate) AS Job

        IF @NeedHistory = 0
        BEGIN
            UPDATE [mon].[Job_monitoring_history] SET [JMH_next_schedule] = @NextRun,
                                                      [JMH_monitoring_status] = CASE WHEN @LastMessage IS NOT NULL THEN 1 ELSE 0 END,
                                                      [JMH_job_status] = CASE WHEN @LastMessage IS NOT NULL THEN 0 ELSE 1 END,
                                                      [JMH_error_message] = @LastMessage,
                                                      [JMH_update_status] = GETDATE()
             WHERE JMH_job_monitoring_config_ID = @JMH_ID
        END
        ELSE
        BEGIN
            INSERT INTO [mon].[Job_monitoring_history] ([JMH_job_monitoring_config_ID],
                                                        [JMH_next_schedule],
                                                        [JMH_monitoring_status],
                                                        [JMH_job_status],
                                                        [JMH_error_message],
                                                        [JMH_update_status])
            VALUES (@JMC_ID,
                    @NextRun,
                    CASE WHEN @LastMessage IS NOT NULL THEN 1 ELSE 0 END,
                    CASE WHEN @LastMessage IS NOT NULL THEN 0 ELSE 1 END,
                    @LastMessage,
                    DEFAULT
                )
        END
    END

END TRY
BEGIN CATCH

	/* Traitement des erreurs (sans RaiseError) */
	EXEC dbo.get_Error_Info @in_RaiseError = 0;

END CATCH;

/*------------------ Retour au programme appelant -----------------*/
RETURN (@@error);
GO


IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[mon].[Get_Jobs_Status]') AND type in (N'P', N'PC'))
DROP PROCEDURE [mon].[Get_Jobs_Status]
GO

CREATE PROCEDURE [mon].[Get_Jobs_Status]
	@in_debug TINYINT = 0,
	@in_JobName NVARCHAR(1000) = NULL
AS
/*============================================================================= 

	Explication du traitement realise par la SP
	-------------------------------------------
	Cette SP sert à retourner le status des jobs
 
	Contexte d'utilisation
	----------------------
	Appelée depuis le MDV ou à la main
 
	Parametres  
	----------  
	@in_debug : non utilisé
	@in_JobName : nom du job (si null All jobs)
 
	Creation : 17.08.23 / FLA
   
	Modifications : 
  
=============================================================================*/


SET NOCOUNT ON;

/*------------------- Declaration des variables --------------------*/


/*------------ Affectation des parametres aux variables ------------*/

/*-------------------------- Traitement ---------------------------*/
BEGIN TRY

	   SELECT   JMC.JMC_name AS JobName,
				JMH.JMH_error_message AS Error,
				CASE
					 WHEN (   JMH.JMH_job_status = 0
						AND   JMH_monitoring_status = 1) THEN 'ERROR'
					 WHEN (CASE WHEN ISNULL(JMH.JMH_next_schedule,T1.JMH_next_schedule) < ISNULL(T1.JMH_next_schedule,JMH.JMH_next_schedule) THEN  ISNULL(JMH.JMH_next_schedule,T1.JMH_next_schedule) ELSE ISNULL(T1.JMH_next_schedule,JMH.JMH_next_schedule) END < GETDATE()) THEN 'NOT EXECUTED'
					 ELSE '' END AS Status
	  FROM      mon.Job_monitoring_config JMC
	 INNER JOIN mon.Job_monitoring_history JMH
		ON JMC.Job_monitoring_config_ID = JMH.JMH_job_monitoring_config_ID
	   AND JMC.JMC_ignore               = 0
	   AND JMH.JMH_update_status        = (SELECT MAX(JMH2.JMH_update_status)
											 FROM mon.Job_monitoring_history JMH2
											WHERE JMH2.JMH_job_monitoring_config_ID = JMC.Job_monitoring_config_ID)
	OUTER APPLY (SELECT JMH3.JMH_next_schedule FROM mon.Job_monitoring_config JMC3
	 INNER JOIN mon.Job_monitoring_history JMH3
		ON JMC3.Job_monitoring_config_ID = JMH3.JMH_job_monitoring_config_ID
	   AND JMH3.JMH_update_status        = (SELECT MAX(JMH4.JMH_update_status)
											  FROM mon.Job_monitoring_history JMH4
											 WHERE JMH4.JMH_job_monitoring_config_ID = JMC3.Job_monitoring_config_ID)
	WHERE JMC3.JMC_name = JMC.JMC_caller) AS T1
	   WHERE JMC.JMC_name = COALESCE(@in_JobName, JMC.JMC_name)
		ORDER BY JMC.JMC_name

END TRY
BEGIN CATCH

	/* Traitement des erreurs (sans RaiseError) */
	EXEC dbo.get_Error_Info @in_RaiseError = 0;

END CATCH;

/*------------------ Retour au programme appelant -----------------*/
RETURN (@@error);
GO


/***********************************************************************/
/*                         INITIALISATION                              */
/*                                                                     */
/***********************************************************************/

EXEC [mon].[Initialize_Jobs_Status]

SELECT      *
  FROM      mon.Job_monitoring_config JMC
 INNER JOIN mon.Job_monitoring_history JMH
    ON JMC.Job_monitoring_config_ID = JMH.JMH_Job_monitoring_config_ID
ORDER BY JMC.JMC_name


EXEC [mon].[Get_Jobs_Status]
