From 03a84a8d7bc32539126c070389993aba630e0ba9 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:21:06 -0400 Subject: [PATCH] sp_IndexCleanup: fix NULL database_name on Azure SQL DB On Azure SQL DB, sys.databases.database_id is scoped to the logical server while DB_ID(), DB_NAME(), and the database_id column in every DMV are scoped to the database/elastic pool, and the two id spaces do not have to agree (documented in the DB_ID() remarks). The procedure stored the sys.databases id in #databases, so on Hyperscale DB_NAME(@database_id) resolved to NULL and the first insert into a NOT NULL database_name column died with Msg 515. The same mismatch made fo.database_id = os.database_id match nothing, silently emptying #operational_stats. Fix, direction-proof on both counts: - #databases now stores ISNULL(DB_ID(d.name), d.database_id) - the local-space id, identical on box product - so every DMV predicate (dm_db_index_operational_stats, dm_db_index_usage_stats, the operational stats join) compares within one id space. - Names are never derived from ids anymore: every DB_NAME(@database_id) / DB_NAME(os.database_id) / QUOTENAME(DB_NAME(@current_database_id)) site now uses @current_database_name, parameterized into the dynamic SQL. Verified: compiles on SQL 2017/2022/2025; full local harness on 2022 passes (adversarial 32/32, fixture-cases 31/31 including the execute check, rule-coverage 40/40 covering @get_all_databases, no-access 4/4). Co-Authored-By: Claude Fable 5 --- sp_IndexCleanup/sp_IndexCleanup.sql | 53 +++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/sp_IndexCleanup/sp_IndexCleanup.sql b/sp_IndexCleanup/sp_IndexCleanup.sql index f918a8dd..52dd3162 100644 --- a/sp_IndexCleanup/sp_IndexCleanup.sql +++ b/sp_IndexCleanup/sp_IndexCleanup.sql @@ -1360,6 +1360,17 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RETURN; END; + /* + Resolve the database_id locally with DB_ID() rather than trusting + sys.databases. On Azure SQL DB, sys.databases.database_id is scoped + to the logical server while DB_ID(), DB_NAME(), and the database_id + column in every DMV are scoped to the database/elastic pool, and the + two id spaces do not have to agree. Carrying the sys.databases id + made DB_NAME(@database_id) return NULL (Msg 515 on NOT NULL + database_name columns) and made DMV database_id joins match nothing. + DB_ID(d.name) returns the same value as d.database_id everywhere + except Azure, where it returns the id the DMVs actually use. + */ INSERT INTO #databases WITH @@ -1370,7 +1381,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ) SELECT d.name, - d.database_id + database_id = + ISNULL + ( + DB_ID(d.name), + d.database_id + ) FROM sys.databases AS d WHERE d.name = @database_name AND d.state = 0 @@ -1404,7 +1420,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ) SELECT d.name, - d.database_id + /* DB_ID(d.name) rather than d.database_id: see the single-database insert above */ + database_id = + ISNULL + ( + DB_ID(d.name), + d.database_id + ) FROM sys.databases AS d WHERE d.database_id > 4 /* Skip system databases */ AND d.state = 0 @@ -1717,7 +1739,11 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SELECT DISTINCT @database_id, - database_name = DB_NAME(@database_id), + /* + @database_name, never DB_NAME(@database_id): on Azure SQL DB the id + spaces differ and DB_NAME() of a foreign-space id returns NULL + */ + database_name = @database_name, schema_id = s.schema_id, schema_name = s.name, object_id = i.object_id, @@ -1889,6 +1915,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. EXECUTE sys.sp_executesql @sql, N'@database_id integer, + @database_name sysname, @min_reads bigint, @min_writes bigint, @min_size_gb decimal(10,2), @@ -1896,6 +1923,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @object_id integer, @schema_name sysname', @current_database_id, + @current_database_name, @min_reads, @min_writes, @min_size_gb, @@ -2298,7 +2326,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SELECT os.database_id, - database_name = DB_NAME(os.database_id), + database_name = @database_name, schema_id = s.schema_id, schema_name = s.name, os.object_id, @@ -2359,7 +2387,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ) GROUP BY os.database_id, - DB_NAME(os.database_id), s.schema_id, s.name, os.object_id, @@ -2421,8 +2448,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. EXECUTE sys.sp_executesql @sql, N'@database_id integer, + @database_name sysname, @object_id integer', @current_database_id, + @current_database_name, @object_id; SET @rc = ROWCOUNT_BIG(); @@ -2549,7 +2578,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SELECT database_id = @database_id, - database_name = DB_NAME(@database_id), + database_name = @database_name, i.object_id, i.index_id, s.schema_id, @@ -2765,9 +2794,11 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. EXECUTE sys.sp_executesql @sql, N'@database_id integer, + @database_name sysname, @object_id integer, @min_rows bigint', @current_database_id, + @current_database_name, @object_id, @min_rows; @@ -2797,7 +2828,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SELECT database_id = @database_id, - database_name = DB_NAME(@database_id), + database_name = @database_name, x.object_id, x.index_id, x.schema_id, @@ -2965,8 +2996,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. EXECUTE sys.sp_executesql @sql, N'@database_id integer, + @database_name sysname, @object_id integer', @current_database_id, + @current_database_name, @object_id; SET @rc = ROWCOUNT_BIG(); @@ -3011,7 +3044,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ) SELECT @current_database_id, - database_name = DB_NAME(@current_database_id), + database_name = @current_database_name, id1.schema_id, id1.schema_name, id1.table_name, @@ -3089,7 +3122,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. WHEN id1.is_unique_constraint = 1 THEN N'ALTER TABLE ' + - QUOTENAME(DB_NAME(@current_database_id)) + + QUOTENAME(@current_database_name) + N'.' + QUOTENAME(id1.schema_name) + N'.' + @@ -3115,7 +3148,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. N'INDEX ' + QUOTENAME(id1.index_name) + N' ON ' + - QUOTENAME(DB_NAME(@current_database_id)) + + QUOTENAME(@current_database_name) + N'.' + QUOTENAME(id1.schema_name) + N'.' +