2611 字
13 分鐘
SQL Agent 多 Job 匯出怎麼做:一支腳本重建多個排程與 Step
結論
用一支腳本批次匯出並重建 SQL Agent Job,連 step、schedule、通知一起帶走,省時間又避免漏設定。
適合用在哪裡
- DB 搬機 / 環境切換(DEV → UAT → PROD)
- 多個 SQL Agent Job 要同步重建
- 想避免手動建立 Job 遺漏 step 或 schedule
- DBA / 後端工程師做維運自動化
流程步驟
1. 定義參數與 Job 清單
先集中管理會變動的設定(DB、Owner、是否覆蓋),再用表變數定義要匯出的 Job 名單,支援改名。這樣做可以一次處理多個 Job,不用重複操作。
DECLARE @OverrideDatabaseName SYSNAME = N'你的新DB'; -- 指定新 DBDECLARE @OverrideOwnerLoginName SYSNAME = N'sa'; -- 指定 owner
INSERT INTO @Jobs (SourceJobName, TargetJobName)VALUES (N'Job_A', N'Job_A_New'), (N'Job_B', NULL);2. 先做檢查,避免匯出錯誤
在產生腳本前先驗證來源 Job 是否存在,以及目標名稱是否重複。這能避免搬到一半才發現錯誤。
IF EXISTS ( SELECT 1 FROM @Jobs j LEFT JOIN msdb.dbo.sysjobs sj ON sj.name = j.SourceJobName WHERE sj.job_id IS NULL)BEGIN RAISERROR(N'Job 不存在', 16, 1); RETURN;END;3. 動態組出 Job / Step / Schedule
透過系統表 msdb.dbo.sysjobs、sysjobsteps、sysschedules,逐一組出完整腳本。這樣重建後的 Job 行為會與原本一致。
EXEC msdb.dbo.sp_add_job @job_name = N'Job_A_New', @enabled = 1;
EXEC msdb.dbo.sp_add_jobstep @job_id = @jobId, @step_name = N'Step1', @command = N'SELECT 1;', @database_name = N'你的新DB';4. 輸出 Warning 與檢查資訊
腳本會額外標示風險,例如非 TSQL step、Proxy、寫死 DB 名稱等,讓你在部署前能先檢查。
-- WARNING: 非 TSQL step-- WARNING: 使用 Proxy-- WARNING: command 可能寫死 DB補充
- command 內若寫死 DB 名稱,腳本不會自動改,需手動調整
- Proxy / SSIS / PowerShell step 要先在新環境建立對應設定
- 正式環境建議不要直接覆蓋 Job,先確認再執行
指令 / 範例整理
Click to expand
USE msdb;GO
SET NOCOUNT ON;
--------------------------------------------------------------------------------- 參數區-------------------------------------------------------------------------------DECLARE @OverrideDatabaseName SYSNAME = N'你的新DB'; -- NULL = 不覆蓋 step database_nameDECLARE @OverrideOwnerLoginName SYSNAME = N'sa'; -- NULL = 沿用原 ownerDECLARE @OverrideOperatorName SYSNAME = NULL; -- NULL = 沿用原 operatorDECLARE @DropIfExists BIT = 1; -- 1=若同名 Job 已存在,先刪除再重建DECLARE @AttachToLocalServer BIT = 1; -- 1=最後執行 sp_add_jobserver 綁到 (LOCAL)
DECLARE @CRLF NVARCHAR(10) = CHAR(13) + CHAR(10) + '/**/';DECLARE @FinalSQL NVARCHAR(MAX) = N'';DECLARE @Header NVARCHAR(MAX) = N'';
--------------------------------------------------------------------------------- 指定要匯出的 Jobs-------------------------------------------------------------------------------DECLARE @Jobs TABLE( RowNum INT IDENTITY(1,1) PRIMARY KEY, SourceJobName SYSNAME NOT NULL, TargetJobName SYSNAME NULL);
-- ===== 在這裡填你要匯出的 Jobs =====INSERT INTO @Jobs (SourceJobName, TargetJobName)VALUES (N'Job_A', N'Job_A_New'), (N'Job_B', NULL), (N'Job_C', N'Job_C_New');-- ==================================
--------------------------------------------------------------------------------- 基本檢查:來源 Job 是否存在-------------------------------------------------------------------------------IF EXISTS( SELECT 1 FROM @Jobs j LEFT JOIN msdb.dbo.sysjobs sj ON sj.name = j.SourceJobName WHERE sj.job_id IS NULL)BEGIN SELECT j.SourceJobName FROM @Jobs j LEFT JOIN msdb.dbo.sysjobs sj ON sj.name = j.SourceJobName WHERE sj.job_id IS NULL;
RAISERROR(N'上面列出的 SourceJobName 在 msdb.dbo.sysjobs 中找不到,請先修正。', 16, 1); RETURN;END;
--------------------------------------------------------------------------------- 檢查 TargetJobName 是否重複-------------------------------------------------------------------------------IF EXISTS( SELECT 1 FROM ( SELECT ISNULL(TargetJobName, SourceJobName) AS FinalJobName FROM @Jobs ) x GROUP BY x.FinalJobName HAVING COUNT(*) > 1)BEGIN SELECT x.FinalJobName, COUNT(*) AS Cnt FROM ( SELECT ISNULL(TargetJobName, SourceJobName) AS FinalJobName FROM @Jobs ) x GROUP BY x.FinalJobName HAVING COUNT(*) > 1;
RAISERROR(N'上面列出的 TargetJobName / FinalJobName 有重複,會互相覆蓋,請先修正。', 16, 1); RETURN;END;
--------------------------------------------------------------------------------- Header-------------------------------------------------------------------------------SET @Header =N'/* =====================================================================' + @CRLF +N' SQL Agent Multi-Job Export Script' + @CRLF +N' Generated : ' + CONVERT(NVARCHAR(19), GETDATE(), 120) + @CRLF +N' Notes:' + @CRLF +N' 1. 請先確認新環境是否存在對應 Login / Operator / Proxy / Database。' + @CRLF +N' 2. 若 step command 內文有寫死舊 DB,需人工調整。' + @CRLF +N' 3. 建議先建立後停用 Job,手動測試 step 無誤再啟用 schedule。' + @CRLF +N'===================================================================== */' + @CRLF + @CRLF;
SET @FinalSQL = @Header;
--------------------------------------------------------------------------------- 先準備待處理 Job 清單-------------------------------------------------------------------------------DECLARE @JobList TABLE( RowNum INT PRIMARY KEY, SourceJobName SYSNAME NOT NULL, TargetJobName SYSNAME NOT NULL, JobId UNIQUEIDENTIFIER NOT NULL);
INSERT INTO @JobList (RowNum, SourceJobName, TargetJobName, JobId)SELECT j.RowNum, j.SourceJobName, ISNULL(j.TargetJobName, j.SourceJobName) AS TargetJobName, sj.job_idFROM @Jobs jJOIN msdb.dbo.sysjobs sj ON sj.name = j.SourceJobName;
--------------------------------------------------------------------------------- 逐一組 Script-------------------------------------------------------------------------------DECLARE @RowNum INT, @SourceJobName SYSNAME, @TargetJobName SYSNAME, @JobId UNIQUEIDENTIFIER, @JobSQL NVARCHAR(MAX), @Warnings NVARCHAR(MAX), @Tmp NVARCHAR(MAX);
DECLARE JobCursor CURSOR FAST_FORWARD FORSELECT RowNum, SourceJobName, TargetJobName, JobIdFROM @JobListORDER BY RowNum;
OPEN JobCursor;FETCH NEXT FROM JobCursor INTO @RowNum, @SourceJobName, @TargetJobName, @JobId;
WHILE @@FETCH_STATUS = 0BEGIN SET @JobSQL = N''; SET @Warnings = N''; SET @Tmp = N'';
--------------------------------------------------------------------------- -- Job 區塊標頭 --------------------------------------------------------------------------- SET @JobSQL = @JobSQL + N'/* =====================================================================' + @CRLF + N' Job #' + CAST(@RowNum AS NVARCHAR(10)) + N'' + @CRLF + N' Source Job : ' + REPLACE(@SourceJobName, '*/', '* /') + @CRLF + N' Target Job : ' + REPLACE(@TargetJobName, '*/', '* /') + @CRLF + N'===================================================================== */' + @CRLF + @CRLF;
--------------------------------------------------------------------------- -- Warning 區 --------------------------------------------------------------------------- IF EXISTS ( SELECT 1 FROM msdb.dbo.sysjobsteps WHERE job_id = @JobId GROUP BY job_id HAVING COUNT(*) > 1 ) BEGIN SET @Warnings = @Warnings + N'-- WARNING: 此 Job 含多個 steps,請逐一確認 step 間跳轉。' + @CRLF; END;
IF EXISTS ( SELECT 1 FROM msdb.dbo.sysjobsteps WHERE job_id = @JobId AND subsystem <> N'TSQL' ) BEGIN SET @Warnings = @Warnings + N'-- WARNING: 含非 TSQL steps(如 CmdExec / PowerShell / SSIS),新環境需確認元件與權限。' + @CRLF; END;
IF EXISTS ( SELECT 1 FROM msdb.dbo.sysjobsteps WHERE job_id = @JobId AND proxy_id IS NOT NULL AND proxy_id <> 0 ) BEGIN SET @Warnings = @Warnings + N'-- WARNING: 此 Job 使用 Proxy,新環境必須先建立對應 Proxy/Credential。' + @CRLF; END;
IF EXISTS ( SELECT 1 FROM msdb.dbo.sysjobsteps WHERE job_id = @JobId AND ( command LIKE N'%USE [%]%' OR command LIKE N'%].dbo.%' OR command LIKE N'%.dbo.%' ) ) BEGIN SET @Warnings = @Warnings + N'-- WARNING: 某些 step command 可能寫死資料庫名稱,請人工檢查 command 內容。' + @CRLF; END;
IF @Warnings <> N'' BEGIN SET @JobSQL = @JobSQL + N'/* Pre-check warnings */' + @CRLF + @Warnings + @CRLF; END;
--------------------------------------------------------------------------- -- 刪除同名 Job(可選) --------------------------------------------------------------------------- SET @JobSQL = @JobSQL + N'USE msdb;' + @CRLF + N'GO' + @CRLF;
IF @DropIfExists = 1 BEGIN SET @JobSQL = @JobSQL + N'IF EXISTS (SELECT 1 FROM msdb.dbo.sysjobs WHERE name = N''' + REPLACE(@TargetJobName, '''', '''''') + N''')' + @CRLF + N'BEGIN' + @CRLF + N' EXEC msdb.dbo.sp_delete_job @job_name = N''' + REPLACE(@TargetJobName, '''', '''''') + N''';' + @CRLF + N'END' + @CRLF + N'GO' + @CRLF; END;
SET @JobSQL = @JobSQL + N'DECLARE @jobId BINARY(16);' + @CRLF + @CRLF;
--------------------------------------------------------------------------- -- sp_add_job --------------------------------------------------------------------------- SELECT @Tmp = N'EXEC msdb.dbo.sp_add_job' + @CRLF + N' @job_name = N''' + REPLACE(@TargetJobName, '''', '''''') + N''',' + @CRLF + N' @enabled = ' + CAST(j.enabled AS NVARCHAR(10)) + N',' + @CRLF + N' @notify_level_eventlog = ' + CAST(j.notify_level_eventlog AS NVARCHAR(10)) + N',' + @CRLF + N' @notify_level_email = ' + CAST(j.notify_level_email AS NVARCHAR(10)) + N',' + @CRLF + N' @notify_level_netsend = ' + CAST(j.notify_level_netsend AS NVARCHAR(10)) + N',' + @CRLF + N' @notify_level_page = ' + CAST(j.notify_level_page AS NVARCHAR(10)) + N',' + @CRLF + N' @delete_level = ' + CAST(j.delete_level AS NVARCHAR(10)) + N',' + @CRLF + N' @description = N''' + REPLACE(ISNULL(j.description, N''), '''', '''''') + N''',' + @CRLF + N' @category_name = N''' + REPLACE(ISNULL(c.name, N'[Uncategorized (Local)]'), '''', '''''') + N''',' + @CRLF + N' @owner_login_name = N''' + REPLACE(ISNULL(@OverrideOwnerLoginName, SUSER_SNAME(j.owner_sid)), '''', '''''') + N''',' + @CRLF + N' @job_id = @jobId OUTPUT;' + @CRLF + @CRLF FROM msdb.dbo.sysjobs j LEFT JOIN msdb.dbo.syscategories c ON j.category_id = c.category_id WHERE j.job_id = @JobId;
SET @JobSQL = @JobSQL + ISNULL(@Tmp, N'');
--------------------------------------------------------------------------- -- Steps --------------------------------------------------------------------------- SET @Tmp = N'';
SELECT @Tmp = @Tmp + N'EXEC msdb.dbo.sp_add_jobstep' + @CRLF + N' @job_id = @jobId,' + @CRLF + N' @step_id = ' + CAST(s.step_id AS NVARCHAR(10)) + N',' + @CRLF + N' @step_name = N''' + REPLACE(s.step_name, '''', '''''') + N''',' + @CRLF + N' @subsystem = N''' + REPLACE(s.subsystem, '''', '''''') + N''',' + @CRLF + N' @command = N''' + REPLACE(ISNULL(s.command, N''), '''', '''''') + N''',' + @CRLF + CASE WHEN s.additional_parameters IS NULL THEN N'' ELSE N' @additional_parameters = N''' + REPLACE(s.additional_parameters, '''', '''''') + N''',' + @CRLF END + N' @cmdexec_success_code = ' + CAST(ISNULL(s.cmdexec_success_code, 0) AS NVARCHAR(10)) + N',' + @CRLF + N' @on_success_action = ' + CAST(s.on_success_action AS NVARCHAR(10)) + N',' + @CRLF + N' @on_success_step_id = ' + CAST(s.on_success_step_id AS NVARCHAR(10)) + N',' + @CRLF + N' @on_fail_action = ' + CAST(s.on_fail_action AS NVARCHAR(10)) + N',' + @CRLF + N' @on_fail_step_id = ' + CAST(s.on_fail_step_id AS NVARCHAR(10)) + N',' + @CRLF + CASE WHEN s.server IS NULL THEN N'' ELSE N' @server = N''' + REPLACE(s.server, '''', '''''') + N''',' + @CRLF END + N' @database_name = ' + CASE WHEN ISNULL(@OverrideDatabaseName, s.database_name) IS NULL THEN N'NULL' ELSE N'N''' + REPLACE(ISNULL(@OverrideDatabaseName, s.database_name), '''', '''''') + N'''' END + N',' + @CRLF + N' @database_user_name = ' + CASE WHEN s.database_user_name IS NULL THEN N'NULL' ELSE N'N''' + REPLACE(s.database_user_name, '''', '''''') + N'''' END + N',' + @CRLF + N' @retry_attempts = ' + CAST(ISNULL(s.retry_attempts, 0) AS NVARCHAR(10)) + N',' + @CRLF + N' @retry_interval = ' + CAST(ISNULL(s.retry_interval, 0) AS NVARCHAR(10)) + N',' + @CRLF + N' @os_run_priority = ' + CAST(ISNULL(s.os_run_priority, 0) AS NVARCHAR(10)) + N',' + @CRLF + N' @output_file_name = ' + CASE WHEN s.output_file_name IS NULL THEN N'NULL' ELSE N'N''' + REPLACE(s.output_file_name, '''', '''''') + N'''' END + N',' + @CRLF + CASE WHEN p.name IS NULL OR p.name = N'' THEN N'' ELSE N' @proxy_name = N''' + REPLACE(p.name, '''', '''''') + N''',' + @CRLF END + N' @flags = ' + CAST(ISNULL(s.flags, 0) AS NVARCHAR(10)) + N';' + @CRLF + @CRLF FROM msdb.dbo.sysjobsteps s LEFT JOIN msdb.dbo.sysproxies p ON s.proxy_id = p.proxy_id WHERE s.job_id = @JobId ORDER BY s.step_id;
SET @JobSQL = @JobSQL + ISNULL(@Tmp, N'');
--------------------------------------------------------------------------- -- start_step_id --------------------------------------------------------------------------- SELECT @Tmp = N'EXEC msdb.dbo.sp_update_job' + @CRLF + N' @job_id = @jobId,' + @CRLF + N' @start_step_id = ' + CAST(j.start_step_id AS NVARCHAR(10)) + N';' + @CRLF + @CRLF FROM msdb.dbo.sysjobs j WHERE j.job_id = @JobId;
SET @JobSQL = @JobSQL + ISNULL(@Tmp, N'');
--------------------------------------------------------------------------- -- schedules --------------------------------------------------------------------------- SET @Tmp = N'';
SELECT @Tmp = @Tmp + N'EXEC msdb.dbo.sp_add_jobschedule' + @CRLF + N' @job_id = @jobId,' + @CRLF + N' @name = N''' + REPLACE(sch.name, '''', '''''') + N''',' + @CRLF + N' @enabled = ' + CAST(sch.enabled AS NVARCHAR(10)) + N',' + @CRLF + N' @freq_type = ' + CAST(sch.freq_type AS NVARCHAR(10)) + N',' + @CRLF + N' @freq_interval = ' + CAST(sch.freq_interval AS NVARCHAR(10)) + N',' + @CRLF + N' @freq_subday_type = ' + CAST(sch.freq_subday_type AS NVARCHAR(10)) + N',' + @CRLF + N' @freq_subday_interval = ' + CAST(sch.freq_subday_interval AS NVARCHAR(10)) + N',' + @CRLF + N' @freq_relative_interval = ' + CAST(sch.freq_relative_interval AS NVARCHAR(10)) + N',' + @CRLF + N' @freq_recurrence_factor = ' + CAST(sch.freq_recurrence_factor AS NVARCHAR(10)) + N',' + @CRLF + N' @active_start_date = ' + CAST(sch.active_start_date AS NVARCHAR(20)) + N',' + @CRLF + N' @active_end_date = ' + CAST(sch.active_end_date AS NVARCHAR(20)) + N',' + @CRLF + N' @active_start_time = ' + CAST(sch.active_start_time AS NVARCHAR(20)) + N',' + @CRLF + N' @active_end_time = ' + CAST(sch.active_end_time AS NVARCHAR(20)) + N';' + @CRLF + @CRLF FROM ( SELECT DISTINCT sch.name, sch.enabled, sch.freq_type, sch.freq_interval, sch.freq_subday_type, sch.freq_subday_interval, sch.freq_relative_interval, sch.freq_recurrence_factor, sch.active_start_date, sch.active_end_date, sch.active_start_time, sch.active_end_time FROM msdb.dbo.sysjobschedules js JOIN msdb.dbo.sysschedules sch ON js.schedule_id = sch.schedule_id WHERE js.job_id = @JobId ) sch ORDER BY sch.name;
SET @JobSQL = @JobSQL + ISNULL(@Tmp, N'');
--------------------------------------------------------------------------- -- operator / notify --------------------------------------------------------------------------- SELECT @Tmp = N'EXEC msdb.dbo.sp_update_job' + @CRLF + N' @job_id = @jobId,' + @CRLF + N' @notify_level_email = ' + CAST(j.notify_level_email AS NVARCHAR(10)) + N',' + @CRLF + N' @notify_level_netsend = ' + CAST(j.notify_level_netsend AS NVARCHAR(10)) + N',' + @CRLF + N' @notify_level_page = ' + CAST(j.notify_level_page AS NVARCHAR(10)) + CASE WHEN ISNULL(@OverrideOperatorName, op_email.name) IS NOT NULL THEN N',' + @CRLF + N' @notify_email_operator_name = N''' + REPLACE(ISNULL(@OverrideOperatorName, op_email.name), '''', '''''') + N'''' ELSE N'' END + CASE WHEN ISNULL(@OverrideOperatorName, op_netsend.name) IS NOT NULL THEN N',' + @CRLF + N' @notify_netsend_operator_name = N''' + REPLACE(ISNULL(@OverrideOperatorName, op_netsend.name), '''', '''''') + N'''' ELSE N'' END + CASE WHEN ISNULL(@OverrideOperatorName, op_page.name) IS NOT NULL THEN N',' + @CRLF + N' @notify_page_operator_name = N''' + REPLACE(ISNULL(@OverrideOperatorName, op_page.name), '''', '''''') + N'''' ELSE N'' END + N';' + @CRLF + @CRLF FROM msdb.dbo.sysjobs j LEFT JOIN msdb.dbo.sysoperators op_email ON j.notify_email_operator_id = op_email.id LEFT JOIN msdb.dbo.sysoperators op_netsend ON j.notify_netsend_operator_id = op_netsend.id LEFT JOIN msdb.dbo.sysoperators op_page ON j.notify_page_operator_id = op_page.id WHERE j.job_id = @JobId;
SET @JobSQL = @JobSQL + ISNULL(@Tmp, N'');
--------------------------------------------------------------------------- -- job server --------------------------------------------------------------------------- IF @AttachToLocalServer = 1 BEGIN SET @JobSQL = @JobSQL + N'EXEC msdb.dbo.sp_add_jobserver' + @CRLF + N' @job_id = @jobId,' + @CRLF + N' @server_name = N''(LOCAL)'';' + @CRLF + @CRLF; END;
--------------------------------------------------------------------------- -- step review --------------------------------------------------------------------------- SET @JobSQL = @JobSQL + N'/* Step Review' + @CRLF;
SET @Tmp = N'';
SELECT @Tmp = @Tmp + N'-- Step ' + CAST(s.step_id AS NVARCHAR(10)) + N' | Name: ' + REPLACE(REPLACE(s.step_name, CHAR(13), N' '), CHAR(10), N' ') + N' | Subsystem: ' + s.subsystem + N' | DB: ' + ISNULL(s.database_name, N'(NULL)') + N' | Proxy: ' + ISNULL(p.name, N'(NULL)') + CASE WHEN s.command LIKE N'%USE [%]%' OR s.command LIKE N'%].dbo.%' OR s.command LIKE N'%.dbo.%' THEN N' | CHECK_COMMAND_FOR_DB_NAME=YES' ELSE N'' END + @CRLF FROM msdb.dbo.sysjobsteps s LEFT JOIN msdb.dbo.sysproxies p ON s.proxy_id = p.proxy_id WHERE s.job_id = @JobId ORDER BY s.step_id;
SET @JobSQL = @JobSQL + ISNULL(@Tmp, N'') + N'*/' + @CRLF + N'GO' + @CRLF + @CRLF;
--------------------------------------------------------------------------- -- 加到總腳本 --------------------------------------------------------------------------- SET @FinalSQL = @FinalSQL + @JobSQL;
FETCH NEXT FROM JobCursor INTO @RowNum, @SourceJobName, @TargetJobName, @JobId;END
CLOSE JobCursor;DEALLOCATE JobCursor;
--------------------------------------------------------------------------------- 最終輸出-------------------------------------------------------------------------------SELECT @FinalSQL AS DeployScript;GO收尾
這種腳本重點不是匯出,而是讓 Job 搬遷變成「可重複、可驗證」的流程。
SQL Agent 多 Job 匯出怎麼做:一支腳本重建多個排程與 Step
https://joyceowo.github.io/posts/32da78ea09fa8006894dd4c158a631fb/