From c0ef74018b849db4349b5eb8efa89cac46cb4666 Mon Sep 17 00:00:00 2001 From: Darling Data Date: Mon, 24 Mar 2025 14:11:32 +0000 Subject: [PATCH 1/4] Automation: Format and Build SQL File --- Install-All/DarlingData.sql | 73 +++++++++++++++-------------- sp_QuickieStore/sp_QuickieStore.sql | 8 ++-- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/Install-All/DarlingData.sql b/Install-All/DarlingData.sql index f1e17881..0a74a1eb 100644 --- a/Install-All/DarlingData.sql +++ b/Install-All/DarlingData.sql @@ -1,4 +1,4 @@ --- Compile Date: 03/24/2025 13:48:58 UTC +-- Compile Date: 03/24/2025 14:11:31 UTC SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; @@ -26174,7 +26174,13 @@ DECLARE @requires_secondary_processing bit, @split_sql nvarchar(max), @error_msg nvarchar(2000), - @conflict_list nvarchar(max) = N''; + @conflict_list nvarchar(max) = N'', + @database_cursor CURSOR, + @filter_cursor CURSOR, + @dynamic_sql nvarchar(max) = N'', + @secondary_sql nvarchar(max) = N'', + @temp_target_table nvarchar(100), + @exist_or_not_exist nvarchar(20); /* In cases where we are escaping @query_text_search and @@ -26740,9 +26746,6 @@ BEGIN END; END; -DECLARE - @database_cursor CURSOR; - SET @database_cursor = CURSOR @@ -27019,7 +27022,8 @@ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;', ) AS ids CROSS APPLY ids.nodes(''x'') AS x (x) ) AS ids - OPTION(RECOMPILE);', + OPTION(RECOMPILE); + ', @string_split_strings = N' SELECT DISTINCT ids = @@ -27063,7 +27067,8 @@ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;', ) AS ids CROSS APPLY ids.nodes(''x'') AS x (x) ) AS ids - OPTION(RECOMPILE);', + OPTION(RECOMPILE); + ', @troubleshoot_insert = N' INSERT #troubleshoot_performance @@ -27078,7 +27083,8 @@ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;', @current_table, GETDATE() ) - OPTION(RECOMPILE);', + OPTION(RECOMPILE); + ', @troubleshoot_update = N' UPDATE tp @@ -27086,7 +27092,8 @@ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;', tp.end_time = GETDATE() FROM #troubleshoot_performance AS tp WHERE tp.current_table = @current_table - OPTION(RECOMPILE);', + OPTION(RECOMPILE); + ', @troubleshoot_info = N' SELECT ( @@ -27105,7 +27112,8 @@ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;', PATH(N''''), TYPE ).query(''.[1]'') AS current_query - OPTION(RECOMPILE);', + OPTION(RECOMPILE); + ', @rc = 0, @em = @expert_mode, @fo = @format_output, @@ -28530,9 +28538,6 @@ OR @ignore_plan_hashes IS NOT NULL OR @ignore_sql_handles IS NOT NULL ) BEGIN - DECLARE - @filter_cursor CURSOR; - SET @filter_cursor = CURSOR LOCAL @@ -28605,7 +28610,7 @@ BEGIN END; /* Execute the dynamic SQL to populate the temporary table */ - DECLARE @dynamic_sql nvarchar(max) = N' + SET @dynamic_sql = N' INSERT INTO ' + @temp_table + N' WITH @@ -28649,9 +28654,6 @@ BEGIN @current_table = 'inserting #include_plan_ids for ' + @param_name; /* Build appropriate SQL based on parameter type */ - DECLARE - @secondary_sql nvarchar(max) = N''; - IF @param_name = 'include_query_ids' OR @param_name = 'ignore_query_ids' BEGIN @@ -28672,7 +28674,7 @@ BEGIN N'_query_ids AS iqi WHERE iqi.query_id = qsp.query_id ) - OPTION(RECOMPILE);'; + OPTION(RECOMPILE);' + @nc10; END; ELSE IF @param_name = 'include_query_hashes' @@ -28702,7 +28704,7 @@ BEGIN WHERE iqh.query_hash = qsq.query_hash ) ) - OPTION(RECOMPILE);'; + OPTION(RECOMPILE);' + @nc10; END; ELSE IF @param_name = 'include_plan_hashes' @@ -28724,7 +28726,7 @@ BEGIN END + N'_plan_hashes AS iph WHERE iph.plan_hash = qsp.query_plan_hash ) - OPTION(RECOMPILE);'; + OPTION(RECOMPILE);' + @nc10; END; ELSE IF @@ -28761,7 +28763,7 @@ BEGIN ) ) ) - OPTION(RECOMPILE);'; + OPTION(RECOMPILE);' + @nc10; END; /* Process secondary sql if defined */ @@ -28805,14 +28807,14 @@ BEGIN END; /* Update where clause if needed */ - DECLARE - @temp_target_table nvarchar(100) = + SELECT + @temp_target_table = CASE WHEN @is_include = 1 THEN N'#include_plan_ids' ELSE N'#ignore_plan_ids' END, - @exist_or_not_exist nvarchar(20) = + @exist_or_not_exist = CASE WHEN @is_include = 1 THEN N'EXISTS' @@ -32211,7 +32213,7 @@ WHERE EXISTS AND qsp.database_id = qsq.database_id WHERE qsq.context_settings_id = qcs.context_settings_id ) -OPTION(RECOMPILE);'; +OPTION(RECOMPILE);' + @nc10; INSERT #query_context_settings @@ -33156,8 +33158,7 @@ ORDER BY END END END + N' DESC -OPTION(RECOMPILE);' - + @nc10 +OPTION(RECOMPILE);' + @nc10 ); IF @debug = 1 @@ -33267,7 +33268,7 @@ BEGIN FROM #query_store_plan_feedback AS qspf ORDER BY qspf.plan_id - OPTION(RECOMPILE);'; + OPTION(RECOMPILE);' + @nc10; IF @debug = 1 BEGIN @@ -33325,7 +33326,7 @@ BEGIN FROM #query_store_query_hints AS qsqh ORDER BY qsqh.query_id - OPTION(RECOMPILE);'; + OPTION(RECOMPILE);' + @nc10; IF @debug = 1 BEGIN @@ -33373,7 +33374,7 @@ BEGIN FROM #query_store_query_variant AS qsqv ORDER BY qsqv.parent_query_id - OPTION(RECOMPILE);'; + OPTION(RECOMPILE);' + @nc10; IF @debug = 1 BEGIN @@ -33664,7 +33665,7 @@ BEGIN WHERE x.n = 1 ORDER BY x.query_id - OPTION(RECOMPILE);'; + OPTION(RECOMPILE);' + @nc10; IF @debug = 1 BEGIN @@ -33880,7 +33881,7 @@ BEGIN ) ORDER BY qsq.query_id - OPTION(RECOMPILE);' + OPTION(RECOMPILE);' + @nc10 ); IF @debug = 1 @@ -33925,7 +33926,7 @@ BEGIN ( nvarchar(max), N' - SELECT DISTINCT + SELECT source = ''query_store_wait_stats_by_query'', database_name = @@ -34039,7 +34040,7 @@ BEGIN ORDER BY qsws.plan_id, qsws.total_query_wait_time_ms DESC - OPTION(RECOMPILE);' + OPTION(RECOMPILE);' + @nc10 ); IF @debug = 1 @@ -34178,7 +34179,7 @@ BEGIN qsws.database_id ORDER BY SUM(qsws.total_query_wait_time_ms) DESC - OPTION(RECOMPILE);' + OPTION(RECOMPILE);' + @nc10 ); IF @debug = 1 @@ -34278,7 +34279,7 @@ BEGIN N' dqso.size_based_cleanup_mode_desc FROM #database_query_store_options AS dqso - OPTION(RECOMPILE);' + OPTION(RECOMPILE);' + @nc10 ); IF @debug = 1 diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index cc1e1cb5..abde29a9 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4122,8 +4122,8 @@ BEGIN /* Execute the dynamic SQL to populate the temporary table */ SET @dynamic_sql = N' - INSERT INTO - ' + @temp_table + N' + INSERT INTO + ' + @temp_table + N' WITH (TABLOCK) ( @@ -4164,8 +4164,8 @@ BEGIN SELECT @current_table = 'inserting #include_plan_ids for ' + @param_name; - /* Build appropriate SQL based on parameter type */ - IF @param_name = 'include_query_ids' + /* Build appropriate SQL based on parameter type */ + IF @param_name = 'include_query_ids' OR @param_name = 'ignore_query_ids' BEGIN SELECT @secondary_sql = N' From acf7dd7db8efc2f37d1dd7eee4ce7aa807f19fd3 Mon Sep 17 00:00:00 2001 From: Darling Data Date: Tue, 25 Mar 2025 20:35:57 +0000 Subject: [PATCH 2/4] Automation: Format and Build SQL File --- Install-All/DarlingData.sql | 116 ++++++++++++++++++++-------- sp_QuickieStore/sp_QuickieStore.sql | 12 +-- 2 files changed, 89 insertions(+), 39 deletions(-) diff --git a/Install-All/DarlingData.sql b/Install-All/DarlingData.sql index 0a74a1eb..e9925531 100644 --- a/Install-All/DarlingData.sql +++ b/Install-All/DarlingData.sql @@ -1,4 +1,4 @@ --- Compile Date: 03/24/2025 14:11:31 UTC +-- Compile Date: 03/25/2025 20:35:56 UTC SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; @@ -10807,12 +10807,12 @@ BEGIN lock_mode nvarchar(10) NULL, resource_owner_type nvarchar(256) NULL, transaction_count int NULL, - transaction_name nvarchar(512) NULL, + transaction_name nvarchar(1024) NULL, last_transaction_started datetime2(7) NULL, last_transaction_completed datetime2(7) NULL, client_option_1 varchar(261) NULL, client_option_2 varchar(307) NULL, - wait_resource nvarchar(100) NULL, + wait_resource nvarchar(1024) NULL, priority int NULL, log_used bigint NULL, client_app nvarchar(256) NULL, @@ -11281,7 +11281,7 @@ BEGIN wait_time = bd.value('(process/@waittime)[1]', 'bigint'), lastbatchstarted = bd.value('(process/@lastbatchstarted)[1]', 'datetime2'), lastbatchcompleted = bd.value('(process/@lastbatchcompleted)[1]', 'datetime2'), - wait_resource = bd.value('(process/@waitresource)[1]', 'nvarchar(100)'), + wait_resource = bd.value('(process/@waitresource)[1]', 'nvarchar(1024)'), status = bd.value('(process/@status)[1]', 'nvarchar(10)'), priority = bd.value('(process/@priority)[1]', 'integer'), transaction_count = bd.value('(process/@trancount)[1]', 'integer'), @@ -11338,7 +11338,7 @@ BEGIN wait_time = bg.value('(process/@waittime)[1]', 'bigint'), last_transaction_started = bg.value('(process/@lastbatchstarted)[1]', 'datetime2'), last_transaction_completed = bg.value('(process/@lastbatchcompleted)[1]', 'datetime2'), - wait_resource = bg.value('(process/@waitresource)[1]', 'nvarchar(100)'), + wait_resource = bg.value('(process/@waitresource)[1]', 'nvarchar(1024)'), status = bg.value('(process/@status)[1]', 'nvarchar(10)'), priority = bg.value('(process/@priority)[1]', 'integer'), transaction_count = bg.value('(process/@trancount)[1]', 'integer'), @@ -11890,10 +11890,10 @@ SELECT blocked_ecid = bd.value('(process/@ecid)[1]', 'integer'), query_text_pre = bd.value('(process/inputbuf/text())[1]', 'nvarchar(max)'), wait_time = bd.value('(process/@waittime)[1]', 'bigint'), - transaction_name = bd.value('(process/@transactionname)[1]', 'nvarchar(512)'), + transaction_name = bd.value('(process/@transactionname)[1]', 'nvarchar(1024)'), last_transaction_started = bd.value('(process/@lasttranstarted)[1]', 'datetime2'), last_transaction_completed = CONVERT(datetime2, NULL), - wait_resource = bd.value('(process/@waitresource)[1]', 'nvarchar(100)'), + wait_resource = bd.value('(process/@waitresource)[1]', 'nvarchar(1024)'), lock_mode = bd.value('(process/@lockMode)[1]', 'nvarchar(10)'), status = bd.value('(process/@status)[1]', 'nvarchar(10)'), priority = bd.value('(process/@priority)[1]', 'integer'), @@ -12010,10 +12010,10 @@ SELECT blocked_ecid = bd.value('(process/@ecid)[1]', 'integer'), query_text_pre = bg.value('(process/inputbuf/text())[1]', 'nvarchar(max)'), wait_time = bg.value('(process/@waittime)[1]', 'bigint'), - transaction_name = bg.value('(process/@transactionname)[1]', 'nvarchar(512)'), + transaction_name = bg.value('(process/@transactionname)[1]', 'nvarchar(1024)'), last_transaction_started = bg.value('(process/@lastbatchstarted)[1]', 'datetime2'), last_transaction_completed = bg.value('(process/@lastbatchcompleted)[1]', 'datetime2'), - wait_resource = bg.value('(process/@waitresource)[1]', 'nvarchar(100)'), + wait_resource = bg.value('(process/@waitresource)[1]', 'nvarchar(1024)'), lock_mode = bg.value('(process/@lockMode)[1]', 'nvarchar(10)'), status = bg.value('(process/@status)[1]', 'nvarchar(10)'), priority = bg.value('(process/@priority)[1]', 'integer'), @@ -28591,11 +28591,13 @@ BEGIN /* Choose appropriate string split function based on data type */ IF @data_type = N'bigint' BEGIN - SELECT @split_sql = @string_split_ints; + SELECT + @split_sql = @string_split_ints; END ELSE BEGIN - SELECT @split_sql = @string_split_strings; + SELECT + @split_sql = @string_split_strings; END; /* Execute the initial insert with troubleshooting if enabled */ @@ -28603,7 +28605,7 @@ BEGIN BEGIN EXECUTE sys.sp_executesql @troubleshoot_insert, - N'@current_table nvarchar(100)', + N'@current_table nvarchar(100)', @current_table; SET STATISTICS XML ON; @@ -28620,9 +28622,14 @@ BEGIN N') EXECUTE sys.sp_executesql @split_sql, - N''@ids nvarchar(4000)'', + N''@ids nvarchar(4000)'', @param_value;'; + IF @debug = 1 + BEGIN + PRINT @dynamic_sql; + END; + EXEC sys.sp_executesql @dynamic_sql, N'@split_sql nvarchar(max), @@ -28636,7 +28643,7 @@ BEGIN EXECUTE sys.sp_executesql @troubleshoot_update, - N'@current_table nvarchar(100)', + N'@current_table nvarchar(100)', @current_table; EXECUTE sys.sp_executesql @@ -28657,7 +28664,9 @@ BEGIN IF @param_name = 'include_query_ids' OR @param_name = 'ignore_query_ids' BEGIN - SELECT @secondary_sql = N' + SET @secondary_sql = @isolation_level; + + SELECT @secondary_sql += N' SELECT DISTINCT qsp.plan_id FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp @@ -28677,10 +28686,13 @@ BEGIN OPTION(RECOMPILE);' + @nc10; END; ELSE + IF @param_name = 'include_query_hashes' OR @param_name = 'ignore_query_hashes' BEGIN - SELECT @secondary_sql = N' + SET @secondary_sql = @isolation_level; + + SELECT @secondary_sql += N' SELECT DISTINCT qsp.plan_id FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp @@ -28707,10 +28719,13 @@ BEGIN OPTION(RECOMPILE);' + @nc10; END; ELSE + IF @param_name = 'include_plan_hashes' OR @param_name = 'ignore_plan_hashes' BEGIN - SELECT @secondary_sql = N' + SET @secondary_sql = @isolation_level; + + SELECT @secondary_sql += N' SELECT DISTINCT qsp.plan_id FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp @@ -28729,11 +28744,13 @@ BEGIN OPTION(RECOMPILE);' + @nc10; END; ELSE - IF - @param_name = 'include_sql_handles' + + IF @param_name = 'include_sql_handles' OR @param_name = 'ignore_sql_handles' BEGIN - SELECT @secondary_sql = N' + SET @secondary_sql = @isolation_level; + + SELECT @secondary_sql += N' SELECT DISTINCT qsp.plan_id FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp @@ -28773,21 +28790,42 @@ BEGIN BEGIN EXECUTE sys.sp_executesql @troubleshoot_insert, - N'@current_table nvarchar(100)', + N'@current_table nvarchar(100)', @current_table; SET STATISTICS XML ON; END; - INSERT INTO - #include_plan_ids - WITH - (TABLOCK) - ( - plan_id - ) - EXECUTE sys.sp_executesql - @secondary_sql; + IF @debug = 1 + BEGIN + PRINT @secondary_sql; + END; + + /* Insert into the correct target table based on include/ignore */ + IF @is_include = 1 + BEGIN + INSERT INTO + #include_plan_ids + WITH + (TABLOCK) + ( + plan_id + ) + EXECUTE sys.sp_executesql + @secondary_sql; + END + ELSE + BEGIN + INSERT INTO + #ignore_plan_ids + WITH + (TABLOCK) + ( + plan_id + ) + EXECUTE sys.sp_executesql + @secondary_sql; + END; IF @troubleshoot_performance = 1 BEGIN @@ -28795,18 +28833,24 @@ BEGIN EXECUTE sys.sp_executesql @troubleshoot_update, - N'@current_table nvarchar(100)', + N'@current_table nvarchar(100)', @current_table; EXECUTE sys.sp_executesql @troubleshoot_info, - N'@sql nvarchar(max), @current_table nvarchar(100)', + N'@sql nvarchar(max), @current_table nvarchar(100)', @secondary_sql, @current_table; END; END; + END; - /* Update where clause if needed */ + /* Update where clause based on parameter type */ + IF @param_name = 'include_plan_ids' + OR @param_name = 'ignore_plan_ids' + OR @requires_secondary_processing = 1 + BEGIN + /* Choose the correct table and exists/not exists operator */ SELECT @temp_target_table = CASE @@ -28821,6 +28865,7 @@ BEGIN ELSE N'NOT EXISTS' END; + /* Add the filter condition to the where clause */ SELECT @where_clause += N'AND ' + @@ -28832,6 +28877,11 @@ BEGIN FROM ' + @temp_target_table + N' AS idi WHERE idi.plan_id = qsrs.plan_id )' + @nc10; + + IF @debug = 1 + BEGIN + PRINT @where_clause; + END; END; FETCH NEXT diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 92ab7f3e..4b4cc0c0 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4102,12 +4102,12 @@ BEGIN /* Choose appropriate string split function based on data type */ IF @data_type = N'bigint' BEGIN - SELECT + SELECT @split_sql = @string_split_ints; END ELSE BEGIN - SELECT + SELECT @split_sql = @string_split_strings; END; @@ -4197,7 +4197,7 @@ BEGIN OPTION(RECOMPILE);' + @nc10; END; ELSE - + IF @param_name = 'include_query_hashes' OR @param_name = 'ignore_query_hashes' BEGIN @@ -4230,7 +4230,7 @@ BEGIN OPTION(RECOMPILE);' + @nc10; END; ELSE - + IF @param_name = 'include_plan_hashes' OR @param_name = 'ignore_plan_hashes' BEGIN @@ -4255,7 +4255,7 @@ BEGIN OPTION(RECOMPILE);' + @nc10; END; ELSE - + IF @param_name = 'include_sql_handles' OR @param_name = 'ignore_sql_handles' BEGIN @@ -4357,7 +4357,7 @@ BEGIN END; /* Update where clause based on parameter type */ - IF @param_name = 'include_plan_ids' + IF @param_name = 'include_plan_ids' OR @param_name = 'ignore_plan_ids' OR @requires_secondary_processing = 1 BEGIN From 5aa9a23b06dbce0f9a647f05c6f4f250fa2108e9 Mon Sep 17 00:00:00 2001 From: Darling Data Date: Wed, 26 Mar 2025 03:15:20 +0000 Subject: [PATCH 3/4] Automation: Format and Build SQL File --- Install-All/DarlingData.sql | 239 +++++++++++++++++----------- sp_IndexCleanup/sp_IndexCleanup.sql | 30 ++-- 2 files changed, 162 insertions(+), 107 deletions(-) diff --git a/Install-All/DarlingData.sql b/Install-All/DarlingData.sql index e9925531..1bc7ceb0 100644 --- a/Install-All/DarlingData.sql +++ b/Install-All/DarlingData.sql @@ -1,4 +1,4 @@ --- Compile Date: 03/25/2025 20:35:56 UTC +-- Compile Date: 03/26/2025 03:15:19 UTC SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; @@ -13720,28 +13720,30 @@ GO ALTER PROCEDURE dbo.sp_IndexCleanup ( - @database_name sysname = NULL, - @schema_name sysname = NULL, - @table_name sysname = NULL, - @min_reads bigint = 0, - @min_writes bigint = 0, - @min_size_gb decimal(10,2) = 0, - @min_rows bigint = 0, - @get_all_databases bit = 0, /*looks for all accessible user databases and returns combined results*/ + @database_name sysname = NULL, /*focus on a single database*/ + @schema_name sysname = NULL, /*use when focusing on a single table, or to a single schema with no table name*/ + @table_name sysname = NULL, /*use when focusing on a single table*/ + @min_reads bigint = 0, /*only look at indexes with a minimum number of reads*/ + @min_writes bigint = 0, /*only look at indexes with a minimum number of writes*/ + @min_size_gb decimal(10,2) = 0, /*only look at indexes with a minimum size*/ + @min_rows bigint = 0, /*only look at indexes with a minimum number of rows*/ + @dedupe_only bit = 'false', /*only perform deduplication, don't mark unused indexes for removal*/ + @get_all_databases bit = 'false', /*looks for all accessible user databases and returns combined results*/ @include_databases nvarchar(max) = NULL, /*comma-separated list of databases to include (only when @get_all_databases = 1)*/ @exclude_databases nvarchar(max) = NULL, /*comma-separated list of databases to exclude (only when @get_all_databases = 1)*/ - @help bit = 'false', - @debug bit = 'false', - @version varchar(20) = NULL OUTPUT, - @version_date datetime = NULL OUTPUT + @help bit = 'false', /*learn about the procedure and parameters*/ + @debug bit = 'false', /*print dynamic sql, show temp table contents*/ + @version varchar(20) = NULL OUTPUT, /*script version number*/ + @version_date datetime = NULL OUTPUT /*script version date*/ ) WITH RECOMPILE AS BEGIN SET NOCOUNT ON; - BEGIN TRY - /* Check for SQL Server 2012 (11.0) or later for FORMAT and CONCAT functions*/ + SELECT + @version = '1.4', + @version_date = '20250401'; IF /* Check SQL Server 2012+ for FORMAT and CONCAT functions */ @@ -13770,10 +13772,6 @@ BEGIN TRY RETURN; END; - SELECT - @version = '1.4', - @version_date = '20250401'; - /* Help section, for help. Will become more helpful when out of beta. @@ -13793,7 +13791,10 @@ BEGIN TRY help = N'always validate all changes against a non-production environment!' UNION ALL SELECT - help = N'please test carefully.'; + help = N'please test carefully.' + UNION ALL + SELECT + help = N'brought to you by erikdarling.com / code.erikdarling.com'; /* Parameters @@ -13807,12 +13808,13 @@ BEGIN TRY CASE ap.name WHEN N'@database_name' THEN 'the name of the database you wish to analyze' - WHEN N'@schema_name' THEN 'the schema name to filter indexes by' - WHEN N'@table_name' THEN 'the table name to filter indexes by' + WHEN N'@schema_name' THEN 'limits analysis to tables in the specified schema when used without @table_name' + WHEN N'@table_name' THEN 'the table name to filter indexes by, requires @schema_name if not dbo' WHEN N'@min_reads' THEN 'minimum number of reads for an index to be considered used' WHEN N'@min_writes' THEN 'minimum number of writes for an index to be considered used' WHEN N'@min_size_gb' THEN 'minimum size in GB for an index to be analyzed' WHEN N'@min_rows' THEN 'minimum number of rows for a table to be analyzed' + WHEN N'@dedupe_only' THEN 'only perform index deduplication, do not mark unused indexes for removal' WHEN N'@get_all_databases' THEN 'set to 1 to analyze all accessible user databases' WHEN N'@include_databases' THEN 'comma-separated list of databases to include when @get_all_databases = 1' WHEN N'@exclude_databases' THEN 'comma-separated list of databases to exclude when @get_all_databases = 1' @@ -13832,6 +13834,7 @@ BEGIN TRY WHEN N'@min_writes' THEN 'any positive integer or 0' WHEN N'@min_size_gb' THEN 'any positive decimal or 0' WHEN N'@min_rows' THEN 'any positive integer or 0' + WHEN N'@dedupe_only' THEN '0 or 1 - only perform index deduplication, do not mark unused indexes for removal' WHEN N'@get_all_databases' THEN '0 or 1' WHEN N'@include_databases' THEN 'comma-separated list of database names' WHEN N'@exclude_databases' THEN 'comma-separated list of database names' @@ -13851,6 +13854,7 @@ BEGIN TRY WHEN N'@min_writes' THEN '0' WHEN N'@min_size_gb' THEN '0' WHEN N'@min_rows' THEN '0' + WHEN N'@dedupe_only' THEN '0' WHEN N'@get_all_databases' THEN '0' WHEN N'@include_databases' THEN 'NULL' WHEN N'@exclude_databases' THEN 'NULL' @@ -13984,12 +13988,23 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ELSE 0 END; + /* Auto-enable dedupe_only mode if server uptime is low */ + IF CONVERT(integer, @uptime_days) <= 7 AND @dedupe_only = 0 + BEGIN + IF @debug = 1 + BEGIN + RAISERROR('Server uptime is less than 7 days. Automatically enabling @dedupe_only mode.', 0, 1) WITH NOWAIT; + END; + + SET @dedupe_only = 1; + END; + /* Initial checks for object validity */ IF @debug = 1 BEGIN - RAISERROR('Checking paramaters...', 0, 0) WITH NOWAIT; + RAISERROR('Checking parameters...', 0, 0) WITH NOWAIT; END; IF @schema_name IS NULL @@ -14000,8 +14015,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RAISERROR('Parameter @schema_name cannot be NULL when specifying a table, defaulting to dbo', 10, 1) WITH NOWAIT; END; - SELECT - @schema_name = N'dbo'; + SET @schema_name = N'dbo'; END; IF @min_reads < 0 @@ -14280,7 +14294,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CREATE TABLE #index_reporting_stats ( - summary_level varchar(20) NOT NULL, /* 'DATABASE', 'TABLE', 'INDEX', 'SUMMARY' */ + summary_level varchar(20) NOT NULL, /* 'DATABASE', 'TABLE', 'INDEX', 'SUMMARY' */ database_name sysname NULL, schema_name sysname NULL, table_name sysname NULL, @@ -14553,8 +14567,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. N'rdsadmin' ) BEGIN - SELECT - @database_name = DB_NAME(); + SET @database_name = DB_NAME(); END; /* Single database mode */ @@ -14581,7 +14594,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* Get the database_id for backwards compatibility */ SELECT @current_database_id = d.database_id - FROM #databases AS d; + FROM #databases AS d + OPTION(RECOMPILE); END; END ELSE @@ -14706,9 +14720,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. N'.' + QUOTENAME(@table_name); - SELECT - @object_id = - OBJECT_ID(@full_object_name); + SET @object_id = OBJECT_ID(@full_object_name); IF @object_id IS NULL BEGIN @@ -14817,7 +14829,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. )'; END; - IF @object_id IS NOT NULL BEGIN IF @debug = 1 @@ -14825,10 +14836,22 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RAISERROR('adding object_id filter', 0, 0) WITH NOWAIT; END; - SELECT @sql += N' + SET @sql += N' AND t.object_id = @object_id'; END; + IF @schema_name IS NOT NULL + AND @object_id IS NULL + BEGIN + IF @debug = 1 + BEGIN + RAISERROR('adding schema_name filter', 0, 0) WITH NOWAIT; + END; + + SET @sql += N' + AND s.name = @schema_name'; + END; + SET @sql += N' AND EXISTS ( @@ -14899,13 +14922,15 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @min_writes bigint, @min_size_gb decimal(10,2), @min_rows bigint, - @object_id integer', + @object_id integer, + @schema_name sysname', @current_database_id, @min_reads, @min_writes, @min_size_gb, @min_rows, - @object_id; + @object_id, + @schema_name; SET @rc = ROWCOUNT_BIG(); @@ -15935,13 +15960,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ( SELECT 1/0 - FROM #index_details AS id + FROM #index_details AS id WHERE id.index_id = #index_analysis.index_id AND id.object_id = #index_analysis.object_id AND id.user_scans > 0 ) THEN 100 ELSE 0 - END - OPTION(RECOMPILE); /* Indexes with scans get some priority */ + END /* Indexes with scans get some priority */ + OPTION(RECOMPILE); IF @debug = 1 BEGIN @@ -15953,33 +15978,36 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. END; /* Rule 1: Identify unused indexes */ - UPDATE - #index_analysis - SET - #index_analysis.consolidation_rule = - CASE - WHEN @uptime_warning = 1 - THEN 'Unused Index (WARNING: Server uptime < 14 days - usage data may be incomplete)' - ELSE 'Unused Index' - END, - #index_analysis.action = N'DISABLE' - WHERE EXISTS - ( - SELECT - 1/0 - FROM #index_details id - WHERE id.database_id = #index_analysis.database_id - AND id.object_id = #index_analysis.object_id - AND id.index_id = #index_analysis.index_id - AND id.user_seeks = 0 - AND id.user_scans = 0 - AND id.user_lookups = 0 - AND id.is_primary_key = 0 /* Don't disable primary keys */ - AND id.is_unique_constraint = 0 /* Don't disable unique constraints */ - AND id.is_eligible_for_dedupe = 1 /* Only eligible indexes */ - ) - AND #index_analysis.index_id <> 1 - OPTION(RECOMPILE); /* Don't disable clustered indexes */ + IF @dedupe_only = 0 + BEGIN + UPDATE + #index_analysis + SET + #index_analysis.consolidation_rule = + CASE + WHEN @uptime_warning = 1 + THEN 'Unused Index (WARNING: Server uptime < 14 days - usage data may be incomplete)' + ELSE 'Unused Index' + END, + #index_analysis.action = N'DISABLE' + WHERE EXISTS + ( + SELECT + 1/0 + FROM #index_details id + WHERE id.database_id = #index_analysis.database_id + AND id.object_id = #index_analysis.object_id + AND id.index_id = #index_analysis.index_id + AND id.user_seeks = 0 + AND id.user_scans = 0 + AND id.user_lookups = 0 + AND id.is_primary_key = 0 /* Don't disable primary keys */ + AND id.is_unique_constraint = 0 /* Don't disable unique constraints */ + AND id.is_eligible_for_dedupe = 1 /* Only eligible indexes */ + ) + AND #index_analysis.index_id <> 1 /* Don't disable clustered indexes */ + OPTION(RECOMPILE); + END; IF @debug = 1 BEGIN @@ -16044,7 +16072,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. AND id2_uc.index_id = ia2.index_id AND id2_uc.is_unique_constraint = 1 ) - AND EXISTS + AND EXISTS ( SELECT 1/0 @@ -16356,7 +16384,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CASE /* If both have includes, combine them without duplicates */ WHEN kss.superset_includes IS NOT NULL - AND kss.subset_includes IS NOT NULL + AND kss.subset_includes IS NOT NULL THEN /* Create combined includes using XML method that works with all SQL Server versions */ ( @@ -16432,7 +16460,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. UPDATE ia2 SET - ia2.superseded_by = N'Supersedes ' + ia1.index_name + ia2.superseded_by = + N'Supersedes ' + + ia1.index_name FROM #index_analysis AS ia1 JOIN #index_analysis AS ia2 ON ia1.database_id = ia2.database_id @@ -16468,7 +16498,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. END FROM #index_analysis AS ia1 WHERE ia1.consolidation_rule IS NULL /* Not already processed */ - AND ia1.action IS NULL /* Not already processed by earlier rules */ + AND ia1.action IS NULL /* Not already processed by earlier rules */ AND EXISTS ( /* Find nonclustered indexes */ @@ -17362,7 +17392,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* Sort duplicate/subset indexes first (20), then unused indexes last (25) */ sort_order = CASE - WHEN ia.consolidation_rule LIKE 'Unused Index%' THEN 25 + WHEN ia.consolidation_rule LIKE 'Unused Index%' + THEN 25 ELSE 20 END, ia.database_name, @@ -17645,16 +17676,20 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. OPTION(RECOMPILE); /* If any clustered indexes were added, mark them as KEEP */ - UPDATE #index_analysis - SET action = N'KEEP' - WHERE index_id = 1 /* Clustered indexes */ - AND action IS NULL; + UPDATE + #index_analysis + SET + #index_analysis.action = N'KEEP' + WHERE #index_analysis.index_id = 1 /* Clustered indexes */ + AND #index_analysis.action IS NULL; /* Update index priority for clustered indexes to ensure they're not chosen for deduplication */ - UPDATE #index_analysis - SET index_priority = 1000 /* Maximum priority */ - WHERE index_id = 1 /* Clustered indexes */ - AND index_priority IS NULL; + UPDATE + #index_analysis + SET + #index_analysis.index_priority = 1000 /* Maximum priority */ + WHERE #index_analysis.index_id = 1 /* Clustered indexes */ + AND #index_analysis.index_priority IS NULL; IF @debug = 1 BEGIN @@ -18147,7 +18182,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. script_type = CASE /* Add compression status to script_type */ - WHEN ce.can_compress = 1 THEN 'KEPT - NEEDS COMPRESSION' + WHEN ce.can_compress = 1 + THEN 'KEPT - NEEDS COMPRESSION' ELSE 'KEPT' END, ia.consolidation_rule, @@ -18535,7 +18571,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. total_min_savings_gb = ( SELECT - SUM( + SUM + ( CASE WHEN subia.action = N'DISABLE' THEN subps.total_space_gb @@ -18560,7 +18597,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. total_max_savings_gb = ( SELECT - SUM( + SUM + ( CASE WHEN subia.action = N'DISABLE' THEN subps.total_space_gb @@ -18783,7 +18821,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. total_min_savings_gb = ( SELECT - SUM( + SUM + ( CASE WHEN subia.action = N'DISABLE' THEN subps.total_space_gb @@ -18810,7 +18849,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. total_max_savings_gb = ( SELECT - SUM( + SUM + ( CASE WHEN subia.action = N'DISABLE' THEN subps.total_space_gb @@ -18988,13 +19028,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ROW_NUMBER() OVER ( PARTITION BY - database_name, - schema_name, - table_name, - index_name, + irs.database_name, + irs.schema_name, + irs.table_name, + irs.index_name, irs.script_type ORDER BY - result_type DESC /* Prefer non-NULL result types */ + irs.result_type DESC /* Prefer non-NULL result types */ ) AS rn FROM #index_cleanup_results AS irs ) AS ir @@ -19223,7 +19263,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. WHEN ISNULL(irs.unused_indexes, 0) > 0 THEN FORMAT ( - CONVERT(decimal(38,2), + CONVERT + ( + decimal(38,2), ISNULL ( irs.user_updates / @@ -19295,7 +19337,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THEN FORMAT ( - CONVERT(decimal(38,2), + CONVERT + ( + decimal(38,2), ISNULL ( (irs.row_lock_wait_count + irs.page_lock_wait_count) / @@ -19381,7 +19425,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THEN FORMAT ( - CONVERT(decimal(38,2), + CONVERT + ( + decimal(38,2), ISNULL ( (irs.page_latch_wait_count + irs.page_io_latch_wait_count) / @@ -19465,6 +19511,15 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. irs.table_name OPTION(RECOMPILE); + /* Output message for dedupe_only mode */ + IF @dedupe_only = 1 + BEGIN + IF @debug = 1 + BEGIN + RAISERROR('Note: Operating in dedupe_only mode. Unused indexes were considered for deduplication only, not for removal.', 0, 1) WITH NOWAIT; + END; + END; + END TRY BEGIN CATCH THROW; diff --git a/sp_IndexCleanup/sp_IndexCleanup.sql b/sp_IndexCleanup/sp_IndexCleanup.sql index 6459345f..594c40f2 100644 --- a/sp_IndexCleanup/sp_IndexCleanup.sql +++ b/sp_IndexCleanup/sp_IndexCleanup.sql @@ -283,7 +283,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THEN 1 ELSE 0 END; - + /* Auto-enable dedupe_only mode if server uptime is low */ IF CONVERT(integer, @uptime_days) <= 7 AND @dedupe_only = 0 BEGIN @@ -291,7 +291,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. BEGIN RAISERROR('Server uptime is less than 7 days. Automatically enabling @dedupe_only mode.', 0, 1) WITH NOWAIT; END; - + SET @dedupe_only = 1; END; @@ -1135,8 +1135,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SET @sql += N' AND t.object_id = @object_id'; END; - - IF @schema_name IS NOT NULL + + IF @schema_name IS NOT NULL AND @object_id IS NULL BEGIN IF @debug = 1 @@ -2262,7 +2262,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. AND id.user_scans > 0 ) THEN 100 ELSE 0 END /* Indexes with scans get some priority */ - OPTION(RECOMPILE); + OPTION(RECOMPILE); IF @debug = 1 BEGIN @@ -2302,7 +2302,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. AND id.is_eligible_for_dedupe = 1 /* Only eligible indexes */ ) AND #index_analysis.index_id <> 1 /* Don't disable clustered indexes */ - OPTION(RECOMPILE); + OPTION(RECOMPILE); END; IF @debug = 1 @@ -2756,8 +2756,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. UPDATE ia2 SET - ia2.superseded_by = - N'Supersedes ' + + ia2.superseded_by = + N'Supersedes ' + ia1.index_name FROM #index_analysis AS ia1 JOIN #index_analysis AS ia2 @@ -3688,7 +3688,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* Sort duplicate/subset indexes first (20), then unused indexes last (25) */ sort_order = CASE - WHEN ia.consolidation_rule LIKE 'Unused Index%' + WHEN ia.consolidation_rule LIKE 'Unused Index%' THEN 25 ELSE 20 END, @@ -3972,17 +3972,17 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. OPTION(RECOMPILE); /* If any clustered indexes were added, mark them as KEEP */ - UPDATE + UPDATE #index_analysis - SET + SET #index_analysis.action = N'KEEP' WHERE #index_analysis.index_id = 1 /* Clustered indexes */ AND #index_analysis.action IS NULL; /* Update index priority for clustered indexes to ensure they're not chosen for deduplication */ - UPDATE + UPDATE #index_analysis - SET + SET #index_analysis.index_priority = 1000 /* Maximum priority */ WHERE #index_analysis.index_id = 1 /* Clustered indexes */ AND #index_analysis.index_priority IS NULL; @@ -4478,7 +4478,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. script_type = CASE /* Add compression status to script_type */ - WHEN ce.can_compress = 1 + WHEN ce.can_compress = 1 THEN 'KEPT - NEEDS COMPRESSION' ELSE 'KEPT' END, @@ -5806,7 +5806,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. irs.schema_name, irs.table_name OPTION(RECOMPILE); - + /* Output message for dedupe_only mode */ IF @dedupe_only = 1 BEGIN From b75236f3644409fd094c59ee48d061d246b46d59 Mon Sep 17 00:00:00 2001 From: Andrew Lund Date: Thu, 3 Apr 2025 17:05:59 +0200 Subject: [PATCH 4/4] When checking for compression eligibility, indexes on tables with sparse columns should be flagged as ineligible. Addiionally, clustered indexes which contain columns with ineligible data types should also be flagged as ineligible. Otherwise, the compression eligibility of each index will be based on the server and compatability checks. --- sp_IndexCleanup/sp_IndexCleanup.sql | 42 ++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/sp_IndexCleanup/sp_IndexCleanup.sql b/sp_IndexCleanup/sp_IndexCleanup.sql index 594c40f2..49d734eb 100644 --- a/sp_IndexCleanup/sp_IndexCleanup.sql +++ b/sp_IndexCleanup/sp_IndexCleanup.sql @@ -1316,6 +1316,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. END; /* Check for sparse columns or incompatible data types */ + /* Check for sparse columns or incompatible data types */ IF @can_compress = 1 BEGIN IF @debug = 1 @@ -1331,9 +1332,45 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ce SET ce.can_compress = 0, - ce.reason = ''Table contains sparse columns or incompatible data types'' + ce.reason = ''Table contains sparse columns'' FROM #compression_eligibility AS ce WHERE EXISTS + ( + SELECT + 1/0 + FROM ' + QUOTENAME(@current_database_name) + N'.sys.columns AS c + WHERE c.object_id = ce.object_id + AND + ( + c.is_sparse = 1 + ) + ) + OPTION(RECOMPILE); + '; + + IF @debug = 1 + BEGIN + PRINT @sql; + END; + + EXECUTE sys.sp_executesql + @sql; + + SELECT + @sql = N' + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + + UPDATE + ce + SET + ce.can_compress = 0, + ce.reason = ''Index contains incompatible data types'' + FROM #compression_eligibility AS ce + JOIN sys.indexes AS i + ON i.object_id = ce.object_id AND i.index_id = ce.index_id + WHERE ce.can_compress = 1 + AND i.type = 1 + AND EXISTS ( SELECT 1/0 @@ -1343,8 +1380,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. WHERE c.object_id = ce.object_id AND ( - c.is_sparse = 1 - OR t.name IN (N''text'', N''ntext'', N''image'') + t.name IN (N''text'', N''ntext'', N''image'') ) ) OPTION(RECOMPILE);