From 84ac447f3ee55e8449bf43739fa30d341cb096f2 Mon Sep 17 00:00:00 2001 From: ypermitin Date: Sun, 12 Nov 2023 18:15:38 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BB=D1=83?= =?UTF-8?q?=D0=B6=D0=B5=D0=B1=D0=BD=D0=BE=D0=B9=20=D0=B1=D0=B0=D0=B7=D1=8B?= =?UTF-8?q?=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateServiceDatabaseScript.sql | 12 +- ..._FixCreateOrUPdateJobsBySettingsProcV2.sql | 216 +++++++++++++++++ ..._FixCreateOrUPdateJobsBySettingsProcV3.sql | 224 ++++++++++++++++++ 3 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 SQL-Server-Maintenance/Service-Database/Migrations/V1_0_0_13__Jobs_FixCreateOrUPdateJobsBySettingsProcV2.sql create mode 100644 SQL-Server-Maintenance/Service-Database/Migrations/V1_0_0_14__Jobs_FixCreateOrUPdateJobsBySettingsProcV3.sql diff --git a/SQL-Server-Maintenance/Service-Database/CreateServiceDatabaseScript.sql b/SQL-Server-Maintenance/Service-Database/CreateServiceDatabaseScript.sql index b399879..cf22ba9 100644 --- a/SQL-Server-Maintenance/Service-Database/CreateServiceDatabaseScript.sql +++ b/SQL-Server-Maintenance/Service-Database/CreateServiceDatabaseScript.sql @@ -285,6 +285,8 @@ INSERT [dbo].[changelog] ([id], [type], [version], [description], [name], [check GO INSERT [dbo].[changelog] ([id], [type], [version], [description], [name], [checksum], [installed_by], [installed_on], [success]) VALUES (14, 0, N'1.0.0.12', N'Jobs FixCreateOrUPdateJobsBySettingsProc (72 ms)', N'V1_0_0_12__Jobs_FixCreateOrUPdateJobsBySettingsProc.sql', N'830D32CAB88A1ACC673F515AA8D5B96D', N'sa', CAST(N'2023-11-09T18:58:14.717' AS DateTime), 1) GO +INSERT [dbo].[changelog] ([id], [type], [version], [description], [name], [checksum], [installed_by], [installed_on], [success]) VALUES (1014, 0, N'1.0.0.13', N'Jobs FixCreateOrUPdateJobsBySettingsProcV2 (33 ms)', N'V1_0_0_13__Jobs_FixCreateOrUPdateJobsBySettingsProcV2.sql', N'20830460B9482F8F0E072F0258C2C246', N'sa', CAST(N'2023-11-10T10:20:16.897' AS DateTime), 1) +GO SET IDENTITY_INSERT [dbo].[changelog] OFF GO @@ -1287,6 +1289,10 @@ BEGIN SET @jobDescription = REPLACE(@Description, '{DatabaseName}', @currentDatabaseName); DECLARE @currentJobAction nvarchar(max) = REPLACE(@JobAction, '{DatabaseName}', @currentDatabaseName); + SET @jobAlreadyExists = 0; + SET @currentJobId = NULL; + SET @currentjobVersionDate = NULL; + SELECT @jobAlreadyExists = 1, @currentJobId = sj.job_id, @@ -1330,7 +1336,11 @@ BEGIN END CLOSE job_templates_databases_cursor; DEALLOCATE job_templates_databases_cursor; - END ELSE BEGIN + END ELSE BEGIN + SET @jobAlreadyExists = 0; + SET @currentJobId = NULL; + SET @currentjobVersionDate = NULL; + SELECT @jobAlreadyExists = 1, @currentJobId = sj.job_id, diff --git a/SQL-Server-Maintenance/Service-Database/Migrations/V1_0_0_13__Jobs_FixCreateOrUPdateJobsBySettingsProcV2.sql b/SQL-Server-Maintenance/Service-Database/Migrations/V1_0_0_13__Jobs_FixCreateOrUPdateJobsBySettingsProcV2.sql new file mode 100644 index 0000000..0302d78 --- /dev/null +++ b/SQL-Server-Maintenance/Service-Database/Migrations/V1_0_0_13__Jobs_FixCreateOrUPdateJobsBySettingsProcV2.sql @@ -0,0 +1,216 @@ +ALTER PROCEDURE [dbo].[sp_CreateOrUpdateJobsBySettings] + @force bit = 0 +AS +BEGIN + SET NOCOUNT ON; + + -- Поля шаблона + DECLARE + @Id int, + @Enable bit, + @ApplyTemplateQuery nvarchar(max), + @Name nvarchar(250), + @Description nvarchar(512), + @JobAction nvarchar(max), + @ScheduleEnable bit, + @ScheduleFreqType int, + @ScheduleFreqInterval int, + @ScheduleFreqSubdayType int, + @ScheduleFreqSubdayInterval int, + @ScheduleFreqRelativeInterval int, + @ScheduleFreqRecurrenceFactor int, + @ScheduleActiveStartDay int, + @ScheduleActiveEndDay int, + @ScheduleActiveStartTime int, + @ScheduleActiveEndTime int, + @VersionDate datetime, + @TimeoutSec int; + + DECLARE + @jobName nvarchar(250), + @jobDescription nvarchar(513), + @jobScript nvarchar(max), + @currentjobVersionDate datetime, + @currentJobId uniqueidentifier, + @JobAlreadyExists bit = 0, + @msg nvarchar(max); + + -- Служебные переменные + DECLARE + @sql nvarchar(max), + @currentDatabaseName nvarchar(250); + + DECLARE job_templates_cursor CURSOR + FOR SELECT + [Id] + ,[Enable] + ,[ApplyTemplateQuery] + ,[Name] + ,[Description] + ,[JobAction] + ,[ScheduleEnable] + ,[ScheduleFreqType] + ,[ScheduleFreqInterval] + ,[ScheduleFreqSubdayType] + ,[ScheduleFreqSubdayInterval] + ,[ScheduleFreqRelativeInterval] + ,[ScheduleFreqRecurrenceFactor] + ,[ScheduleActiveStartDay] + ,[ScheduleActiveEndDay] + ,[ScheduleActiveStartTime] + ,[ScheduleActiveEndTime] + ,[VersionDate] + ,[TimeoutSec] + FROM [dbo].[JobTemplates] + WHERE [UseSetting] = 1; + OPEN job_templates_cursor; + + FETCH NEXT FROM job_templates_cursor + INTO @Id, @Enable, @ApplyTemplateQuery, @Name, @Description, @JobAction, @ScheduleEnable, + @ScheduleFreqType, @ScheduleFreqInterval, @ScheduleFreqSubdayType, @ScheduleFreqSubdayInterval, + @ScheduleFreqRelativeInterval, @ScheduleFreqRecurrenceFactor, @ScheduleActiveStartDay, + @ScheduleActiveEndDay, @ScheduleActiveStartTime, @ScheduleActiveEndTime, @VersionDate, @TimeoutSec; + + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @Description = @Description + ' (Version date:' + CAST(@VersionDate AS nvarchar(max)) + ')'; + + IF(@ApplyTemplateQuery IS NOT NULL) + BEGIN + -- Задания создаются по базам данных + IF(NOT EXISTS(SELECT + [name] + FROM sys.dm_exec_describe_first_result_set (@ApplyTemplateQuery, NULL, 0) + WHERE [name] = 'DatabaseName')) + BEGIN + PRINT @Name; + THROW 51000, 'Запрос шаблона не содержит поля DatabaseName.', 1; + END + + IF (OBJECT_ID('tempdb..##databasesForJobs') IS NOT NULL) + DROP Table ##databasesForJobs; + IF(1 = 0) + BEGIN + -- !!! Костыль для поддержания корректного поведения редактора SQL кода, + -- иначе ругается на несуществующую глобавльную временную таблицу + CREATE TABLE ##databasesForJobs (DatabaseName nvarchar(255)); + END + SET @sql = CAST('SELECT [DatabaseName] INTO ##databasesForJobs FROM (' AS nvarchar(max)) + + CAST(@ApplyTemplateQuery AS nvarchar(max)) + + CAST(') AS T' AS nvarchar(max)) + EXEC sp_executesql @sql + + DECLARE job_templates_databases_cursor CURSOR + FOR SELECT [DatabaseName] FROM ##databasesForJobs; + OPEN job_templates_databases_cursor; + FETCH NEXT FROM job_templates_databases_cursor INTO @currentDatabaseName; + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @jobName = REPLACE(@Name, '{DatabaseName}', @currentDatabaseName); + SET @jobDescription = REPLACE(@Description, '{DatabaseName}', @currentDatabaseName); + DECLARE @currentJobAction nvarchar(max) = REPLACE(@JobAction, '{DatabaseName}', @currentDatabaseName); + + SELECT + @jobAlreadyExists = 1, + @currentJobId = sj.job_id, + @currentjobVersionDate = CASE WHEN sj.date_modified > sj.date_created THEN sj.date_modified ELSE sj.date_created END + FROM [msdb].[dbo].[sysjobs] sj + WHERE sj.[name] = @jobName + + -- Если задание уже существует, но в настройках содержится более новая версия, + -- то удаляем старое задание и создаем заново + IF(@jobAlreadyExists = 1 AND (@force = 1 OR @VersionDate > @currentjobVersionDate)) + BEGIN + EXEC msdb.dbo.sp_delete_job + @job_id = @currentJobId, + @delete_unused_schedule = 1; + SET @msg = 'Удалено задание: ' + @jobName; + PRINT @msg; + SET @jobAlreadyExists = 0; + END + + IF(@jobAlreadyExists = 0) + BEGIN + EXECUTE [dbo].[sp_CreateSimpleJob] + @jobName = @jobName + ,@jobDescription = @jobDescription + ,@jobEnabled = @Enable + ,@databaseName = @currentDatabaseName + ,@jobAction = @currentJobAction + ,@scheduleEnabled = @ScheduleEnable + ,@scheduleFreqType = @ScheduleFreqType + ,@scheduleFreqInterval = @ScheduleFreqInterval + ,@scheduleFreqSubdayType = @ScheduleFreqSubdayType + ,@scheduleFreqSubdayInterval = @ScheduleFreqSubdayInterval + ,@scheduleFreqRelativeInterval = @ScheduleFreqRelativeInterval + ,@scheduleFreqRecurrenceFactor = @ScheduleFreqRecurrenceFactor + ,@scheduleActiveStartDate = @ScheduleActiveStartDay + ,@scheduleActiveEndDate = @ScheduleActiveEndDay + ,@scheduleActiveStartTime = @ScheduleActiveStartTime + ,@scheduleActiveEndTime = @ScheduleActiveEndTime + ,@jobTimeoutSec = @TimeoutSec + + SET @msg = 'Создано задание: ' + @jobName; + PRINT @msg; + END + + FETCH NEXT FROM job_templates_databases_cursor INTO @currentDatabaseName; + END + CLOSE job_templates_databases_cursor; + DEALLOCATE job_templates_databases_cursor; + END ELSE BEGIN + SELECT + @jobAlreadyExists = 1, + @currentJobId = sj.job_id, + @currentjobVersionDate = CASE WHEN sj.date_modified > sj.date_created THEN sj.date_modified ELSE sj.date_created END + FROM [msdb].[dbo].[sysjobs] sj + WHERE sj.[name] = @Name + + -- Если задание уже существует, но в настройках содержится более новая версия, + -- то удаляем старое задание и создаем заново + IF(@jobAlreadyExists = 1 AND (@force = 1 OR @VersionDate > @currentjobVersionDate)) + BEGIN + EXEC msdb.dbo.sp_delete_job + @job_id = @currentJobId, + @delete_unused_schedule = 1; + SET @msg = 'Удалено задание: ' + @Name; + PRINT @msg; + SET @jobAlreadyExists = 0; + END + + IF(@jobAlreadyExists = 0) + BEGIN + -- Задание создается единое на весь сервер + EXECUTE [dbo].[sp_CreateSimpleJob] + @jobName = @Name + ,@jobDescription = @Description + ,@jobEnabled = @Enable + ,@databaseName = 'SQLServerMaintenance' + ,@jobAction = @JobAction + ,@scheduleEnabled = @ScheduleEnable + ,@scheduleFreqType = @ScheduleFreqType + ,@scheduleFreqInterval = @ScheduleFreqInterval + ,@scheduleFreqSubdayType = @ScheduleFreqSubdayType + ,@scheduleFreqSubdayInterval = @ScheduleFreqSubdayInterval + ,@scheduleFreqRelativeInterval = @ScheduleFreqRelativeInterval + ,@scheduleFreqRecurrenceFactor = @ScheduleFreqRecurrenceFactor + ,@scheduleActiveStartDate = @ScheduleActiveStartDay + ,@scheduleActiveEndDate = @ScheduleActiveEndDay + ,@scheduleActiveStartTime = @ScheduleActiveStartTime + ,@scheduleActiveEndTime = @ScheduleActiveEndTime + ,@jobTimeoutSec = @TimeoutSec + + SET @msg = 'Создано задание: ' + @Name; + PRINT @msg; + END + END + + FETCH NEXT FROM job_templates_cursor + INTO @Id, @Enable, @ApplyTemplateQuery, @Name, @Description, @JobAction, @ScheduleEnable, + @ScheduleFreqType, @ScheduleFreqInterval, @ScheduleFreqSubdayType, @ScheduleFreqSubdayInterval, + @ScheduleFreqRelativeInterval, @ScheduleFreqRecurrenceFactor, @ScheduleActiveStartDay, + @ScheduleActiveEndDay, @ScheduleActiveStartTime, @ScheduleActiveEndTime, @VersionDate, @TimeoutSec; + END + CLOSE job_templates_cursor; + DEALLOCATE job_templates_cursor; +END \ No newline at end of file diff --git a/SQL-Server-Maintenance/Service-Database/Migrations/V1_0_0_14__Jobs_FixCreateOrUPdateJobsBySettingsProcV3.sql b/SQL-Server-Maintenance/Service-Database/Migrations/V1_0_0_14__Jobs_FixCreateOrUPdateJobsBySettingsProcV3.sql new file mode 100644 index 0000000..df73233 --- /dev/null +++ b/SQL-Server-Maintenance/Service-Database/Migrations/V1_0_0_14__Jobs_FixCreateOrUPdateJobsBySettingsProcV3.sql @@ -0,0 +1,224 @@ +ALTER PROCEDURE [dbo].[sp_CreateOrUpdateJobsBySettings] + @force bit = 0 +AS +BEGIN + SET NOCOUNT ON; + + -- Поля шаблона + DECLARE + @Id int, + @Enable bit, + @ApplyTemplateQuery nvarchar(max), + @Name nvarchar(250), + @Description nvarchar(512), + @JobAction nvarchar(max), + @ScheduleEnable bit, + @ScheduleFreqType int, + @ScheduleFreqInterval int, + @ScheduleFreqSubdayType int, + @ScheduleFreqSubdayInterval int, + @ScheduleFreqRelativeInterval int, + @ScheduleFreqRecurrenceFactor int, + @ScheduleActiveStartDay int, + @ScheduleActiveEndDay int, + @ScheduleActiveStartTime int, + @ScheduleActiveEndTime int, + @VersionDate datetime, + @TimeoutSec int; + + DECLARE + @jobName nvarchar(250), + @jobDescription nvarchar(513), + @jobScript nvarchar(max), + @currentjobVersionDate datetime, + @currentJobId uniqueidentifier, + @JobAlreadyExists bit = 0, + @msg nvarchar(max); + + -- Служебные переменные + DECLARE + @sql nvarchar(max), + @currentDatabaseName nvarchar(250); + + DECLARE job_templates_cursor CURSOR + FOR SELECT + [Id] + ,[Enable] + ,[ApplyTemplateQuery] + ,[Name] + ,[Description] + ,[JobAction] + ,[ScheduleEnable] + ,[ScheduleFreqType] + ,[ScheduleFreqInterval] + ,[ScheduleFreqSubdayType] + ,[ScheduleFreqSubdayInterval] + ,[ScheduleFreqRelativeInterval] + ,[ScheduleFreqRecurrenceFactor] + ,[ScheduleActiveStartDay] + ,[ScheduleActiveEndDay] + ,[ScheduleActiveStartTime] + ,[ScheduleActiveEndTime] + ,[VersionDate] + ,[TimeoutSec] + FROM [dbo].[JobTemplates] + WHERE [UseSetting] = 1; + OPEN job_templates_cursor; + + FETCH NEXT FROM job_templates_cursor + INTO @Id, @Enable, @ApplyTemplateQuery, @Name, @Description, @JobAction, @ScheduleEnable, + @ScheduleFreqType, @ScheduleFreqInterval, @ScheduleFreqSubdayType, @ScheduleFreqSubdayInterval, + @ScheduleFreqRelativeInterval, @ScheduleFreqRecurrenceFactor, @ScheduleActiveStartDay, + @ScheduleActiveEndDay, @ScheduleActiveStartTime, @ScheduleActiveEndTime, @VersionDate, @TimeoutSec; + + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @Description = @Description + ' (Version date:' + CAST(@VersionDate AS nvarchar(max)) + ')'; + + IF(@ApplyTemplateQuery IS NOT NULL) + BEGIN + -- Задания создаются по базам данных + IF(NOT EXISTS(SELECT + [name] + FROM sys.dm_exec_describe_first_result_set (@ApplyTemplateQuery, NULL, 0) + WHERE [name] = 'DatabaseName')) + BEGIN + PRINT @Name; + THROW 51000, 'Запрос шаблона не содержит поля DatabaseName.', 1; + END + + IF (OBJECT_ID('tempdb..##databasesForJobs') IS NOT NULL) + DROP Table ##databasesForJobs; + IF(1 = 0) + BEGIN + -- !!! Костыль для поддержания корректного поведения редактора SQL кода, + -- иначе ругается на несуществующую глобавльную временную таблицу + CREATE TABLE ##databasesForJobs (DatabaseName nvarchar(255)); + END + SET @sql = CAST('SELECT [DatabaseName] INTO ##databasesForJobs FROM (' AS nvarchar(max)) + + CAST(@ApplyTemplateQuery AS nvarchar(max)) + + CAST(') AS T' AS nvarchar(max)) + EXEC sp_executesql @sql + + DECLARE job_templates_databases_cursor CURSOR + FOR SELECT [DatabaseName] FROM ##databasesForJobs; + OPEN job_templates_databases_cursor; + FETCH NEXT FROM job_templates_databases_cursor INTO @currentDatabaseName; + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @jobName = REPLACE(@Name, '{DatabaseName}', @currentDatabaseName); + SET @jobDescription = REPLACE(@Description, '{DatabaseName}', @currentDatabaseName); + DECLARE @currentJobAction nvarchar(max) = REPLACE(@JobAction, '{DatabaseName}', @currentDatabaseName); + + SET @jobAlreadyExists = 0; + SET @currentJobId = NULL; + SET @currentjobVersionDate = NULL; + + SELECT + @jobAlreadyExists = 1, + @currentJobId = sj.job_id, + @currentjobVersionDate = CASE WHEN sj.date_modified > sj.date_created THEN sj.date_modified ELSE sj.date_created END + FROM [msdb].[dbo].[sysjobs] sj + WHERE sj.[name] = @jobName + + -- Если задание уже существует, но в настройках содержится более новая версия, + -- то удаляем старое задание и создаем заново + IF(@jobAlreadyExists = 1 AND (@force = 1 OR @VersionDate > @currentjobVersionDate)) + BEGIN + EXEC msdb.dbo.sp_delete_job + @job_id = @currentJobId, + @delete_unused_schedule = 1; + SET @msg = 'Удалено задание: ' + @jobName; + PRINT @msg; + SET @jobAlreadyExists = 0; + END + + IF(@jobAlreadyExists = 0) + BEGIN + EXECUTE [dbo].[sp_CreateSimpleJob] + @jobName = @jobName + ,@jobDescription = @jobDescription + ,@jobEnabled = @Enable + ,@databaseName = @currentDatabaseName + ,@jobAction = @currentJobAction + ,@scheduleEnabled = @ScheduleEnable + ,@scheduleFreqType = @ScheduleFreqType + ,@scheduleFreqInterval = @ScheduleFreqInterval + ,@scheduleFreqSubdayType = @ScheduleFreqSubdayType + ,@scheduleFreqSubdayInterval = @ScheduleFreqSubdayInterval + ,@scheduleFreqRelativeInterval = @ScheduleFreqRelativeInterval + ,@scheduleFreqRecurrenceFactor = @ScheduleFreqRecurrenceFactor + ,@scheduleActiveStartDate = @ScheduleActiveStartDay + ,@scheduleActiveEndDate = @ScheduleActiveEndDay + ,@scheduleActiveStartTime = @ScheduleActiveStartTime + ,@scheduleActiveEndTime = @ScheduleActiveEndTime + ,@jobTimeoutSec = @TimeoutSec + + SET @msg = 'Создано задание: ' + @jobName; + PRINT @msg; + END + + FETCH NEXT FROM job_templates_databases_cursor INTO @currentDatabaseName; + END + CLOSE job_templates_databases_cursor; + DEALLOCATE job_templates_databases_cursor; + END ELSE BEGIN + SET @jobAlreadyExists = 0; + SET @currentJobId = NULL; + SET @currentjobVersionDate = NULL; + + SELECT + @jobAlreadyExists = 1, + @currentJobId = sj.job_id, + @currentjobVersionDate = CASE WHEN sj.date_modified > sj.date_created THEN sj.date_modified ELSE sj.date_created END + FROM [msdb].[dbo].[sysjobs] sj + WHERE sj.[name] = @Name + + -- Если задание уже существует, но в настройках содержится более новая версия, + -- то удаляем старое задание и создаем заново + IF(@jobAlreadyExists = 1 AND (@force = 1 OR @VersionDate > @currentjobVersionDate)) + BEGIN + EXEC msdb.dbo.sp_delete_job + @job_id = @currentJobId, + @delete_unused_schedule = 1; + SET @msg = 'Удалено задание: ' + @Name; + PRINT @msg; + SET @jobAlreadyExists = 0; + END + + IF(@jobAlreadyExists = 0) + BEGIN + -- Задание создается единое на весь сервер + EXECUTE [dbo].[sp_CreateSimpleJob] + @jobName = @Name + ,@jobDescription = @Description + ,@jobEnabled = @Enable + ,@databaseName = 'SQLServerMaintenance' + ,@jobAction = @JobAction + ,@scheduleEnabled = @ScheduleEnable + ,@scheduleFreqType = @ScheduleFreqType + ,@scheduleFreqInterval = @ScheduleFreqInterval + ,@scheduleFreqSubdayType = @ScheduleFreqSubdayType + ,@scheduleFreqSubdayInterval = @ScheduleFreqSubdayInterval + ,@scheduleFreqRelativeInterval = @ScheduleFreqRelativeInterval + ,@scheduleFreqRecurrenceFactor = @ScheduleFreqRecurrenceFactor + ,@scheduleActiveStartDate = @ScheduleActiveStartDay + ,@scheduleActiveEndDate = @ScheduleActiveEndDay + ,@scheduleActiveStartTime = @ScheduleActiveStartTime + ,@scheduleActiveEndTime = @ScheduleActiveEndTime + ,@jobTimeoutSec = @TimeoutSec + + SET @msg = 'Создано задание: ' + @Name; + PRINT @msg; + END + END + + FETCH NEXT FROM job_templates_cursor + INTO @Id, @Enable, @ApplyTemplateQuery, @Name, @Description, @JobAction, @ScheduleEnable, + @ScheduleFreqType, @ScheduleFreqInterval, @ScheduleFreqSubdayType, @ScheduleFreqSubdayInterval, + @ScheduleFreqRelativeInterval, @ScheduleFreqRecurrenceFactor, @ScheduleActiveStartDay, + @ScheduleActiveEndDay, @ScheduleActiveStartTime, @ScheduleActiveEndTime, @VersionDate, @TimeoutSec; + END + CLOSE job_templates_cursor; + DEALLOCATE job_templates_cursor; +END \ No newline at end of file