Differenze
Queste sono le differenze tra la revisione selezionata e la versione attuale della pagina.
| Entrambe le parti precedenti la revisione Revisione precedente Prossima revisione | Revisione precedente | ||
| hobby:development:sql:database_scheduler [2026/08/14 10:33] – mauro.cortese | hobby:development:sql:database_scheduler [2026/08/14 14:00] (versione attuale) – mauro.cortese | ||
|---|---|---|---|
| Linea 4: | Linea 4: | ||
| \\ | \\ | ||
| \\ | \\ | ||
| - | <sxh sql> | ||
| - | /* ============================================================================ | ||
| - | | ||
| - | | ||
| - | | ||
| - | - dbo.SchedulerConfig | ||
| - | - dbo.SchedulerConfigTimes | ||
| - | - dbo.SchedulerLog | ||
| - | - dbo.sp_SchedulerDispatcher (main orchestrator, | ||
| - | - supporting nonclustered indexes for the dispatcher' | ||
| - | | + | Scheduler table-driven in SQL Server, basato su SQL Server Agent con una tabella di configurazione e una stored procedure " |
| - | EXEC dbo.sp_SchedulerDispatcher; | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | SET NOCOUNT ON; | + | === Architettura === |
| - | GO | + | - **Tabella di configurazione: |
| + | - **Tabella di log:** traccia le esecuzioni (successo/ | ||
| + | - **Stored procedure dispatcher: | ||
| + | - **Un solo SQL Server Agent Job:** che gira ogni minuto (o ogni X minuti) e chiama il dispatcher | ||
| + | Vantaggio: non serve creare un job Agent per ogni procedura, ne basta uno solo che orchestra tutto in base alla tabella. | ||
| - | /* ---------------------------------------------------------------------------- | ||
| - | 1) CONFIGURATION TABLE | ||
| - | | ||
| - | | ||
| - | ---------------------------------------------------------------------------- */ | ||
| - | IF OBJECT_ID(' | ||
| - | BEGIN | ||
| - | CREATE TABLE dbo.SchedulerConfig | ||
| - | ( | ||
| - | ID INT IDENTITY(1, | ||
| - | JobName NVARCHAR(100) NOT NULL, -- Human-friendly name, not used by the engine | ||
| - | ProcedureSchema SYSNAME NOT NULL DEFAULT ' | ||
| - | ProcedureName SYSNAME NOT NULL, -- Name of the stored procedure to execute | ||
| - | Parameters NVARCHAR(MAX) NULL, | ||
| - | -- 'INTERVAL' | + | === Creazione delle tabelle === |
| - | -- 'DAILY_TIME' | + | <sxh sql> |
| - | -- 'ONE_TIME' | + | -- ----------------------------------------------------------------------------- |
| - | ScheduleType VARCHAR(15) NOT NULL, | + | -- 1) CONFIGURATION TABLE |
| - | + | -- Stores the definition of every job. ScheduleType drives which of the other | |
| - | FrequencyMinutes INT NULL, -- required only when ScheduleType = 'INTERVAL' | + | -- scheduling columns/ |
| - | SpecificDateTime DATETIME NULL, -- required only when ScheduleType = 'ONE_TIME' | + | -- ----------------------------------------------------------------------------- |
| - | + | CREATE TABLE dbo.scheduler_config( | |
| - | StartTime TIME NULL, -- optional time-of-day window, applies to INTERVAL only | + | IdJob INT IDENTITY(1, |
| - | EndTime TIME NULL, | + | JobName |
| - | WeekDays VARCHAR(20) NULL, -- optional allowed weekdays (1=Monday..7=Sunday), | + | ProcedureSchema |
| - | + | ProcedureName | |
| - | IsActive BIT NOT NULL DEFAULT 1, -- enable/ | + | Parameters |
| - | IsRunning BIT NOT NULL DEFAULT 0, -- prevents overlapping executions of the same job | + | ScheduleType |
| - | LastRunDate DATETIME NULL, -- timestamp of the last time this job started | + | |
| - | + | | |
| - | CONSTRAINT | + | FrequencyMinutes |
| - | CHECK (ScheduleType IN (' | + | SpecificDateTime |
| - | ); | + | StartTime |
| - | END | + | EndTime |
| + | WeekDays | ||
| + | IsActive | ||
| + | IsRunning | ||
| + | LastRunDate | ||
| + | |||
| + | CONSTRAINT | ||
| + | CHECK (ScheduleType IN (' | ||
| + | ); | ||
| GO | GO | ||
| - | + | ||
| - | /* ---------------------------------------------------------------------------- | + | -- ----------------------------------------------------------------------------------- |
| - | | + | -- 2) FIXED DAILY RUN TIMES |
| - | | + | -- |
| - | | + | -- |
| - | ---------------------------------------------------------------------------- | + | -- ----------------------------------------------------------------------------------- |
| - | IF OBJECT_ID(' | + | CREATE TABLE dbo.scheduler_config_times |
| - | BEGIN | + | |
| - | CREATE TABLE dbo.SchedulerConfigTimes | + | ConfigID |
| - | ( | + | RunTime |
| - | ID INT IDENTITY(1, | + | LastRunDate |
| - | ConfigID INT NOT NULL REFERENCES dbo.SchedulerConfig(ID), | + | ); |
| - | RunTime TIME NOT NULL, -- e.g. ' | + | |
| - | LastRunDate DATE NULL -- last calendar date this specific slot fired; prevents re-firing within the same day | + | |
| - | ); | + | |
| - | END | + | |
| GO | GO | ||
| - | + | ||
| - | /* ---------------------------------------------------------------------------- | + | -- ---------------------------------------------------------------------------- |
| - | | + | -- 3) EXECUTION LOG |
| - | | + | -- |
| - | | + | -- |
| - | ---------------------------------------------------------------------------- | + | -- ---------------------------------------------------------------------------- |
| - | IF OBJECT_ID(' | + | CREATE TABLE dbo.scheduler_log |
| - | BEGIN | + | ID INT IDENTITY(1, |
| - | CREATE TABLE dbo.SchedulerLog | + | ConfigID |
| - | ( | + | ProcedureName |
| - | ID INT IDENTITY(1, | + | StartDate |
| - | ConfigID INT NOT NULL, -- FK back to SchedulerConfig.ID | + | EndDate |
| - | ProcedureName SYSNAME NOT NULL, -- denormalized for quick reading without a join | + | Outcome |
| - | StartDate DATETIME NOT NULL, | + | ErrorMessage |
| - | EndDate DATETIME NULL, | + | ); |
| - | Outcome VARCHAR(20) NULL, -- ' | + | |
| - | ErrorMessage NVARCHAR(MAX) NULL -- populated only when Outcome = ' | + | |
| - | ); | + | |
| - | END | + | |
| GO | GO | ||
| - | + | ||
| - | /* ---------------------------------------------------------------------------- | + | -- ----------------------------------------------------------------------------- |
| - | | + | -- 4) SUPPORTING INDEXES |
| - | | + | -- |
| - | | + | -- |
| - | | + | -- |
| - | ---------------------------------------------------------------------------- | + | -- ----------------------------------------------------------------------------- |
| - | IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = ' | + | CREATE NONCLUSTERED INDEX sX_Schedu_cerConfig_Dispatch |
| - | BEGIN | + | ON dbo.scheduler_config |
| - | CREATE NONCLUSTERED INDEX IX_SchedulerConfig_Dispatch | + | INCLUDE (ProcedureSchema, |
| - | ON dbo.SchedulerConfig | + | |
| - | INCLUDE (ProcedureSchema, | + | |
| - | END | + | |
| GO | GO | ||
| - | + | | |
| - | IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = ' | + | CREATE NONCLUSTERED INDEX sX_Schedu_cerConfigTimes_ConfigID |
| - | BEGIN | + | ON dbo.scheduler_config_times |
| - | CREATE NONCLUSTERED INDEX IX_SchedulerConfigTimes_ConfigID | + | INCLUDE (RunTime, LastRunDate); |
| - | ON dbo.SchedulerConfigTimes | + | |
| - | INCLUDE (RunTime, LastRunDate); | + | |
| - | END | + | |
| GO | GO | ||
| - | /* ---------------------------------------------------------------------------- | + | </sxh> |
| - | 5) DISPATCHER PROCEDURE | + | |
| - | Main orchestrator, | + | |
| - | Agent Job. Handles INTERVAL, DAILY_TIME and ONE_TIME scheduling. | + | |
| - | No CURSOR object is used: due jobs are collected in a table variable and | + | |
| - | | + | |
| - | EXEC needs its own isolated TRY/CATCH so one failing job never blocks | + | |
| - | the others. All status/log updates that don't need per-row isolation | + | |
| - | are done as set-based statements instead. | + | |
| - | ---------------------------------------------------------------------------- */ | + | |
| - | CREATE OR ALTER PROCEDURE dbo.sp_SchedulerDispatcher | + | |
| - | AS | + | |
| - | BEGIN | + | |
| - | SET NOCOUNT ON; | + | |
| - | DECLARE @Now DATETIME | + | === Dispatcher procedure === |
| - | DECLARE @Today DATE = CAST(@Now AS DATE); | + | |
| - | DECLARE @NowTime TIME = CAST(@Now AS TIME); | + | |
| - | DECLARE @TodayWeekDay VARCHAR(1) | + | |
| - | -- Working table with a row number, used to walk through the due jobs one | + | <sxh sql> |
| - | -- at a time without allocating a CURSOR object. LogID is filled later via | + | |
| - | -- an OUTPUT clause, avoiding a lookup query inside the loop. | + | |
| - | DECLARE @DueJobs TABLE | + | |
| - | ( | + | |
| - | RowNum INT IDENTITY(1, | + | |
| - | ConfigID INT, | + | |
| - | ProcedureSchema SYSNAME, | + | |
| - | ProcedureName SYSNAME, | + | |
| - | Parameters NVARCHAR(MAX), | + | |
| - | TimeSlotID INT NULL, | + | |
| - | LogID INT NULL | + | |
| - | ); | + | |
| - | + | ||
| - | -- 1) INTERVAL jobs: due when enough minutes have passed since LastRunDate, | + | |
| - | -- and (if set) we're inside the allowed time window and weekday list. | + | |
| - | INSERT INTO @DueJobs (ConfigID, ProcedureSchema, | + | |
| - | SELECT ID, ProcedureSchema, | + | |
| - | FROM dbo.SchedulerConfig | + | |
| - | WHERE IsActive = 1 | + | |
| - | AND IsRunning = 0 | + | |
| - | AND ScheduleType = ' | + | |
| - | AND (LastRunDate IS NULL OR DATEDIFF(MINUTE, | + | |
| - | AND (StartTime IS NULL OR @NowTime >= StartTime) | + | |
| - | AND (EndTime IS NULL OR @NowTime | + | |
| - | AND (WeekDays IS NULL OR WeekDays LIKE ' | + | |
| - | + | ||
| - | -- 2) DAILY_TIME jobs: due when current time has reached a configured | + | |
| - | -- RunTime slot and that slot hasn't already fired today. | + | |
| - | INSERT INTO @DueJobs (ConfigID, ProcedureSchema, | + | |
| - | SELECT c.ID, c.ProcedureSchema, | + | |
| - | FROM dbo.SchedulerConfig c | + | |
| - | INNER JOIN dbo.SchedulerConfigTimes t ON t.ConfigID = c.ID | + | |
| - | WHERE c.IsActive = 1 | + | |
| - | AND c.IsRunning = 0 | + | |
| - | AND c.ScheduleType = ' | + | |
| - | AND (c.WeekDays IS NULL OR c.WeekDays LIKE ' | + | |
| - | AND (t.LastRunDate IS NULL OR t.LastRunDate < @Today) | + | |
| - | AND CONVERT(CHAR(5), | + | |
| - | + | ||
| - | -- 3) ONE_TIME jobs: due once, when current datetime has reached | + | |
| - | -- SpecificDateTime and the job has never run before. | + | |
| - | INSERT INTO @DueJobs (ConfigID, ProcedureSchema, | + | |
| - | SELECT ID, ProcedureSchema, | + | |
| - | FROM dbo.SchedulerConfig | + | |
| - | WHERE IsActive = 1 | + | |
| - | AND IsRunning = 0 | + | |
| - | AND ScheduleType = ' | + | |
| - | AND LastRunDate IS NULL | + | |
| - | AND @Now >= SpecificDateTime; | + | |
| - | + | ||
| - | -- Nothing due: exit early, no further statements needed. | + | |
| - | IF NOT EXISTS (SELECT 1 FROM @DueJobs) | + | |
| - | RETURN; | + | |
| - | + | ||
| - | -- Mark every due job as running and stamp LastRunDate in one set-based UPDATE, | + | |
| - | -- instead of doing it row-by-row inside the loop. | + | |
| - | UPDATE c | + | |
| - | SET c.IsRunning = 1, c.LastRunDate = GETDATE() | + | |
| - | FROM dbo.SchedulerConfig c | + | |
| - | INNER JOIN @DueJobs d ON d.ConfigID = c.ID; | + | |
| - | + | ||
| - | -- Same for DAILY_TIME slots: stamp all fired slots at once. | + | |
| - | UPDATE t | + | |
| - | SET t.LastRunDate = @Today | + | |
| - | FROM dbo.SchedulerConfigTimes t | + | |
| - | INNER JOIN @DueJobs d ON d.TimeSlotID = t.ID | + | |
| - | WHERE d.TimeSlotID IS NOT NULL; | + | |
| - | + | ||
| - | -- Bulk-insert one ' | + | |
| - | -- IDs directly via OUTPUT so the loop below needs no lookup query. | + | |
| - | DECLARE @LogMap TABLE (ConfigID INT, LogID INT); | + | |
| - | + | ||
| - | INSERT INTO dbo.SchedulerLog (ConfigID, ProcedureName, | + | |
| - | OUTPUT inserted.ConfigID, | + | |
| - | SELECT ConfigID, ProcedureName, | + | |
| - | FROM @DueJobs; | + | |
| - | + | ||
| - | UPDATE d | + | |
| - | SET d.LogID = m.LogID | + | |
| - | FROM @DueJobs d | + | |
| - | INNER JOIN @LogMap m ON m.ConfigID = d.ConfigID; | + | |
| - | + | ||
| - | -- Execute each due procedure individually: | + | |
| - | -- handling genuinely requires row-by-row processing, so this is a plain | + | |
| - | -- WHILE loop keyed on RowNum rather than a CURSOR. | + | |
| - | DECLARE @i INT = 1, @Count INT = (SELECT COUNT(*) FROM @DueJobs); | + | |
| - | DECLARE @ConfigID INT, @Schema SYSNAME, @Name SYSNAME, @Params NVARCHAR(MAX), | + | |
| - | DECLARE @SQL NVARCHAR(MAX); | + | |
| - | + | ||
| - | WHILE @i <= @Count | + | |
| - | BEGIN | + | |
| - | SELECT | + | |
| - | @ConfigID = ConfigID, | + | |
| - | @Schema = ProcedureSchema, | + | |
| - | @Name = ProcedureName, | + | |
| - | @Params = Parameters, | + | |
| - | @LogID = LogID | + | |
| - | FROM @DueJobs | + | |
| - | WHERE RowNum = @i; | + | |
| - | + | ||
| - | -- QUOTENAME() protects schema/ | + | |
| - | -- reserved-word issues; parameters, if present, are appended as-is. | + | |
| - | SET @SQL = QUOTENAME(@Schema) + ' | + | |
| - | + CASE WHEN @Params IS NOT NULL THEN ' ' + @Params ELSE '' | + | |
| - | + | ||
| - | BEGIN TRY | + | |
| - | EXEC (@SQL); | + | |
| - | + | ||
| - | UPDATE dbo.SchedulerLog SET EndDate = GETDATE(), Outcome = ' | + | |
| - | END TRY | + | |
| - | BEGIN CATCH | + | |
| - | -- One failing procedure never stops the loop: error is logged, | + | |
| - | -- next job proceeds. | + | |
| - | UPDATE dbo.SchedulerLog | + | |
| - | SET EndDate = GETDATE(), Outcome = ' | + | |
| - | WHERE ID = @LogID; | + | |
| - | END CATCH | + | |
| - | + | ||
| - | -- Always release the running flag, whether the procedure succeeded or failed. | + | |
| - | UPDATE dbo.SchedulerConfig SET IsRunning = 0 WHERE ID = @ConfigID; | + | |
| - | + | ||
| - | SET @i += 1; | + | |
| - | END | + | |
| - | END | + | |
| - | GO | + | |
| - | /* ---------------------------------------------------------------------------- | + | -- ---------------------------------------------------------------------------- |
| - | 6) SAMPLE CONFIGURATION ROWS (optional — comment out or delete if not needed) | + | -- DISPATCHER PROCEDURE |
| - | ---------------------------------------------------------------------------- */ | + | -- Main orchestrator, |
| - | -- INSERT INTO dbo.SchedulerConfig | + | -- Agent Job. Handles INTERVAL, DAILY_TIME and ONE_TIME scheduling. |
| + | -- No CURSOR object is used: due jobs are collected in a table variable and | ||
| + | -- walked with a WHILE loop, which is required only because each dynamic | ||
| + | -- EXEC needs its own isolated TRY/CATCH so one failing job never blocks | ||
| + | -- the others. All status/log updates that don't need per-row isolation | ||
| + | -- are done as set-based statements instead. | ||
| + | -- -------------------------------------------------------------------------- | ||
| + | -- SAMPLE CONFIGURATION ROWS | ||
| + | -- -------------------------------------------------------------------------- */ | ||
| + | -- INSERT INTO dbo.scheduler_config | ||
| -- VALUES (' | -- VALUES (' | ||
| + | |||
| -- DECLARE @NewConfigID INT; | -- DECLARE @NewConfigID INT; | ||
| - | -- INSERT INTO dbo.SchedulerConfig | + | -- INSERT INTO dbo.scheduler_config |
| -- VALUES (' | -- VALUES (' | ||
| -- SET @NewConfigID = SCOPE_IDENTITY(); | -- SET @NewConfigID = SCOPE_IDENTITY(); | ||
| - | -- INSERT INTO dbo.SchedulerConfigTimes | + | -- INSERT INTO dbo.scheduler_config_times |
| -- VALUES (@NewConfigID, | -- VALUES (@NewConfigID, | ||
| - | + | ||
| - | -- INSERT INTO dbo.SchedulerConfig | + | -- INSERT INTO dbo.scheduler_config |
| -- VALUES (' | -- VALUES (' | ||
| + | -- ---------------------------------------------------------------------------- | ||
| + | CREATE OR ALTER PROCEDURE dbo.sp_scheduler_dispatcher | ||
| + | AS | ||
| + | BEGIN | ||
| + | SET NOCOUNT ON; | ||
| + | |||
| + | DECLARE @Now DATETIME | ||
| + | DECLARE @Today DATE = CAST(@Now AS DATE); | ||
| + | DECLARE @NowTime TIME = CAST(@Now AS TIME); | ||
| + | DECLARE @TodayWeekDay VARCHAR(1) = CAST(DATEPART(WEEKDAY, | ||
| + | |||
| + | -- Working table with a row number, used to walk through the due jobs one | ||
| + | -- at a time without allocating a CURSOR object. LogID is filled later via | ||
| + | -- an OUTPUT clause, avoiding a lookup query inside the loop. | ||
| + | DECLARE @DueJobs TABLE ( | ||
| + | RowNum | ||
| + | ConfigID | ||
| + | ProcedureSchema | ||
| + | ProcedureName | ||
| + | Parameters | ||
| + | TimeSlotID | ||
| + | LogID INT NULL | ||
| + | ); | ||
| + | |||
| + | -- 1) INTERVAL jobs: due when enough minutes have passed since LastRunDate, | ||
| + | -- and (if set) we're inside the allowed time window and weekday list. | ||
| + | INSERT INTO @DueJobs (ConfigID, ProcedureSchema, | ||
| + | SELECT ID, ProcedureSchema, | ||
| + | FROM dbo.scheduler_config | ||
| + | WHERE IsActive = 1 | ||
| + | AND IsRunning = 0 | ||
| + | AND ScheduleType = ' | ||
| + | AND (LastRunDate IS NULL OR DATEDIFF(MINUTE, | ||
| + | AND (StartTime IS NULL OR @NowTime >= StartTime) | ||
| + | AND (EndTime IS NULL OR @NowTime <= EndTime) | ||
| + | AND (WeekDays IS NULL OR WeekDays LIKE ' | ||
| + | |||
| + | -- 2) DAILY_TIME jobs: due when current time has reached a configured | ||
| + | -- RunTime slot and that slot hasn't already fired today. | ||
| + | INSERT INTO @DueJobs (ConfigID, ProcedureSchema, | ||
| + | SELECT c.ID, c.ProcedureSchema, | ||
| + | FROM dbo.scheduler_config c | ||
| + | INNER JOIN dbo.scheduler_config_times t ON t.ConfigID = c.ID | ||
| + | WHERE c.IsActive = 1 | ||
| + | AND c.IsRunning = 0 | ||
| + | AND c.ScheduleType = ' | ||
| + | AND (c.WeekDays IS NULL OR c.WeekDays LIKE ' | ||
| + | AND (t.LastRunDate IS NULL OR t.LastRunDate < @Today) | ||
| + | AND CONVERT(CHAR(5), | ||
| + | |||
| + | -- 3) ONE_TIME jobs: due once, when current datetime has reached | ||
| + | -- SpecificDateTime and the job has never run before. | ||
| + | INSERT INTO @DueJobs (ConfigID, ProcedureSchema, | ||
| + | SELECT ID, ProcedureSchema, | ||
| + | FROM dbo.scheduler_config | ||
| + | WHERE IsActive = 1 | ||
| + | AND IsRunning = 0 | ||
| + | AND ScheduleType = ' | ||
| + | AND LastRunDate IS NULL | ||
| + | AND @Now >= SpecificDateTime; | ||
| + | |||
| + | -- Nothing due: exit early, no further statements needed. | ||
| + | IF NOT EXISTS (SELECT * FROM @DueJobs) | ||
| + | RETURN; | ||
| + | |||
| + | -- Mark every due job as running and stamp LastRunDate in one set-based UPDATE, | ||
| + | -- instead of doing it row-by-row inside the loop. | ||
| + | UPDATE c SET | ||
| + | | ||
| + | , | ||
| + | FROM dbo.scheduler_config c | ||
| + | INNER JOIN @DueJobs d ON d.ConfigID = c.ID; | ||
| + | |||
| + | -- Same for DAILY_TIME slots: stamp all fired slots at once. | ||
| + | UPDATE t SET | ||
| + | t.LastRunDate = @Today | ||
| + | FROM dbo.scheduler_config_times t | ||
| + | INNER JOIN @DueJobs d ON d.TimeSlotID = t.ID | ||
| + | WHERE d.TimeSlotID IS NOT NULL; | ||
| + | |||
| + | -- Bulk-insert one ' | ||
| + | -- IDs directly via OUTPUT so the loop below needs no lookup query. | ||
| + | DECLARE @LogMap TABLE ( | ||
| + | | ||
| + | ,LogID INT | ||
| + | ); | ||
| + | |||
| + | INSERT INTO dbo.scheduler_log (ConfigID, ProcedureName, | ||
| + | | ||
| + | | ||
| + | |||
| + | UPDATE d SET | ||
| + | d.LogID = m.LogID | ||
| + | FROM @DueJobs d | ||
| + | INNER JOIN @LogMap m ON m.ConfigID = d.ConfigID; | ||
| + | |||
| + | -- Execute each due procedure individually: | ||
| + | -- handling genuinely requires row-by-row processing, so this is a plain | ||
| + | -- WHILE loop keyed on RowNum rather than a CURSOR. | ||
| + | DECLARE @i INT = 1; | ||
| + | DECLARE @Count INT = (SELECT COUNT(*) FROM @DueJobs); | ||
| + | DECLARE @ConfigID INT; | ||
| + | DECLARE @Schema SYSNAME; | ||
| + | DECLARE @Name SYSNAME; | ||
| + | DECLARE @Params NVARCHAR(MAX) | ||
| + | DECLARE @LogID INT; | ||
| + | DECLARE @SqlCmd NVARCHAR(MAX); | ||
| + | |||
| + | WHILE @i <= @Count | ||
| + | BEGIN | ||
| + | SELECT | ||
| + | @ConfigID = ConfigID, | ||
| + | @Schema | ||
| + | @Name = ProcedureName, | ||
| + | @Params | ||
| + | @LogID | ||
| + | FROM @DueJobs | ||
| + | WHERE RowNum = @i; | ||
| + | |||
| + | -- QUOTENAME() protects schema/ | ||
| + | -- reserved-word issues; parameters, if present, are appended as-is. | ||
| + | SET @SqlCmd = QUOTENAME(@Schema) + ' | ||
| + | |||
| + | BEGIN TRY | ||
| + | EXEC (@SqlCmd); | ||
| + | UPDATE dbo.scheduler_log SET EndDate = GETDATE(), Outcome = ' | ||
| + | END TRY | ||
| + | BEGIN CATCH | ||
| + | -- One failing procedure never stops the loop: error is logged, | ||
| + | -- next job proceeds. | ||
| + | UPDATE dbo.scheduler_log | ||
| + | SET EndDate = GETDATE(), Outcome = ' | ||
| + | WHERE ID = @LogID; | ||
| + | END CATCH | ||
| + | |||
| + | -- Always release the running flag, whether the procedure succeeded or failed. | ||
| + | UPDATE dbo.scheduler_config SET IsRunning = 0 WHERE ID = @ConfigID; | ||
| + | |||
| + | SET @i += 1; | ||
| + | END | ||
| + | END | ||
| + | GO | ||
| </ | </ | ||