{"id":1082,"date":"2025-06-27T15:01:32","date_gmt":"2025-06-27T20:01:32","guid":{"rendered":"https:\/\/www.sqlfreelancer.com\/blog\/?p=1082"},"modified":"2025-06-27T15:31:50","modified_gmt":"2025-06-27T20:31:50","slug":"automate-sql-maintenance-in-azure-sql-databases-with-elastic-jobs","status":"publish","type":"post","link":"https:\/\/www.sqlfreelancer.com\/blog\/automate-sql-maintenance-in-azure-sql-databases-with-elastic-jobs\/","title":{"rendered":"Automate SQL Maintenance in Azure SQL Databases with Elastic Jobs"},"content":{"rendered":"<p data-start=\"263\" data-end=\"528\">Azure SQL Database offers powerful PaaS capabilities, but it lacks the familiar SQL Agent you might rely on for index and statistics maintenance in traditional SQL Server. Fortunately, Elastic Jobs can help you fill this gap and keep your databases healthy.<\/p>\n<p data-start=\"530\" data-end=\"678\">In this post, I\u2019ll walk you step-by-step through automating index and statistics maintenance across multiple Azure SQL Databases using Elastic Jobs.<\/p>\n<h4 data-start=\"1619\" data-end=\"1658\">1. Create an Elastic Job Agent<\/h4>\n<p data-start=\"1660\" data-end=\"1676\">In Azure Portal:<\/p>\n<ul>\n<li data-start=\"1678\" data-end=\"1715\">\n<p data-start=\"1681\" data-end=\"1715\">Search for Elastic Job Agents.<\/p>\n<\/li>\n<li data-start=\"1716\" data-end=\"1738\">\n<p data-start=\"1719\" data-end=\"1738\">Click + Create.<\/p>\n<\/li>\n<li data-start=\"1739\" data-end=\"1824\">\n<p data-start=\"1742\" data-end=\"1824\">Choose a Job Database (stores metadata and logs\u2014don\u2019t use your production DB).<\/p>\n<\/li>\n<li data-start=\"1739\" data-end=\"1824\">\n<p data-start=\"1742\" data-end=\"1824\">Review and create.<\/p>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p data-start=\"1742\" data-end=\"1824\"><a href=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentWizard-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-1085 size-large\" src=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentWizard-1-803x1024.png\" alt=\"\" width=\"620\" height=\"791\" srcset=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentWizard-1-803x1024.png 803w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentWizard-1-235x300.png 235w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentWizard-1-768x979.png 768w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentWizard-1.png 1184w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<h4 data-start=\"1935\" data-end=\"1971\">2. Configure Authentication<\/h4>\n<p data-start=\"1973\" data-end=\"2038\">Elastic Jobs must authenticate to your target databases. You can:<\/p>\n<ul>\n<li data-start=\"2040\" data-end=\"2113\">Use a Managed Identity (Recommended)<\/li>\n<li data-start=\"2040\" data-end=\"2113\">Use SQL Authentication<\/li>\n<\/ul>\n<p data-start=\"2115\" data-end=\"2138\">For a managed identity:<\/p>\n<ul data-start=\"2140\" data-end=\"2248\">\n<li data-start=\"2140\" data-end=\"2196\">\n<p data-start=\"2142\" data-end=\"2196\">Enable it on the Elastic Job Agent under Identity.<\/p>\n<\/li>\n<li data-start=\"2197\" data-end=\"2248\">\n<p data-start=\"2199\" data-end=\"2248\">In each target database, create an external user:<\/p>\n<\/li>\n<\/ul>\n<p><code data-start=\"2199\" data-end=\"2248\">CREATE USER [sqlfreelancer-job-agent-identity] FROM EXTERNAL PROVIDER;<\/code><br \/>\n<code>ALTER ROLE db_owner ADD MEMBER [sqlfreelancer-job-agent-identity];<\/code><\/p>\n<p><a href=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentIdentity.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1086\" src=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentIdentity-1024x473.png\" alt=\"\" width=\"620\" height=\"286\" srcset=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentIdentity-1024x473.png 1024w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentIdentity-300x139.png 300w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentIdentity-768x355.png 768w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentIdentity-1536x710.png 1536w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentIdentity.png 1880w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<p data-start=\"1828\" data-end=\"1846\">** To create a new Managed Identity (if necessary)<\/p>\n<ul>\n<li data-start=\"624\" data-end=\"669\">\n<p data-start=\"626\" data-end=\"669\">Go to Azure Portal &gt; Managed Identities<\/p>\n<\/li>\n<li data-start=\"670\" data-end=\"690\">\n<p data-start=\"672\" data-end=\"690\">Click + Create<\/p>\n<\/li>\n<li data-start=\"691\" data-end=\"834\">\n<p data-start=\"693\" data-end=\"700\">Choose:<\/p>\n<ul data-start=\"703\" data-end=\"834\">\n<li data-start=\"703\" data-end=\"739\">\n<p data-start=\"705\" data-end=\"739\">Region: Same as your Job Agent<\/p>\n<\/li>\n<li data-start=\"742\" data-end=\"786\">\n<p data-start=\"744\" data-end=\"786\">Resource group: Same as your Job Agent<\/p>\n<\/li>\n<li data-start=\"789\" data-end=\"834\">\n<p data-start=\"791\" data-end=\"834\">Name: e.g., <code data-start=\"807\" data-end=\"834\">sqlfreelancer-job-agent-identity<\/code><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><a href=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/CreateManagedIdentity.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1087\" src=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/CreateManagedIdentity-1024x683.png\" alt=\"\" width=\"620\" height=\"414\" srcset=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/CreateManagedIdentity-1024x683.png 1024w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/CreateManagedIdentity-300x200.png 300w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/CreateManagedIdentity-768x512.png 768w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/CreateManagedIdentity.png 1223w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<h4 data-start=\"2494\" data-end=\"2527\">3. Create a Target Group<\/h4>\n<p><span data-huuid=\"230553063906137623\">A target group defines the collection of databases or servers where a job will be executed <\/span><span data-huuid=\"230553063906139222\">It acts as a container, specifying the scope of the job&#8217;s execution.<\/span><\/p>\n<p data-start=\"2529\" data-end=\"2594\">Connect to your Job Database using SSMS:<\/p>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-sql\"><span class=\"hljs-keyword\">EXEC<\/span> jobs.sp_add_target_group <span class=\"hljs-variable\">@target_group_name<\/span> <span class=\"hljs-operator\">=<\/span> N<span class=\"hljs-string\">'TargetGroup_MultipleDBs'<\/span>;<br \/>\n<\/code><\/div>\n<\/div>\n<p data-start=\"2677\" data-end=\"2700\">Then add each database:<\/p>\n<p><code>EXEC jobs.sp_add_target_group_member <\/code><\/p>\n<p><code>@target_group_name = N'TargetGroup_MultipleDBs', <\/code><\/p>\n<p><code>@target_type = N'SqlDatabase', <\/code><\/p>\n<p><code>@server_name = N'your-server-name.database.windows.net', <\/code><\/p>\n<p><code>@database_name = N'Database1';<\/code><\/p>\n<p>&nbsp;<\/p>\n<p><code>EXEC jobs.sp_add_target_group_member <\/code><\/p>\n<p><code>@target_group_name = N'TargetGroup_MultipleDBs', <\/code><\/p>\n<p><code>@target_type = N'SqlDatabase', <\/code><\/p>\n<p><code>@server_name = N'your-server-name.database.windows.net', <\/code><\/p>\n<p><code>@database_name = N'Database2';<\/code><\/p>\n<p>&nbsp;<\/p>\n<p><code>EXEC jobs.sp_add_target_group_member <\/code><\/p>\n<p><code>@target_group_name = N'TargetGroup_MultipleDBs', <\/code><\/p>\n<p><code>@target_type = N'SqlDatabase', <\/code><\/p>\n<p><code>@server_name = N'your-server-name.database.windows.net', <\/code><\/p>\n<p><code>@database_name = N'Database3';<\/code><\/p>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentTargetGroup.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1088\" src=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentTargetGroup-1024x679.png\" alt=\"\" width=\"620\" height=\"411\" srcset=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentTargetGroup-1024x679.png 1024w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentTargetGroup-300x199.png 300w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentTargetGroup-768x509.png 768w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentTargetGroup.png 1104w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<h4>4. Create the Job<\/h4>\n<p><code class=\"whitespace-pre! language-sql\"><span class=\"hljs-keyword\">EXEC<\/span> jobs.sp_add_job @job_name = N'IndexAndStatsMaintenance_MultiDB';<\/code><\/p>\n<p>&nbsp;<\/p>\n<h4 data-start=\"3348\" data-end=\"3376\">5. Add the Job Step<\/h4>\n<p data-start=\"3378\" data-end=\"3461\">Here\u2019s an example T-SQL script to rebuild fragmented indexes and update statistics:<\/p>\n<p><code>EXEC jobs.sp_add_jobstep<br \/>\n@job_name = N'IndexAndStatsMaintenance_MultiDB',<br \/>\n@step_name = N'RebuildIndexesAndUpdateStats',<br \/>\n@command = N'<br \/>\nDECLARE @TableName NVARCHAR(256),<\/code><code><br \/>\n@IndexName NVARCHAR(256),<br \/>\n@Frag FLOAT,<br \/>\n@SQL NVARCHAR(MAX);<\/code><\/p>\n<p><code>DECLARE frag_cursor CURSOR FOR<br \/>\nSELECT s.name + ''.'' + o.name, i.name, ips.avg_fragmentation_in_percent<br \/>\nFROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, ''LIMITED'') ips<br \/>\nJOIN sys.indexes i ON ips.object_id = i.object_id AND ips.index_id = i.index_id<br \/>\nJOIN sys.objects o ON o.object_id = i.object_id<br \/>\nJOIN sys.schemas s ON s.schema_id = o.schema_id<br \/>\nWHERE ips.index_id &gt; 0 AND ips.avg_fragmentation_in_percent &gt; 5 AND o.type = ''U'';<\/code><\/p>\n<p><code>OPEN frag_cursor;<br \/>\nFETCH NEXT FROM frag_cursor INTO @TableName, @IndexName, @Frag;<\/code><\/p>\n<p><code>WHILE @@FETCH_STATUS = 0<br \/>\nBEGIN<br \/>\nIF @Frag &gt; 30<br \/>\nSET @SQL = N''ALTER INDEX ['' + @IndexName + ''] ON '' + @TableName + '' REBUILD'';<br \/>\nELSE<br \/>\nSET @SQL = N''ALTER INDEX ['' + @IndexName + ''] ON '' + @TableName + '' REORGANIZE'';<\/code><\/p>\n<p><code>EXEC sp_executesql @SQL;<br \/>\nFETCH NEXT FROM frag_cursor INTO @TableName, @IndexName, @Frag;<br \/>\nEND<\/code><\/p>\n<p><code>CLOSE frag_cursor;<br \/>\nDEALLOCATE frag_cursor;<\/code><\/p>\n<p><code>EXEC sp_updatestats;<br \/>\n',<br \/>\n@target_group_name = N'TargetGroup_MultiDBs';<\/code><\/p>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentJobScript.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1090\" src=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentJobScript-1024x908.png\" alt=\"\" width=\"620\" height=\"550\" srcset=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentJobScript-1024x908.png 1024w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentJobScript-300x266.png 300w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentJobScript-768x681.png 768w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentJobScript-1536x1362.png 1536w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentJobScript.png 1589w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<h4 data-start=\"4685\" data-end=\"4713\">6. Schedule the Job<\/h4>\n<p data-start=\"4715\" data-end=\"4794\">Unlike SQL Server Agent, Elastic Job schedules are created in Azure Portal:<\/p>\n<ol data-start=\"4796\" data-end=\"4903\">\n<li data-start=\"4796\" data-end=\"4832\">\n<p data-start=\"4799\" data-end=\"4832\">Go to your Elastic Job Agent.<\/p>\n<\/li>\n<li data-start=\"4833\" data-end=\"4852\">\n<p data-start=\"4836\" data-end=\"4852\">Select your job.<\/p>\n<\/li>\n<li data-start=\"4853\" data-end=\"4891\">\n<p data-start=\"4856\" data-end=\"4891\">Click Job Definition.<\/p>\n<\/li>\n<li data-start=\"4853\" data-end=\"4891\">Select your job definition and click Edit.<\/li>\n<li data-start=\"4892\" data-end=\"4903\">\n<p data-start=\"4895\" data-end=\"4903\">Fill in:<\/p>\n<\/li>\n<\/ol>\n<ul data-start=\"4905\" data-end=\"5049\">\n<li data-start=\"4905\" data-end=\"4957\">\n<p data-start=\"4907\" data-end=\"4957\">Start Time (UTC): e.g., <code data-start=\"4935\" data-end=\"4957\">2024-06-22T02:00:00Z<\/code><\/p>\n<\/li>\n<li data-start=\"4958\" data-end=\"5049\">\n<p data-start=\"4960\" data-end=\"4985\">Recurrence (ISO8601)<strong data-start=\"4960\" data-end=\"4985\">:<\/strong><\/p>\n<ul data-start=\"4988\" data-end=\"5049\">\n<li data-start=\"4988\" data-end=\"5002\">\n<p data-start=\"4990\" data-end=\"5002\">Daily: <code data-start=\"4997\" data-end=\"5002\">P1D<\/code><\/p>\n<\/li>\n<li data-start=\"5005\" data-end=\"5021\">\n<p data-start=\"5007\" data-end=\"5021\">Hourly: <code data-start=\"5015\" data-end=\"5021\">PT1H<\/code><\/p>\n<\/li>\n<li data-start=\"5024\" data-end=\"5049\">\n<p data-start=\"5026\" data-end=\"5049\">Every 12 hours: <code data-start=\"5042\" data-end=\"5049\">PT12H<\/code><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><a href=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentSchedule.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1091\" src=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentSchedule-737x1024.png\" alt=\"\" width=\"620\" height=\"861\" srcset=\"https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentSchedule-737x1024.png 737w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentSchedule-216x300.png 216w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentSchedule-768x1067.png 768w, https:\/\/www.sqlfreelancer.com\/blog\/wp-content\/uploads\/2025\/06\/ElasticJobAgentSchedule.png 877w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<h4 data-start=\"5183\" data-end=\"5219\">Monitoring and Troubleshooting<\/h4>\n<p data-start=\"5221\" data-end=\"5260\">Use these queries in your Job Database:<\/p>\n<ul data-start=\"5262\" data-end=\"5284\">\n<li data-start=\"5262\" data-end=\"5284\">\n<p data-start=\"5264\" data-end=\"5284\">Recent Job Runs:<\/p>\n<\/li>\n<\/ul>\n<p><code>SELECT * FROM jobs.job_executions ORDER BY start_time DESC;<\/code><\/p>\n<p>&nbsp;<\/p>\n<p>Elastic Jobs are a great way to automate maintenance in Azure SQL Databases without extra infrastructure. With a little setup, you can keep indexes and stats healthy\u2014just like SQL Agent.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Azure SQL Database offers powerful PaaS capabilities, but it lacks the familiar SQL Agent you might rely on for index and statistics maintenance in traditional SQL Server. Fortunately, Elastic Jobs can help you fill this gap and keep your databases healthy. In this post, I\u2019ll walk you step-by-step through automating index and statistics maintenance across [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1107,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[76,91],"tags":[77,117],"class_list":["post-1082","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","category-maintenance","tag-azure","tag-maintenance"],"_links":{"self":[{"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/posts\/1082","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/comments?post=1082"}],"version-history":[{"count":8,"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/posts\/1082\/revisions"}],"predecessor-version":[{"id":1108,"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/posts\/1082\/revisions\/1108"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/media\/1107"}],"wp:attachment":[{"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/media?parent=1082"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/categories?post=1082"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlfreelancer.com\/blog\/wp-json\/wp\/v2\/tags?post=1082"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}