From ca1b591d5c37f69fb3a92c0b9cc9357b7ba6bd21 Mon Sep 17 00:00:00 2001
From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com>
Date: Wed, 19 Mar 2025 17:00:46 -0400
Subject: [PATCH] Delete sp_IndexCleanup_Old.sql
---
sp_IndexCleanup/sp_IndexCleanup_Old.sql | 4586 -----------------------
1 file changed, 4586 deletions(-)
delete mode 100644 sp_IndexCleanup/sp_IndexCleanup_Old.sql
diff --git a/sp_IndexCleanup/sp_IndexCleanup_Old.sql b/sp_IndexCleanup/sp_IndexCleanup_Old.sql
deleted file mode 100644
index acaa3d27..00000000
--- a/sp_IndexCleanup/sp_IndexCleanup_Old.sql
+++ /dev/null
@@ -1,4586 +0,0 @@
-SET ANSI_WARNINGS ON;
-SET ARITHABORT ON;
-SET CONCAT_NULL_YIELDS_NULL ON;
-SET QUOTED_IDENTIFIER ON;
-SET NUMERIC_ROUNDABORT OFF;
-SET IMPLICIT_TRANSACTIONS OFF;
-SET STATISTICS TIME, IO OFF;
-GO
-
-IF OBJECT_ID('dbo.sp_IndexCleanup', 'P') IS NULL
-BEGIN
- EXECUTE ('CREATE PROCEDURE dbo.sp_IndexCleanup AS RETURN 138;');
-END;
-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,
- @help bit = 'false',
- @debug bit = 'false',
- @version varchar(20) = NULL OUTPUT,
- @version_date datetime = NULL OUTPUT
-)
-WITH RECOMPILE
-AS
-BEGIN
-SET NOCOUNT ON;
-
-BEGIN TRY
- /* Check for SQL Server 2012 (11.0) or later for FORMAT and CONCAT functions*/
-
- IF
- /* Check SQL Server 2012+ for FORMAT and CONCAT functions */
- (
- CONVERT
- (
- integer,
- SERVERPROPERTY('EngineEdition')
- ) NOT IN (5, 8) /* Not Azure SQL DB or Managed Instance */
- AND CONVERT
- (
- integer,
- SUBSTRING
- (
- CONVERT
- (
- varchar(20),
- SERVERPROPERTY('ProductVersion')
- ),
- 1,
- 2
- )
- ) < 11) /* Pre-2012 */
- BEGIN
- RAISERROR('This procedure requires SQL Server 2012 (11.0) or later due to the use of FORMAT and CONCAT functions.', 11, 1);
- RETURN;
- END;
-
- SELECT
- @version = '1.4',
- @version_date = '20250401';
-
- SELECT
- for_insurance_purposes =
- N'ALWAYS TEST THESE RECOMMENDATIONS IN A NON-PRODUCTION ENVIRONMENT FIRST!';
-
- /*
- Help section, for help.
- Will become more helpful when out of beta.
- */
- IF @help = 1
- BEGIN
- SELECT
- help = N'hello, i am sp_IndexCleanup'
- UNION ALL
- SELECT
- help = N'this is a script to help clean up unused and duplicate indexes.'
- UNION ALL
- SELECT
- help = N'it will also give you scripted out statements to add page compression to uncompressed indexes.'
- UNION ALL
- SELECT
- help = N'always validate all changes against a non-production environment!'
- UNION ALL
- SELECT
- help = N'without careful analysis and consideration, index changes can negative impacts on performance.';
-
- /*
- Parameters
- */
- SELECT
- parameter_name =
- ap.name,
- data_type =
- t.name,
- description =
- 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'@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'@help' THEN 'displays this help information'
- WHEN N'@debug' THEN 'prints debug information during execution'
- WHEN N'@version' THEN 'returns the version number of the procedure'
- WHEN N'@version_date' THEN 'returns the date this version was released'
- ELSE NULL
- END,
- valid_inputs =
- CASE
- ap.name
- WHEN N'@database_name' THEN 'the name of a database you care about indexes in'
- WHEN N'@schema_name' THEN 'schema name or NULL for all schemas'
- WHEN N'@table_name' THEN 'table name or NULL for all tables'
- WHEN N'@min_reads' THEN 'any positive integer or 0'
- 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'@help' THEN '0 or 1'
- WHEN N'@debug' THEN '0 or 1'
- WHEN N'@version' THEN 'OUTPUT parameter'
- WHEN N'@version_date' THEN 'OUTPUT parameter'
- ELSE NULL
- END,
- defaults =
- CASE
- ap.name
- WHEN N'@database_name' THEN 'NULL'
- WHEN N'@schema_name' THEN 'NULL'
- WHEN N'@table_name' THEN 'NULL'
- WHEN N'@min_reads' THEN '0'
- WHEN N'@min_writes' THEN '0'
- WHEN N'@min_size_gb' THEN '0'
- WHEN N'@min_rows' THEN '0'
- WHEN N'@help' THEN 'false'
- WHEN N'@debug' THEN 'true'
- WHEN N'@version' THEN 'NULL'
- WHEN N'@version_date' THEN 'NULL'
- ELSE NULL
- END
- FROM sys.all_parameters AS ap
- JOIN sys.all_objects AS o
- ON ap.object_id = o.object_id
- JOIN sys.types AS t
- ON ap.system_type_id = t.system_type_id
- AND ap.user_type_id = t.user_type_id
- WHERE o.name = N'sp_IndexCleanup'
- OPTION(MAXDOP 1, RECOMPILE);
-
- SELECT
- mit_license_yo = 'i am MIT licensed, so like, do whatever'
-
- UNION ALL
-
- SELECT
- mit_license_yo = 'see printed messages for full license';
-
- RAISERROR('
-MIT License
-
-Copyright 2025 Darling Data, LLC
-
-https://www.erikdarling.com/
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
-to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
-sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
-following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-', 0, 1) WITH NOWAIT;
-
- RETURN;
- END;
-
- IF @debug = 1
- BEGIN
- RAISERROR('Declaring variables', 0, 0) WITH NOWAIT;
- END;
-
- DECLARE
- /*general script variables*/
- @sql nvarchar(max) = N'',
- @database_id integer = NULL,
- @object_id integer = NULL,
- @full_object_name nvarchar(768) = NULL,
- @uptime_warning bit = 0, /* Will set after @uptime_days is calculated */
- /*print variables*/
- @online bit =
- CASE
- WHEN
- CONVERT
- (
- integer,
- SERVERPROPERTY('EngineEdition')
- ) IN (3, 5, 8)
- THEN 'true' /* Enterprise, Azure SQL DB, Managed Instance */
- ELSE 'false'
- END,
- /* Compression variables */
- @can_compress bit =
- CASE
- WHEN
- CONVERT
- (
- integer,
- SERVERPROPERTY('EngineEdition')
- ) IN (3, 5, 8)
- OR
- (
- CONVERT
- (
- integer,
- SERVERPROPERTY('EngineEdition')
- ) = 2
- AND CONVERT
- (
- integer,
- SUBSTRING
- (
- CONVERT
- (
- varchar(20),
- SERVERPROPERTY('ProductVersion')
- ),
- 1,
- 2
- )
- ) >= 13
- )
- THEN 1
- ELSE 0
- END,
- @uptime_days nvarchar(10) =
- (
- SELECT
- DATEDIFF
- (
- DAY,
- osi.sqlserver_start_time,
- SYSDATETIME()
- )
- FROM sys.dm_os_sys_info AS osi
- );
-
- /* Set uptime warning flag after @uptime_days is calculated */
- SELECT
- @uptime_warning =
- CASE
- WHEN CONVERT(integer, @uptime_days) < 14
- THEN 1
- ELSE 0
- END;
-
- /*
- Initial checks for object validity
- */
- IF @debug = 1
- BEGIN
- RAISERROR('Checking paramaters...', 0, 0) WITH NOWAIT;
- END;
-
- IF @database_name IS NULL
- AND DB_NAME() NOT IN
- (
- N'master',
- N'model',
- N'msdb',
- N'tempdb',
- N'rdsadmin'
- )
- BEGIN
- SELECT
- @database_name = DB_NAME();
- END;
-
- IF @database_name IS NOT NULL
- BEGIN
- SELECT
- @database_id = d.database_id
- FROM sys.databases AS d
- WHERE d.name NOT IN (N'master', N'model', N'msdb', N'tempdb', 'rdsadmin')
- AND d.state = 0
- AND d.is_in_standby = 0
- AND d.is_read_only = 0
- OPTION(RECOMPILE);
- END;
-
- IF @schema_name IS NULL
- AND @table_name IS NOT NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('Parameter @schema_name cannot be NULL when specifying a table, defaulting to dbo', 10, 1) WITH NOWAIT;
- END;
-
- SELECT
- @schema_name = N'dbo';
- END;
-
- IF @schema_name IS NOT NULL
- AND @table_name IS NOT NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('validating object existence for %s.%s.&s.', 0, 0, @database_name, @schema_name, @table_name) WITH NOWAIT;
- END;
-
- SELECT
- @full_object_name =
- QUOTENAME(@database_name) +
- N'.' +
- QUOTENAME(@schema_name) +
- N'.' +
- QUOTENAME(@table_name);
-
- SELECT
- @object_id =
- OBJECT_ID(@full_object_name);
-
- IF @object_id IS NULL
- BEGIN
- RAISERROR('The object %s doesn''t seem to exist', 16, 1, @full_object_name) WITH NOWAIT;
- RETURN;
- END;
- END;
-
- /* Parameter validation */
- IF @min_reads < 0
- OR @min_reads IS NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('Parameter @min_reads cannot be NULL or negative. Setting to 0.', 10, 1) WITH NOWAIT;
- END;
-
- SET @min_reads = 0;
- END;
-
- IF @min_writes < 0
- OR @min_writes IS NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('Parameter @min_writes cannot be NULL or negative. Setting to 0.', 10, 1) WITH NOWAIT;
- END;
-
- SET @min_writes = 0;
- END;
-
- IF @min_size_gb < 0
- OR @min_size_gb IS NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('Parameter @min_size_gb cannot be NULL or negative. Setting to 0.', 10, 1) WITH NOWAIT;
- END;
-
- SET @min_size_gb = 0;
- END;
-
- IF @min_rows < 0
- OR @min_rows IS NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('Parameter @min_rows cannot be NULL or negative. Setting to 0.', 10, 1) WITH NOWAIT;
- END;
-
- SET @min_rows = 0;
- END;
-
- /*
- Temp tables!
- */
-
- IF @debug = 1
- BEGIN
- RAISERROR('Creating temp tables', 0, 0) WITH NOWAIT;
- END;
-
- CREATE TABLE
- #filtered_objects
- (
- database_id integer NOT NULL,
- database_name sysname NOT NULL,
- schema_id integer NOT NULL,
- schema_name sysname NOT NULL,
- object_id integer NOT NULL,
- table_name sysname NOT NULL,
- index_id integer NOT NULL,
- index_name sysname NOT NULL,
- can_compress bit NOT NULL
- PRIMARY KEY CLUSTERED(database_id, schema_id, object_id, index_id)
- );
-
- CREATE TABLE
- #operational_stats
- (
- database_id integer NOT NULL,
- database_name sysname NOT NULL,
- schema_id integer NOT NULL,
- schema_name sysname NOT NULL,
- object_id integer NOT NULL,
- table_name sysname NOT NULL,
- index_id integer NOT NULL,
- index_name sysname NOT NULL,
- range_scan_count bigint NULL,
- singleton_lookup_count bigint NULL,
- forwarded_fetch_count bigint NULL,
- lob_fetch_in_pages bigint NULL,
- row_overflow_fetch_in_pages bigint NULL,
- leaf_insert_count bigint NULL,
- leaf_update_count bigint NULL,
- leaf_delete_count bigint NULL,
- leaf_ghost_count bigint NULL,
- nonleaf_insert_count bigint NULL,
- nonleaf_update_count bigint NULL,
- nonleaf_delete_count bigint NULL,
- leaf_allocation_count bigint NULL,
- nonleaf_allocation_count bigint NULL,
- row_lock_count bigint NULL,
- row_lock_wait_count bigint NULL,
- row_lock_wait_in_ms bigint NULL,
- page_lock_count bigint NULL,
- page_lock_wait_count bigint NULL,
- page_lock_wait_in_ms bigint NULL,
- index_lock_promotion_attempt_count bigint NULL,
- index_lock_promotion_count bigint NULL,
- page_latch_wait_count bigint NULL,
- page_latch_wait_in_ms bigint NULL,
- tree_page_latch_wait_count bigint NULL,
- tree_page_latch_wait_in_ms bigint NULL,
- page_io_latch_wait_count bigint NULL,
- page_io_latch_wait_in_ms bigint NULL,
- page_compression_attempt_count bigint NULL,
- page_compression_success_count bigint NULL,
- PRIMARY KEY CLUSTERED (database_id, schema_id, object_id, index_id)
- );
-
- CREATE TABLE
- #partition_stats
- (
- database_id integer NOT NULL,
- database_name sysname NOT NULL,
- schema_id integer NOT NULL,
- schema_name sysname NOT NULL,
- object_id integer NOT NULL,
- table_name sysname NOT NULL,
- index_id integer NOT NULL,
- index_name sysname NULL,
- partition_id bigint NOT NULL,
- partition_number integer NOT NULL,
- total_rows bigint NULL,
- total_space_gb decimal(38, 4) NULL, /* Using 4 decimal places for GB to maintain precision */
- reserved_lob_gb decimal(38, 4) NULL, /* Using 4 decimal places for GB to maintain precision */
- reserved_row_overflow_gb decimal(38, 4) NULL, /* Using 4 decimal places for GB to maintain precision */
- data_compression_desc nvarchar(60) NULL,
- built_on sysname NULL,
- partition_function_name sysname NULL,
- partition_columns nvarchar(max)
- PRIMARY KEY CLUSTERED(database_id, schema_id, object_id, index_id, partition_id)
- );
-
- CREATE TABLE
- #index_details
- (
- database_id integer NOT NULL,
- database_name sysname NOT NULL,
- schema_id integer NOT NULL,
- schema_name sysname NOT NULL,
- object_id integer NOT NULL,
- table_name sysname NOT NULL,
- index_id integer NOT NULL,
- index_name sysname NULL,
- column_name sysname NOT NULL,
- is_primary_key bit NULL,
- is_unique bit NULL,
- is_unique_constraint bit NULL,
- is_indexed_view integer NOT NULL,
- is_foreign_key bit NULL,
- is_foreign_key_reference bit NULL,
- key_ordinal tinyint NOT NULL,
- index_column_id integer NOT NULL,
- is_descending_key bit NOT NULL,
- is_included_column bit NULL,
- filter_definition nvarchar(max) NULL,
- is_max_length integer NOT NULL,
- user_seeks bigint NOT NULL,
- user_scans bigint NOT NULL,
- user_lookups bigint NOT NULL,
- user_updates bigint NOT NULL,
- last_user_seek datetime NULL,
- last_user_scan datetime NULL,
- last_user_lookup datetime NULL,
- last_user_update datetime NULL,
- is_eligible_for_dedupe bit NOT NULL
- PRIMARY KEY CLUSTERED(database_id, object_id, index_id, column_name)
- );
-
- CREATE TABLE
- #index_analysis
- (
- database_id integer NOT NULL,
- database_name sysname NOT NULL,
- schema_id integer NOT NULL,
- schema_name sysname NOT NULL,
- object_id integer NOT NULL,
- table_name sysname NOT NULL,
- index_id integer NOT NULL,
- index_name sysname NOT NULL,
- is_unique bit NULL,
- key_columns nvarchar(MAX) NULL,
- included_columns nvarchar(MAX) NULL,
- filter_definition nvarchar(MAX) NULL,
- /* Query plan for original CREATE INDEX statement */
- original_index_definition nvarchar(MAX) NULL,
- /*
- Consolidation rule that matched (e.g., Key Duplicate, Key Subset, etc)
- For exact duplicates, use one of: Exact Duplicate, Reverse Duplicate, or Equal Except For Filter
- */
- consolidation_rule nvarchar(256) NULL,
- /*
- Action to take (e.g., DISABLE, MERGE INCLUDES, KEEP)
- If NULL, no action to be taken
- */
- action nvarchar(100) NULL,
- /* Target index to merge with or use instead of this one */
- target_index_name sysname NULL,
- /* When this is a target, the index which points to it as a supersedes in consolidation */
- superseded_by nvarchar(4000) NULL,
- /* Priority score from 0-1 to determine which index to keep (higher is better) */
- index_priority decimal(10,6) NULL
- PRIMARY KEY CLUSTERED(database_id, object_id, index_id)
- );
-
- CREATE TABLE
- #compression_eligibility
- (
- database_id integer NOT NULL,
- database_name sysname NOT NULL,
- schema_id integer NOT NULL,
- schema_name sysname NOT NULL,
- object_id integer NOT NULL,
- table_name sysname NOT NULL,
- index_id integer NOT NULL,
- index_name sysname NOT NULL,
- can_compress bit NOT NULL,
- reason nvarchar(200) NULL,
- PRIMARY KEY CLUSTERED(database_id, object_id, index_id)
- );
-
- CREATE TABLE
- #index_cleanup_results
- (
- result_type varchar(100) NOT NULL,
- sort_order integer NOT NULL,
- database_name sysname NULL,
- schema_name sysname NULL,
- table_name sysname NULL,
- index_name sysname NULL,
- script_type nvarchar(60) NULL, /* Type of script (e.g., MERGE SCRIPT, DISABLE SCRIPT, etc.) */
- consolidation_rule nvarchar(256) NULL, /* Reason for action (e.g., Exact Duplicate, Key Subset) */
- target_index_name sysname NULL, /* If this index is a duplicate, indicates which index is the preferred one */
- superseded_info nvarchar(4000) NULL, /* If this is a kept index, indicates which indexes it supersedes */
- additional_info nvarchar(max) NULL, /* Additional information about the action */
- original_index_definition nvarchar(max) NULL, /* Original statement to create the index */
- index_size_gb decimal(38, 4) NULL, /* Size of the index in GB */
- index_rows bigint NULL, /* Number of rows in the index */
- index_reads bigint NULL, /* Total reads (seeks + scans + lookups) */
- index_writes bigint NULL, /* Total writes */
- script nvarchar(max) NULL /* Script to execute the action */
- );
-
- CREATE TABLE
- #key_duplicate_dedupe
- (
- database_id integer NOT NULL,
- object_id integer NOT NULL,
- database_name sysname NOT NULL,
- schema_name sysname NOT NULL,
- table_name sysname NOT NULL,
- base_key_columns nvarchar(max) NULL,
- filter_definition nvarchar(max) NULL,
- winning_index_name sysname NULL,
- index_list nvarchar(max) NULL,
- );
-
- CREATE TABLE
- #include_subset_dedupe
- (
- database_id integer NOT NULL,
- object_id integer NOT NULL,
- subset_index_name sysname NULL,
- superset_index_name sysname NULL,
- subset_included_columns nvarchar(max) NULL,
- superset_included_columns nvarchar(max) NULL
- );
-
- /* Create a new temp table for detailed reporting statistics */
- CREATE TABLE
- #index_reporting_stats
- (
- summary_level varchar(20) NOT NULL, /* 'DATABASE', 'TABLE', 'INDEX', 'SUMMARY' */
- database_name sysname NULL,
- schema_name sysname NULL,
- table_name sysname NULL,
- index_name sysname NULL,
- server_uptime_days integer NULL,
- uptime_warning bit NULL,
- tables_analyzed integer NULL,
- index_count integer NULL,
- total_size_gb decimal(38, 4) NULL,
- total_rows bigint NULL,
- unused_indexes integer NULL,
- unused_size_gb decimal(38, 4) NULL,
- indexes_to_disable integer NULL,
- indexes_to_merge integer NULL,
- avg_indexes_per_table decimal(10, 2) NULL,
- space_saved_gb decimal(10, 4) NULL,
- compression_min_savings_gb decimal(10, 4) NULL,
- compression_max_savings_gb decimal(10, 4) NULL,
- total_min_savings_gb decimal(10, 4) NULL,
- total_max_savings_gb decimal(10, 4) NULL,
- /* Index usage metrics */
- total_reads bigint NULL,
- total_writes bigint NULL,
- user_seeks bigint NULL,
- user_scans bigint NULL,
- user_lookups bigint NULL,
- user_updates bigint NULL,
- /* Operational stats */
- range_scan_count bigint NULL,
- singleton_lookup_count bigint NULL,
- /* Lock stats */
- row_lock_count bigint NULL,
- row_lock_wait_count bigint NULL,
- row_lock_wait_in_ms bigint NULL,
- page_lock_count bigint NULL,
- page_lock_wait_count bigint NULL,
- page_lock_wait_in_ms bigint NULL,
- /* Latch stats */
- page_latch_wait_count bigint NULL,
- page_latch_wait_in_ms bigint NULL,
- page_io_latch_wait_count bigint NULL,
- page_io_latch_wait_in_ms bigint NULL,
- /* Misc stats */
- forwarded_fetch_count bigint NULL,
- leaf_insert_count bigint NULL,
- leaf_update_count bigint NULL,
- leaf_delete_count bigint NULL
- );
-
- /*
- Start insert queries
- */
-
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #filtered_object insert', 0, 0) WITH NOWAIT;
- END;
-
- SELECT
- @sql = N'
- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;';
-
- SELECT
- @sql = N'
- SELECT DISTINCT
- @database_id,
- database_name = DB_NAME(@database_id),
- schema_id = t.schema_id,
- schema_name = s.name,
- object_id = t.object_id,
- table_name = t.name,
- index_id = i.index_id,
- index_name = ISNULL(i.name, t.name + N''.Heap''),
- can_compress =
- CASE
- WHEN p.index_id > 0
- AND p.data_compression = 0
- THEN 1
- ELSE 0
- END
- FROM ' + QUOTENAME(@database_name) + N'.sys.tables AS t
- JOIN ' + QUOTENAME(@database_name) + N'.sys.schemas AS s
- ON t.schema_id = s.schema_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.indexes AS i
- ON t.object_id = i.object_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.partitions AS p
- ON i.object_id = p.object_id
- AND i.index_id = p.index_id
- LEFT JOIN ' + QUOTENAME(@database_name) + N'.sys.dm_db_index_usage_stats AS us
- ON t.object_id = us.object_id
- AND us.database_id = @database_id
- WHERE t.is_ms_shipped = 0
- AND t.type <> N''TF''
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.views AS v
- WHERE v.object_id = i.object_id
- )';
-
- IF /* Check for temporal tables support */
- (
- CONVERT
- (
- integer,
- SERVERPROPERTY('EngineEdition')
- ) IN (5, 8) /* Azure SQL DB or Managed Instance */
- OR CONVERT
- (
- integer,
- SUBSTRING
- (
- CONVERT
- (
- varchar(20),
- SERVERPROPERTY('ProductVersion')
- ),
- 1,
- 2
- )
- ) >= 13
- ) /* SQL 2016+ */
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('adding temporal table screening', 0, 0) WITH NOWAIT;
- END;
-
- SET @sql += N'
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.tables AS t
- WHERE t.object_id = i.object_id
- AND t.temporal_type > 0
- )';
- END;
-
-
- IF @object_id IS NOT NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('adding object_id filter', 0, 0) WITH NOWAIT;
- END;
-
- SELECT @sql += N'
- AND t.object_id = @object_id';
- END;
-
- SET @sql += N'
- AND EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.dm_db_partition_stats AS ps
- JOIN ' + QUOTENAME(@database_name) + N'.sys.allocation_units AS au
- ON ps.partition_id = au.container_id
- WHERE ps.object_id = t.object_id
- GROUP BY
- ps.object_id
- HAVING
- SUM(au.total_pages) * 8.0 / 1048576.0 >= @min_size_gb
- )
- AND EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.dm_db_partition_stats AS ps
- WHERE ps.object_id = t.object_id
- AND ps.index_id IN (0, 1)
- GROUP BY
- ps.object_id
- HAVING
- SUM(ps.row_count) >= @min_rows
- )
- AND EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.dm_db_index_usage_stats AS ius
- WHERE ius.object_id = t.object_id
- AND ius.database_id = @database_id
- GROUP BY
- ius.object_id
- HAVING
- SUM(ius.user_seeks + ius.user_scans + ius.user_lookups) >= @min_reads
- OR
- SUM(ius.user_updates) >= @min_writes
- )
- OPTION(RECOMPILE);
- ';
-
- IF @debug = 1
- BEGIN
- PRINT @sql;
- END;
-
- INSERT
- #filtered_objects
- WITH
- (TABLOCK)
- (
- database_id,
- database_name,
- schema_id,
- schema_name,
- object_id,
- table_name,
- index_id,
- index_name,
- can_compress
- )
- EXECUTE sys.sp_executesql
- @sql,
- N'@database_id int,
- @min_reads bigint,
- @min_writes bigint,
- @min_size_gb decimal(10,2),
- @min_rows bigint,
- @object_id integer',
- @database_id,
- @min_reads,
- @min_writes,
- @min_size_gb,
- @min_rows,
- @object_id;
-
- IF ROWCOUNT_BIG() = 0
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('No rows inserted into #filtered_objects', 0, 0) WITH NOWAIT;
- END;
- END;
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#filtered_objects',
- fo.*
- FROM #filtered_objects AS fo
- OPTION(RECOMPILE);
-
- RAISERROR('Generating #compression_eligibility insert', 0, 0) WITH NOWAIT;
- END;
-
- /* Populate compression eligibility table */
- INSERT INTO
- #compression_eligibility
- WITH
- (TABLOCK)
- (
- database_id,
- database_name,
- schema_id,
- schema_name,
- object_id,
- table_name,
- index_id,
- index_name,
- can_compress,
- reason
- )
- SELECT
- fo.database_id,
- fo.database_name,
- fo.schema_id,
- fo.schema_name,
- fo.object_id,
- fo.table_name,
- fo.index_id,
- fo.index_name,
- 1, /* Default to compressible */
- NULL
- FROM #filtered_objects AS fo
- WHERE fo.can_compress = 1
- OPTION(RECOMPILE);
-
- /* If SQL Server edition doesn't support compression, mark all as ineligible */
- IF @can_compress = 0
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('updating compression eligibility', 0, 0) WITH NOWAIT;
- END;
-
- UPDATE
- #compression_eligibility
- SET
- #compression_eligibility.can_compress = 0,
- #compression_eligibility.reason = N'SQL Server edition or version does not support compression'
- WHERE #compression_eligibility.can_compress = 1
- OPTION(RECOMPILE);
- END;
-
- /* Check for sparse columns or incompatible data types */
- IF @can_compress = 1
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('Updating #compression_eligibility', 0, 0) WITH NOWAIT;
- END;
-
- SELECT
- @sql = N'
- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-
- UPDATE
- ce
- SET
- ce.can_compress = 0,
- ce.reason = ''Table contains sparse columns or incompatible data types''
- FROM #compression_eligibility AS ce
- WHERE EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.columns AS c
- JOIN ' + QUOTENAME(@database_name) + N'.sys.types AS t
- ON c.user_type_id = t.user_type_id
- WHERE c.object_id = ce.object_id
- AND
- (
- c.is_sparse = 1
- OR t.name IN (N''text'', N''ntext'', N''image'')
- )
- )
- OPTION(RECOMPILE);
- ';
-
- IF @debug = 1
- BEGIN
- PRINT @sql;
- END;
-
- EXECUTE sys.sp_executesql
- @sql;
- END;
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#compression_eligibility',
- ce.*
- FROM #compression_eligibility AS ce
- OPTION(RECOMPILE);
-
- RAISERROR('Generating #operational_stats insert', 0, 0) WITH NOWAIT;
- END;
-
- SELECT
- @sql = N'
- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;';
-
- SELECT
- @sql += N'
- SELECT
- os.database_id,
- database_name = DB_NAME(os.database_id),
- schema_id = s.schema_id,
- schema_name = s.name,
- os.object_id,
- table_name = t.name,
- os.index_id,
- index_name = ISNULL(i.name, t.name + N''.Heap''),
- range_scan_count = SUM(os.range_scan_count),
- singleton_lookup_count = SUM(os.singleton_lookup_count),
- forwarded_fetch_count = SUM(os.forwarded_fetch_count),
- lob_fetch_in_pages = SUM(os.lob_fetch_in_pages),
- row_overflow_fetch_in_pages = SUM(os.row_overflow_fetch_in_pages),
- leaf_insert_count = SUM(os.leaf_insert_count),
- leaf_update_count = SUM(os.leaf_update_count),
- leaf_delete_count = SUM(os.leaf_delete_count),
- leaf_ghost_count = SUM(os.leaf_ghost_count),
- nonleaf_insert_count = SUM(os.nonleaf_insert_count),
- nonleaf_update_count = SUM(os.nonleaf_update_count),
- nonleaf_delete_count = SUM(os.nonleaf_delete_count),
- leaf_allocation_count = SUM(os.leaf_allocation_count),
- nonleaf_allocation_count = SUM(os.nonleaf_allocation_count),
- row_lock_count = SUM(os.row_lock_count),
- row_lock_wait_count = SUM(os.row_lock_wait_count),
- row_lock_wait_in_ms = SUM(os.row_lock_wait_in_ms),
- page_lock_count = SUM(os.page_lock_count),
- page_lock_wait_count = SUM(os.page_lock_wait_count),
- page_lock_wait_in_ms = SUM(os.page_lock_wait_in_ms),
- index_lock_promotion_attempt_count = SUM(os.index_lock_promotion_attempt_count),
- index_lock_promotion_count = SUM(os.index_lock_promotion_count),
- page_latch_wait_count = SUM(os.page_latch_wait_count),
- page_latch_wait_in_ms = SUM(os.page_latch_wait_in_ms),
- tree_page_latch_wait_count = SUM(os.tree_page_latch_wait_count),
- tree_page_latch_wait_in_ms = SUM(os.tree_page_latch_wait_in_ms),
- page_io_latch_wait_count = SUM(os.page_io_latch_wait_count),
- page_io_latch_wait_in_ms = SUM(os.page_io_latch_wait_in_ms),
- page_compression_attempt_count = SUM(os.page_compression_attempt_count),
- page_compression_success_count = SUM(os.page_compression_success_count)
- FROM ' + QUOTENAME(@database_name) + N'.sys.dm_db_index_operational_stats
- (
- @database_id,
- @object_id,
- NULL,
- NULL
- ) AS os
- JOIN ' + QUOTENAME(@database_name) + N'.sys.tables AS t
- ON os.object_id = t.object_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.schemas AS s
- ON t.schema_id = s.schema_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.indexes AS i
- ON os.object_id = i.object_id
- AND os.index_id = i.index_id
- WHERE EXISTS
- (
- SELECT
- 1/0
- FROM #filtered_objects AS fo
- WHERE fo.database_id = os.database_id
- AND fo.object_id = os.object_id
- )
- GROUP BY
- os.database_id,
- DB_NAME(os.database_id),
- s.schema_id,
- s.name,
- os.object_id,
- t.name,
- os.index_id,
- i.name
- OPTION(RECOMPILE);
- ';
-
- IF @debug = 1
- BEGIN
- PRINT @sql;
- END;
-
- INSERT
- #operational_stats
- WITH
- (TABLOCK)
- (
- database_id,
- database_name,
- schema_id,
- schema_name,
- object_id,
- table_name,
- index_id,
- index_name,
- range_scan_count,
- singleton_lookup_count,
- forwarded_fetch_count,
- lob_fetch_in_pages,
- row_overflow_fetch_in_pages,
- leaf_insert_count,
- leaf_update_count,
- leaf_delete_count,
- leaf_ghost_count,
- nonleaf_insert_count,
- nonleaf_update_count,
- nonleaf_delete_count,
- leaf_allocation_count,
- nonleaf_allocation_count,
- row_lock_count,
- row_lock_wait_count,
- row_lock_wait_in_ms,
- page_lock_count,
- page_lock_wait_count,
- page_lock_wait_in_ms,
- index_lock_promotion_attempt_count,
- index_lock_promotion_count,
- page_latch_wait_count,
- page_latch_wait_in_ms,
- tree_page_latch_wait_count,
- tree_page_latch_wait_in_ms,
- page_io_latch_wait_count,
- page_io_latch_wait_in_ms,
- page_compression_attempt_count,
- page_compression_success_count
- )
- EXECUTE sys.sp_executesql
- @sql,
- N'@database_id integer,
- @object_id integer',
- @database_id,
- @object_id;
-
- IF ROWCOUNT_BIG() = 0
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('No rows inserted into #operational_stats', 0, 0) WITH NOWAIT;
- END;
- END;
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#operational_stats',
- os.*
- FROM #operational_stats AS os
- OPTION(RECOMPILE);
-
- RAISERROR('Generating #index_details insert', 0, 0) WITH NOWAIT;
- END;
-
- SELECT
- @sql = N'
- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;';
-
- SELECT
- @sql += N'
- SELECT
- database_id = @database_id,
- database_name = DB_NAME(@database_id),
- t.object_id,
- i.index_id,
- s.schema_id,
- schema_name = s.name,
- table_name = t.name,
- index_name = ISNULL(i.name, t.name + N''.Heap''),
- column_name = c.name,
- i.is_primary_key,
- i.is_unique,
- i.is_unique_constraint,
- is_indexed_view =
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.objects AS so
- WHERE i.object_id = so.object_id
- AND so.is_ms_shipped = 0
- AND so.type = ''V''
- )
- THEN 1
- ELSE 0
- END,
- is_foreign_key =
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.foreign_key_columns AS f
- WHERE f.parent_column_id = c.column_id
- AND f.parent_object_id = c.object_id
- )
- THEN 1
- ELSE 0
- END,
- is_foreign_key_reference =
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.foreign_key_columns AS f
- WHERE f.referenced_column_id = c.column_id
- AND f.referenced_object_id = c.object_id
- )
- THEN 1
- ELSE 0
- END,
- ic.key_ordinal,
- ic.index_column_id,
- ic.is_descending_key,
- ic.is_included_column,
- i.filter_definition,
- is_max_length =
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.types AS t
- WHERE c.system_type_id = t.system_type_id
- AND c.user_type_id = t.user_type_id
- AND t.name IN (N''varchar'', N''nvarchar'')
- AND t.max_length = -1
- )
- THEN 1
- ELSE 0
- END,
- user_seeks = ISNULL(us.user_seeks, 0),
- user_scans = ISNULL(us.user_scans, 0),
- user_lookups = ISNULL(us.user_lookups, 0),
- user_updates = ISNULL(us.user_updates, 0),
- us.last_user_seek,
- us.last_user_scan,
- us.last_user_lookup,
- us.last_user_update,
- is_eligible_for_dedupe =
- CASE
- WHEN i.type = 2
- THEN 1
- WHEN i.type = 1
- THEN 0
- END
- FROM ' + QUOTENAME(@database_name) + N'.sys.tables AS t
- JOIN ' + QUOTENAME(@database_name) + N'.sys.schemas AS s
- ON t.schema_id = s.schema_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.indexes AS i
- ON t.object_id = i.object_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.index_columns AS ic
- ON i.object_id = ic.object_id
- AND i.index_id = ic.index_id
- JOIN ' + QUOTENAME(@database_name) +
- CONVERT
- (
- nvarchar(MAX),
- N'.sys.columns AS c
- ON ic.object_id = c.object_id
- AND ic.column_id = c.column_id
- LEFT JOIN sys.dm_db_index_usage_stats AS us
- ON i.object_id = us.object_id
- AND i.index_id = us.index_id
- AND us.database_id = @database_id
- WHERE t.is_ms_shipped = 0
- AND i.type IN (1, 2)
- AND i.is_disabled = 0
- AND i.is_hypothetical = 0
- AND EXISTS
- (
- SELECT
- 1/0
- FROM #filtered_objects AS fo
- WHERE fo.database_id = @database_id
- AND fo.object_id = t.object_id
- )
- AND EXISTS
- (
- SELECT
- 1/0
- FROM '
- ) + QUOTENAME(@database_name) +
- CONVERT
- (
- nvarchar(MAX),
- N'.sys.dm_db_partition_stats ps
- WHERE ps.object_id = t.object_id
- AND ps.index_id = 1
- AND ps.row_count >= @min_rows
- )'
- );
-
- IF @object_id IS NOT NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('adding object+id filter', 0, 0) WITH NOWAIT;
- END;
-
- SELECT @sql += N'
- AND t.object_id = @object_id';
- END;
-
- SELECT
- @sql += CONVERT
- (
- nvarchar(max),
- N'
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM ' + QUOTENAME(@database_name) + N'.sys.objects AS so
- WHERE i.object_id = so.object_id
- AND so.is_ms_shipped = 0
- AND so.type = N''TF''
- )
- OPTION(RECOMPILE);
- '
- );
-
- IF @debug = 1
- BEGIN
- PRINT SUBSTRING(@sql, 1, 4000);
- PRINT SUBSTRING(@sql, 4000, 8000);
- END;
-
- INSERT
- #index_details
- WITH
- (TABLOCK)
- (
- database_id,
- database_name,
- object_id,
- index_id,
- schema_id,
- schema_name,
- table_name,
- index_name,
- column_name,
- is_primary_key,
- is_unique,
- is_unique_constraint,
- is_indexed_view,
- is_foreign_key,
- is_foreign_key_reference,
- key_ordinal,
- index_column_id,
- is_descending_key,
- is_included_column,
- filter_definition,
- is_max_length,
- user_seeks,
- user_scans,
- user_lookups,
- user_updates,
- last_user_seek,
- last_user_scan,
- last_user_lookup,
- last_user_update,
- is_eligible_for_dedupe
- )
- EXECUTE sys.sp_executesql
- @sql,
- N'@database_id integer,
- @object_id integer,
- @min_rows integer',
- @database_id,
- @object_id,
- @min_rows;
-
- IF ROWCOUNT_BIG() = 0
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('No rows inserted into #index_details', 0, 0) WITH NOWAIT;
- END;
- END;
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_details',
- *
- FROM #index_details AS id;
-
- RAISERROR('Generating #partition_stats insert', 0, 0) WITH NOWAIT;
- END;
-
- SELECT
- @sql = N'
- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;';
-
- SELECT
- @sql += N'
- SELECT
- database_id = @database_id,
- database_name = DB_NAME(@database_id),
- x.object_id,
- x.index_id,
- x.schema_id,
- x.schema_name,
- x.table_name,
- x.index_name,
- x.partition_id,
- x.partition_number,
- x.total_rows,
- x.total_space_gb,
- x.reserved_lob_gb,
- x.reserved_row_overflow_gb,
- x.data_compression_desc,
- built_on =
- ISNULL
- (
- psfg.partition_scheme_name,
- psfg.filegroup_name
- ),
- psfg.partition_function_name,
- pc.partition_columns
- FROM
- (
- SELECT DISTINCT
- ps.object_id,
- ps.index_id,
- s.schema_id,
- schema_name = s.name,
- table_name = t.name,
- index_name = ISNULL(i.name, t.name + N''.Heap''),
- ps.partition_id,
- p.partition_number,
- total_rows = ps.row_count,
- total_space_gb = SUM(a.total_pages) * 8 / 1024.0 / 1024.0, /* Convert directly to GB */
- reserved_lob_gb = SUM(ps.lob_reserved_page_count) * 8. / 1024. / 1024.0, /* Convert directly to GB */
- reserved_row_overflow_gb = SUM(ps.row_overflow_reserved_page_count) * 8. / 1024. / 1024.0, /* Convert directly to GB */
- p.data_compression_desc,
- i.data_space_id
- FROM ' + QUOTENAME(@database_name) + N'.sys.tables AS t
- JOIN ' + QUOTENAME(@database_name) + N'.sys.indexes AS i
- ON t.object_id = i.object_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.schemas AS s
- ON t.schema_id = s.schema_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.partitions AS p
- ON i.object_id = p.object_id
- AND i.index_id = p.index_id
- JOIN ' + QUOTENAME(@database_name) + N'.sys.allocation_units AS a
- ON p.partition_id = a.container_id
- LEFT HASH JOIN ' + QUOTENAME(@database_name) + N'.sys.dm_db_partition_stats AS ps
- ON p.partition_id = ps.partition_id
- WHERE t.type <> N''TF''
- AND i.type IN (1, 2)
- AND EXISTS
- (
- SELECT
- 1/0
- FROM #filtered_objects AS fo
- WHERE fo.database_id = @database_id
- AND fo.object_id = t.object_id
- )';
-
- IF @object_id IS NOT NULL
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('adding in object_id filter', 0, 0) WITH NOWAIT;
- END;
-
- SELECT @sql += N'
- AND t.object_id = @object_id';
- END;
-
- SELECT
- @sql += N'
- GROUP BY
- ps.object_id,
- ps.index_id,
- s.schema_id,
- s.name,
- t.name,
- i.name,
- ps.partition_id,
- p.partition_number,
- ps.row_count,
- p.data_compression_desc,
- i.data_space_id
- ) AS x
- OUTER APPLY
- (
- SELECT
- filegroup_name =
- fg.name,
- partition_scheme_name =
- ps.name,
- partition_function_name =
- pf.name
- FROM ' + QUOTENAME(@database_name) + N'.sys.filegroups AS fg
- FULL JOIN ' + QUOTENAME(@database_name) + N'.sys.partition_schemes AS ps
- ON ps.data_space_id = fg.data_space_id
- LEFT JOIN ' + QUOTENAME(@database_name) + N'.sys.partition_functions AS pf
- ON pf.function_id = ps.function_id
- WHERE x.data_space_id = fg.data_space_id
- OR x.data_space_id = ps.data_space_id
- ) AS psfg
- OUTER APPLY
- (
- SELECT
- partition_columns =
- STUFF
- (
- (
- SELECT
- N'', '' +
- c.name
- FROM ' + QUOTENAME(@database_name) + N'.sys.index_columns AS ic
- JOIN ' + QUOTENAME(@database_name) + N'.sys.columns AS c
- ON c.object_id = ic.object_id
- AND c.column_id = ic.column_id
- WHERE ic.object_id = x.object_id
- AND ic.index_id = x.index_id
- AND ic.partition_ordinal > 0
- ORDER BY
- ic.partition_ordinal
- FOR
- XML
- PATH(''''),
- TYPE
- ).value(''.'', ''nvarchar(max)''),
- 1,
- 2,
- ''''
- )
- ) AS pc
- OPTION(RECOMPILE);
- ';
-
- IF @debug = 1
- BEGIN
- PRINT SUBSTRING(@sql, 1, 4000);
- PRINT SUBSTRING(@sql, 4000, 8000);
- END;
-
- INSERT
- #partition_stats WITH(TABLOCK)
- (
- database_id,
- database_name,
- object_id,
- index_id,
- schema_id,
- schema_name,
- table_name,
- index_name,
- partition_id,
- partition_number,
- total_rows,
- total_space_gb,
- reserved_lob_gb,
- reserved_row_overflow_gb,
- data_compression_desc,
- built_on,
- partition_function_name,
- partition_columns
- )
- EXECUTE sys.sp_executesql
- @sql,
- N'@database_id integer,
- @object_id integer',
- @database_id,
- @object_id;
-
- IF ROWCOUNT_BIG() = 0
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('No rows inserted into #partition_stats', 0, 0) WITH NOWAIT;
- END;
- END;
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#partition_stats',
- *
- FROM #partition_stats AS ps
- OPTION(RECOMPILE);
-
- RAISERROR('Performing #index_analysis insert', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_analysis
- WITH
- (TABLOCK)
- (
- database_id,
- database_name,
- schema_id,
- schema_name,
- table_name,
- object_id,
- index_id,
- index_name,
- is_unique,
- key_columns,
- included_columns,
- filter_definition,
- original_index_definition
- )
- SELECT
- @database_id,
- database_name = DB_NAME(@database_id),
- id1.schema_id,
- id1.schema_name,
- id1.table_name,
- id1.object_id,
- id1.index_id,
- id1.index_name,
- id1.is_unique,
- key_columns =
- STUFF
- (
- (
- SELECT
- N', ' +
- id2.column_name +
- CASE
- WHEN id2.is_descending_key = 1
- THEN N' DESC'
- ELSE N''
- END
- FROM #index_details id2
- WHERE id2.object_id = id1.object_id
- AND id2.index_id = id1.index_id
- AND id2.is_included_column = 0
- GROUP BY
- id2.column_name,
- id2.is_descending_key,
- id2.key_ordinal
- ORDER BY
- id2.key_ordinal
- FOR
- XML
- PATH(''),
- TYPE
- ).value('text()[1]','nvarchar(max)'),
- 1,
- 2,
- ''
- ),
- included_columns =
- STUFF
- (
- (
- SELECT
- N', ' +
- id2.column_name
- FROM #index_details id2
- WHERE id2.object_id = id1.object_id
- AND id2.index_id = id1.index_id
- AND id2.is_included_column = 1
- GROUP BY
- id2.column_name
- ORDER BY
- id2.column_name
- FOR
- XML
- PATH(''),
- TYPE
- ).value('text()[1]','nvarchar(max)'),
- 1,
- 2,
- ''
- ),
- id1.filter_definition,
- /* Store the original index definition for validation */
- original_index_definition =
- CASE
- /* For unique constraints, use ALTER TABLE ADD CONSTRAINT syntax */
- WHEN id1.is_unique_constraint = 1
- THEN
- N'ALTER TABLE ' +
- QUOTENAME(DB_NAME(@database_id)) +
- N'.' +
- QUOTENAME(id1.schema_name) +
- N'.' +
- QUOTENAME(id1.table_name) +
- N' ADD CONSTRAINT ' +
- QUOTENAME(id1.index_name) +
- N' UNIQUE ('
- /* For regular indexes, use CREATE INDEX syntax */
- ELSE
- N'CREATE ' +
- CASE WHEN id1.is_unique = 1 THEN N'UNIQUE ' ELSE N'' END +
- N'INDEX ' +
- QUOTENAME(id1.index_name) +
- N' ON ' +
- QUOTENAME(DB_NAME(@database_id)) +
- N'.' +
- QUOTENAME(id1.schema_name) +
- N'.' +
- QUOTENAME(id1.table_name) +
- N' ('
- END +
- STUFF
- (
- (
- SELECT
- N', ' +
- id2.column_name +
- CASE
- WHEN id2.is_descending_key = 1
- THEN N' DESC'
- ELSE N''
- END
- FROM #index_details id2
- WHERE id2.object_id = id1.object_id
- AND id2.index_id = id1.index_id
- AND id2.is_included_column = 0
- GROUP BY
- id2.column_name,
- id2.is_descending_key,
- id2.key_ordinal
- ORDER BY
- id2.key_ordinal
- FOR
- XML
- PATH(''),
- TYPE
- ).value('text()[1]','nvarchar(max)'),
- 1,
- 2,
- ''
- ) +
- N')' +
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- FROM #index_details id3
- WHERE id3.object_id = id1.object_id
- AND id3.index_id = id1.index_id
- AND id3.is_included_column = 1
- )
- THEN N' INCLUDE (' +
- STUFF
- (
- (
- SELECT
- N', ' +
- id4.column_name
- FROM #index_details id4
- WHERE id4.object_id = id1.object_id
- AND id4.index_id = id1.index_id
- AND id4.is_included_column = 1
- GROUP BY
- id4.column_name
- ORDER BY
- id4.column_name
- FOR
- XML
- PATH(''),
- TYPE
- ).value('text()[1]','nvarchar(max)'),
- 1,
- 2,
- ''
- ) +
- N')'
- ELSE N''
- END +
- CASE
- WHEN id1.filter_definition IS NOT NULL
- THEN N' WHERE ' + id1.filter_definition
- ELSE N''
- END
- FROM #index_details id1
- WHERE id1.is_eligible_for_dedupe = 1
- GROUP BY
- id1.schema_name,
- id1.schema_id,
- id1.table_name,
- id1.index_name,
- id1.index_id,
- id1.is_unique,
- id1.object_id,
- id1.index_id,
- id1.filter_definition,
- id1.is_unique_constraint
- OPTION(RECOMPILE);
-
- IF ROWCOUNT_BIG() = 0
- BEGIN
- IF @debug = 1
- BEGIN
- RAISERROR('No rows inserted into #index_analysis', 0, 0) WITH NOWAIT;
- END;
- END;
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
-
- RAISERROR('Starting updates', 0, 0) WITH NOWAIT;
- END;
-
- /* Calculate index priority scores based on actual columns that exist */
- UPDATE
- #index_analysis
- SET
- #index_analysis.index_priority =
- CASE
- WHEN #index_analysis.index_id = 1
- THEN 1000 /* Clustered indexes get highest priority */
- ELSE 0
- END
- +
- CASE
- /* Unique indexes get high priority, but reduce priority for unique constraints */
- WHEN #index_analysis.is_unique = 1 AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id_uc
- WHERE id_uc.index_id = #index_analysis.index_id
- AND id_uc.object_id = #index_analysis.object_id
- AND id_uc.is_unique_constraint = 1
- ) THEN 500
- /* Unique constraints get lower priority */
- WHEN #index_analysis.is_unique = 1 AND EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id_uc
- WHERE id_uc.index_id = #index_analysis.index_id
- AND id_uc.object_id = #index_analysis.object_id
- AND id_uc.is_unique_constraint = 1
- ) THEN 50
- ELSE 0
- END
- +
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id
- WHERE id.index_id = #index_analysis.index_id
- AND id.object_id = #index_analysis.object_id
- AND id.user_seeks > 0
- ) THEN 200
- ELSE 0
- END /* Indexes with seeks get priority */
- +
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- 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 */
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after priority score',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- 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 @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 1',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Rule 2: Exact duplicates - matching key columns and includes */
- UPDATE
- ia1
- SET
- ia1.consolidation_rule = N'Exact Duplicate',
- ia1.target_index_name =
- CASE
- WHEN ia1.index_priority > ia2.index_priority
- THEN NULL /* This index is the keeper */
- WHEN ia1.index_priority = ia2.index_priority
- AND ia1.index_name < ia2.index_name
- THEN NULL /* When tied, use alphabetical ordering for consistency */
- ELSE ia2.index_name /* Other index is the keeper */
- END,
- ia1.action =
- CASE
- WHEN ia1.index_priority > ia2.index_priority
- THEN N'KEEP' /* This index is the keeper */
- WHEN ia1.index_priority = ia2.index_priority
- AND ia1.index_name < ia2.index_name
- THEN N'KEEP' /* When tied, use alphabetical ordering for consistency */
- ELSE N'DISABLE' /* Other index gets disabled */
- END
- FROM #index_analysis AS ia1
- JOIN #index_analysis AS ia2
- ON ia1.database_id = ia2.database_id
- AND ia1.object_id = ia2.object_id
- AND ia1.index_name <> ia2.index_name
- AND ia1.key_columns = ia2.key_columns /* Exact key match */
- AND ISNULL(ia1.included_columns, '') = ISNULL(ia2.included_columns, '') /* Exact includes match */
- AND ISNULL(ia1.filter_definition, '') = ISNULL(ia2.filter_definition, '') /* Matching filters */
- WHERE ia1.consolidation_rule IS NULL /* Not already processed */
- AND ia2.consolidation_rule IS NULL /* Not already processed */
- /* Exclude unique constraints - we'll handle those separately in Rule 7 */
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id1_uc
- WHERE id1_uc.database_id = ia1.database_id
- AND id1_uc.object_id = ia1.object_id
- AND id1_uc.index_id = ia1.index_id
- AND id1_uc.is_unique_constraint = 1
- )
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id2_uc
- WHERE id2_uc.database_id = ia2.database_id
- AND id2_uc.object_id = ia2.object_id
- AND id2_uc.index_id = ia2.index_id
- AND id2_uc.is_unique_constraint = 1
- )
- AND EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id1
- WHERE id1.database_id = ia1.database_id
- AND id1.object_id = ia1.object_id
- AND id1.index_id = ia1.index_id
- AND id1.is_eligible_for_dedupe = 1
- )
- AND EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id2
- WHERE id2.database_id = ia2.database_id
- AND id2.object_id = ia2.object_id
- AND id2.index_id = ia2.index_id
- AND id2.is_eligible_for_dedupe = 1
- )
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 2',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
-
- /* Special debug for exact duplicates */
- RAISERROR('Special debug for exact duplicates after rule 2:', 0, 0) WITH NOWAIT;
- SELECT
- ia1.index_name AS index1_name,
- ia1.action AS index1_action,
- ia1.consolidation_rule AS index1_rule,
- ia1.index_priority AS index1_priority,
- ia1.target_index_name AS index1_target,
- ia1.filter_definition AS index1_filter,
- ia2.index_name AS index2_name,
- ia2.action AS index2_action,
- ia2.consolidation_rule AS index2_rule,
- ia2.index_priority AS index2_priority,
- ia2.target_index_name AS index2_target,
- ia2.filter_definition AS index2_filter
- FROM #index_analysis AS ia1
- JOIN #index_analysis AS ia2
- ON ia1.database_id = ia2.database_id
- AND ia1.object_id = ia2.object_id
- AND ia1.index_name <> ia2.index_name
- AND ia1.key_columns = ia2.key_columns /* Exact key match */
- AND ISNULL(ia1.included_columns, '') = ISNULL(ia2.included_columns, '') /* Exact includes match */
- AND ISNULL(ia1.filter_definition, '') = ISNULL(ia2.filter_definition, '') /* Matching filters */
- WHERE ia1.consolidation_rule = N'Exact Duplicate'
- OR ia2.consolidation_rule = N'Exact Duplicate'
- ORDER BY ia1.index_name
- OPTION(RECOMPILE);
- END;
-
- /* Rule 3: Key duplicates - matching key columns, different includes */
- UPDATE
- ia1
- SET
- ia1.consolidation_rule = N'Key Duplicate',
- ia1.target_index_name =
- CASE
- /* If one is unique and the other isn't, prefer the unique one */
- WHEN ia1.is_unique = 1
- AND ia2.is_unique = 0
- THEN NULL
- WHEN ia1.is_unique = 0
- AND ia2.is_unique = 1
- THEN ia2.index_name
- /* Otherwise use priority */
- WHEN ia1.index_priority >= ia2.index_priority
- THEN NULL
- ELSE ia2.index_name
- END,
- ia1.action =
- CASE
- WHEN (ia1.is_unique = 1 AND ia2.is_unique = 0)
- OR
- (
- ia1.index_priority >= ia2.index_priority
- AND NOT (ia1.is_unique = 0 AND ia2.is_unique = 1)
- )
- AND ISNULL(ia1.included_columns, N'') <> ISNULL(ia2.included_columns, N'')
- THEN N'MERGE INCLUDES' /* Keep this index but merge includes */
- ELSE N'DISABLE' /* Other index is keeper, disable this one */
- END,
- /* For the winning index, set clear superseded_by text for the report */
- ia1.superseded_by =
- CASE
- WHEN (ia1.is_unique = 1 AND ia2.is_unique = 0)
- OR
- (
- ia1.index_priority >= ia2.index_priority
- AND NOT (ia1.is_unique = 0 AND ia2.is_unique = 1)
- )
- THEN N'Supersedes ' +
- ia2.index_name
- ELSE NULL
- END
- FROM #index_analysis AS ia1
- JOIN #index_analysis AS ia2
- ON ia1.database_id = ia2.database_id
- AND ia1.object_id = ia2.object_id
- AND ia1.index_name <> ia2.index_name
- AND ia1.key_columns = ia2.key_columns /* Exact key match */
- AND ISNULL(ia1.included_columns, '') <> ISNULL(ia2.included_columns, '') /* Different includes */
- AND ISNULL(ia1.filter_definition, '') = ISNULL(ia2.filter_definition, '') /* Matching filters */
- WHERE ia1.consolidation_rule IS NULL /* Not already processed */
- AND ia2.consolidation_rule IS NULL /* Not already processed */
- /* Exclude pairs where either one is a unique constraint (we'll handle those separately in Rule 7) */
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id1_uc
- WHERE id1_uc.database_id = ia1.database_id
- AND id1_uc.object_id = ia1.object_id
- AND id1_uc.index_id = ia1.index_id
- AND id1_uc.is_unique_constraint = 1
- )
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id2_uc
- WHERE id2_uc.database_id = ia2.database_id
- AND id2_uc.object_id = ia2.object_id
- AND id2_uc.index_id = ia2.index_id
- AND id2_uc.is_unique_constraint = 1
- )
- AND EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id1
- WHERE id1.database_id = ia1.database_id
- AND id1.object_id = ia1.object_id
- AND id1.index_id = ia1.index_id
- AND id1.is_eligible_for_dedupe = 1
- )
- AND EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id2
- WHERE id2.database_id = ia2.database_id
- AND id2.object_id = ia2.object_id
- AND id2.index_id = ia2.index_id
- AND id2.is_eligible_for_dedupe = 1
- )
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 3',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Rule 4: Superset/subset key columns */
- UPDATE
- ia1
- SET
- ia1.consolidation_rule = N'Key Subset',
- ia1.target_index_name = ia2.index_name,
- ia1.action = N'DISABLE' /* The narrower index gets disabled */
- FROM #index_analysis AS ia1
- JOIN #index_analysis AS ia2
- ON ia1.database_id = ia2.database_id
- AND ia1.object_id = ia2.object_id
- AND ia1.index_name <> ia2.index_name
- AND ia2.key_columns LIKE (ia1.key_columns + '%') /* ia2 has wider key that starts with ia1's key */
- AND ISNULL(ia1.filter_definition, '') = ISNULL(ia2.filter_definition, '') /* Matching filters */
- /* Exception: If narrower index is unique and wider is not, they should not be merged */
- AND NOT (ia1.is_unique = 1 AND ia2.is_unique = 0)
- WHERE ia1.consolidation_rule IS NULL /* Not already processed */
- AND ia2.consolidation_rule IS NULL /* Not already processed */
- AND EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id1
- WHERE id1.database_id = ia1.database_id
- AND id1.object_id = ia1.object_id
- AND id1.index_id = ia1.index_id
- AND id1.is_eligible_for_dedupe = 1
- )
- AND EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id2
- WHERE id2.database_id = ia2.database_id
- AND id2.object_id = ia2.object_id
- AND id2.index_id = ia2.index_id
- AND id2.is_eligible_for_dedupe = 1
- )
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 4',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Rule 5: Mark superset indexes for merging with includes from subset */
- UPDATE
- ia2
- SET
- ia2.consolidation_rule = N'Key Superset',
- ia2.action = N'MERGE INCLUDES', /* The wider index gets merged with includes */
- ia2.superseded_by =
- ISNULL
- (
- 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
- AND ia1.object_id = ia2.object_id
- AND ia1.target_index_name = ia2.index_name /* Link from Rule 4 */
- WHERE ia1.consolidation_rule = N'Key Subset'
- AND ia1.action = N'DISABLE'
- AND ia2.consolidation_rule IS NULL /* Not already processed */
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 5',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Rule 6: Merge includes from subset to superset indexes */
- WITH
- KeySubsetSuperset AS
- (
- SELECT
- superset.database_id,
- superset.object_id,
- superset.index_id,
- superset.index_name,
- superset.included_columns AS superset_includes,
- subset.included_columns AS subset_includes
- FROM #index_analysis AS superset
- JOIN #index_analysis AS subset
- ON superset.database_id = subset.database_id
- AND superset.object_id = subset.object_id
- AND subset.target_index_name = superset.index_name
- WHERE superset.action = N'MERGE INCLUDES'
- AND subset.action = N'DISABLE'
- AND superset.consolidation_rule = N'Key Superset'
- AND subset.consolidation_rule = N'Key Subset'
- )
- UPDATE
- ia
- SET
- ia.included_columns =
- CASE
- /* If both have includes, combine them without duplicates */
- WHEN kss.superset_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 */
- (
- SELECT
- /* Combine both sets of includes */
- combined_cols =
- STUFF
- (
- (
- SELECT DISTINCT
- N', ' +
- t.c.value('.', 'sysname')
- FROM
- (
- /* Create XML from superset includes */
- SELECT
- x = CONVERT
- (
- xml,
- N'' +
- REPLACE(kss.superset_includes, N', ', N'') +
- N''
- )
-
- UNION ALL
-
- /* Create XML from subset includes */
- SELECT
- x = CONVERT
- (
- xml,
- N'' +
- REPLACE(kss.subset_includes, N', ', N'') +
- N''
- )
- ) AS a
- /* Split XML into individual columns */
- CROSS APPLY a.x.nodes('/c') AS t(c)
- FOR
- XML
- PATH('')
- ),
- 1,
- 2,
- ''
- )
- )
- /* If only subset has includes, use those */
- WHEN kss.superset_includes IS NULL
- AND kss.subset_includes IS NOT NULL
- THEN kss.subset_includes
- /* If only superset has includes or neither has includes, keep superset's includes */
- ELSE kss.superset_includes
- END
- FROM #index_analysis AS ia
- JOIN KeySubsetSuperset AS kss
- ON ia.database_id = kss.database_id
- AND ia.object_id = kss.object_id
- AND ia.index_id = kss.index_id
- WHERE ia.action = N'MERGE INCLUDES'
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 6',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Update the superseded_by column for the wider index in a separate statement */
- UPDATE
- ia2
- SET
- 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
- AND ia1.object_id = ia2.object_id
- AND ia1.index_name <> ia2.index_name
- AND ia2.key_columns LIKE (ia1.key_columns + N'%') /* ia2 has wider key that starts with ia1's key */
- AND ISNULL(ia1.filter_definition, '') = ISNULL(ia2.filter_definition, '') /* Matching filters */
- /* Exception: If narrower index is unique and wider is not, they should not be merged */
- AND NOT (ia1.is_unique = 1 AND ia2.is_unique = 0)
- WHERE ia1.consolidation_rule = N'Key Subset' /* Use records just processed in previous UPDATE */
- AND ia1.target_index_name = ia2.index_name /* Make sure we're updating the right wider index */
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after update superseded',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Rule 7: Unique constraint vs. nonclustered index handling */
- UPDATE
- ia1
- SET
- ia1.consolidation_rule = N'Unique Constraint Replacement',
- ia1.action =
- CASE
- WHEN ia1.is_unique = 0
- THEN 'MAKE UNIQUE' /* Convert to unique index */
- ELSE 'KEEP' /* Already unique, so just keep it */
- 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 EXISTS
- (
- /* Find nonclustered indexes */
- SELECT
- 1/0
- FROM #index_details AS id1
- WHERE id1.database_id = ia1.database_id
- AND id1.object_id = ia1.object_id
- AND id1.index_id = ia1.index_id
- AND id1.is_eligible_for_dedupe = 1
- )
- AND EXISTS
- (
- /* Find unique constraints with matching key columns */
- SELECT
- 1/0
- FROM #index_details AS id2
- WHERE id2.database_id = ia1.database_id
- AND id2.object_id = ia1.object_id
- AND id2.is_unique_constraint = 1
- AND NOT EXISTS
- (
- /* Verify key columns match between index and unique constraint */
- SELECT
- id2_inner.column_name
- FROM #index_details AS id2_inner
- WHERE id2_inner.database_id = id2.database_id
- AND id2_inner.object_id = id2.object_id
- AND id2_inner.index_id = id2.index_id
- AND id2_inner.is_included_column = 0
-
- EXCEPT
-
- SELECT
- id1_inner.column_name
- FROM #index_details AS id1_inner
- WHERE id1_inner.database_id = ia1.database_id
- AND id1_inner.object_id = ia1.object_id
- AND id1_inner.index_id = ia1.index_id
- AND id1_inner.is_included_column = 0
- )
- )
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 7',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Rule 7.5: Mark unique constraints that have matching nonclustered indexes for disabling */
- /* First, mark unique constraints for disabling */
- UPDATE
- ia_uc
- SET
- ia_uc.consolidation_rule = N'Unique Constraint Replacement',
- ia_uc.action = N'DISABLE', /* Mark unique constraint for disabling */
- ia_uc.target_index_name = ia_nc.index_name /* Point to the nonclustered index that will replace it */
- FROM #index_analysis AS ia_uc /* Unique constraint */
- JOIN #index_details AS id_uc /* Join to get unique constraint details */
- ON id_uc.database_id = ia_uc.database_id
- AND id_uc.object_id = ia_uc.object_id
- AND id_uc.index_id = ia_uc.index_id
- AND id_uc.is_unique_constraint = 1 /* This is a unique constraint */
- JOIN #index_analysis AS ia_nc /* Join to find nonclustered index */
- ON ia_nc.database_id = ia_uc.database_id
- AND ia_nc.object_id = ia_uc.object_id
- AND ia_nc.index_name <> ia_uc.index_name /* Different index */
- WHERE
- /* Verify key columns EXACT match between index and unique constraint */
- ia_uc.key_columns = ia_nc.key_columns
- OPTION(RECOMPILE);
-
- /* Second, mark nonclustered indexes to be made unique */
- UPDATE
- ia_nc
- SET
- ia_nc.consolidation_rule = N'Unique Constraint Replacement',
- ia_nc.action = N'MAKE UNIQUE', /* Mark nonclustered index to be made unique */
- /* CRITICAL: Set target_index_name to NULL to ensure it gets a MERGE script */
- ia_nc.target_index_name = NULL
- FROM #index_analysis AS ia_nc /* Nonclustered index */
- JOIN #index_details AS id_nc /* Join to get nonclustered index details */
- ON id_nc.database_id = ia_nc.database_id
- AND id_nc.object_id = ia_nc.object_id
- AND id_nc.index_id = ia_nc.index_id
- AND id_nc.is_unique_constraint = 0 /* This is not a unique constraint */
- WHERE
- /* Two conditions for matching:
- 1. Index key columns exactly match a unique constraint's key columns
- 2. A unique constraint is already marked for DISABLE and has this index as target */
- EXISTS
- (
- /* Find unique constraint with matching keys that should be disabled */
- SELECT
- 1/0
- FROM #index_analysis AS ia_uc
- JOIN #index_details AS id_uc
- ON id_uc.database_id = ia_uc.database_id
- AND id_uc.object_id = ia_uc.object_id
- AND id_uc.index_id = ia_uc.index_id
- AND id_uc.is_unique_constraint = 1
- WHERE ia_uc.database_id = ia_nc.database_id
- AND ia_uc.object_id = ia_nc.object_id
- /* Check that both indexes have EXACTLY the same key columns */
- AND ia_uc.key_columns = ia_nc.key_columns
- )
- OPTION(RECOMPILE);
-
- /* CRITICAL: Ensure that only the unique constraints that exactly match get this treatment */
- /* And remove any incorrect MAKE UNIQUE actions */
- UPDATE
- ia
- SET
- action = NULL,
- consolidation_rule = NULL,
- target_index_name = NULL
- FROM #index_analysis AS ia
- WHERE ia.action = N'MAKE UNIQUE'
- AND NOT EXISTS (
- /* Check if there's a unique constraint with matching keys that points to this index */
- SELECT 1
- FROM #index_analysis AS ia_uc
- WHERE ia_uc.database_id = ia.database_id
- AND ia_uc.object_id = ia.object_id
- AND ia_uc.key_columns = ia.key_columns
- AND ia_uc.action = N'DISABLE'
- AND ia_uc.target_index_name = ia.index_name
- )
- OPTION(RECOMPILE);
-
- /* Make sure the nonclustered index has the superseded_by field set correctly */
- UPDATE
- ia_nc
- SET
- ia_nc.superseded_by =
- CASE
- WHEN ia_nc.superseded_by IS NULL
- THEN N'Will replace constraint ' +
- ia_uc.index_name
- ELSE ia_nc.superseded_by +
- N', will replace constraint ' + ia_uc.index_name
- END
- FROM #index_analysis AS ia_nc
- JOIN #index_analysis AS ia_uc
- ON ia_uc.database_id = ia_nc.database_id
- AND ia_uc.object_id = ia_nc.object_id
- AND ia_uc.action = N'DISABLE'
- AND ia_uc.target_index_name = ia_nc.index_name
- WHERE ia_nc.action = N'MAKE UNIQUE'
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 7.5',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Rule 8: Identify indexes with same keys but in different order after first column */
- /* This rule flags indexes that have the same set of key columns but ordered differently */
- /* These need manual review as they may be redundant depending on query patterns */
- UPDATE
- ia1
- SET
- ia1.consolidation_rule = N'Same Keys Different Order',
- ia1.action = N'REVIEW', /* These need manual review */
- ia1.target_index_name = ia2.index_name /* Reference the partner index */
- FROM #index_analysis AS ia1
- JOIN #index_analysis AS ia2
- ON ia1.database_id = ia2.database_id
- AND ia1.object_id = ia2.object_id
- AND ia1.index_name < ia2.index_name /* Only process each pair once */
- AND ia1.consolidation_rule IS NULL /* Not already processed */
- AND ia2.consolidation_rule IS NULL /* Not already processed */
- WHERE
- /* Leading columns match */
- EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id1
- JOIN #index_details AS id2
- ON id1.database_id = id2.database_id
- AND id1.object_id = id2.object_id
- AND id1.column_name = id2.column_name
- AND id1.key_ordinal = 1
- AND id2.key_ordinal = 1
- WHERE id1.database_id = ia1.database_id
- AND id1.object_id = ia1.object_id
- AND id1.index_id = ia1.index_id
- AND id2.index_id = ia2.index_id
- )
- /* Same set of key columns but in different order */
- AND NOT EXISTS
- (
- /* Make sure the sets of key columns are exactly the same */
- SELECT
- id1.column_name
- FROM #index_details AS id1
- WHERE id1.database_id = ia1.database_id
- AND id1.object_id = ia1.object_id
- AND id1.index_id = ia1.index_id
- AND id1.is_included_column = 0
- AND id1.key_ordinal > 0
-
- EXCEPT
-
- SELECT
- id2.column_name
- FROM #index_details AS id2
- WHERE id2.database_id = ia2.database_id
- AND id2.object_id = ia2.object_id
- AND id2.index_id = ia2.index_id
- AND id2.is_included_column = 0
- AND id2.key_ordinal > 0
- )
- /* But the order is different (excluding the first column) */
- AND EXISTS
- (
- /* There's at least one column in a different position */
- SELECT
- 1/0
- FROM #index_details AS id1
- JOIN #index_details AS id2
- ON id1.database_id = id2.database_id
- AND id1.object_id = id2.object_id
- AND id1.column_name = id2.column_name
- AND id1.key_ordinal <> id2.key_ordinal
- AND id1.key_ordinal > 1 /* After the first column */
- AND id2.key_ordinal > 1 /* After the first column */
- WHERE id1.database_id = ia1.database_id
- AND id1.object_id = ia1.object_id
- AND id1.index_id = ia1.index_id
- AND id2.index_id = ia2.index_id
- )
- OPTION(RECOMPILE);
-
- IF @debug = 1
- BEGIN
- SELECT
- table_name = '#index_analysis after rule 8',
- ia.*
- FROM #index_analysis AS ia
- OPTION(RECOMPILE);
- END;
-
- /* Create a reference to the detailed summary that will appear at the end */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_cleanup_results
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- consolidation_rule,
- script_type,
- additional_info,
- target_index_name,
- superseded_info,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT
- result_type = 'SUMMARY',
- sort_order = 1,
- database_name = '',
- schema_name = '',
- table_name = '',
- index_name = '',
- consolidation_rule = N'',
- script_type = 'Index Cleanup Scripts',
- additional_info = N'A detailed index analysis report appears after these scripts',
- target_index_name = '',
- superseded_info = '',
- original_index_definition = '',
- index_size_gb = 0,
- index_rows = 0,
- index_reads = 0,
- index_writes = 0
- OPTION(RECOMPILE);
-
-
- /* Identify key duplicates where both indexes have MERGE INCLUDES action */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #key_duplicate_dedupe insert', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #key_duplicate_dedupe
- WITH
- (TABLOCK)
- (
- database_id,
- object_id,
- database_name,
- schema_name,
- table_name,
- base_key_columns,
- filter_definition,
- winning_index_name,
- index_list
- )
- SELECT
- ia.database_id,
- ia.object_id,
- database_name = MAX(ia.database_name),
- schema_name = MAX(ia.schema_name),
- table_name = MAX(ia.table_name),
- base_key_columns = ia.key_columns,
- filter_definition = ISNULL(ia.filter_definition, N''),
- /* Choose the index with most included columns as the winner (or first alphabetically if tied) */
- winning_index_name =
- (
- SELECT TOP (1)
- candidate.index_name
- FROM #index_analysis AS candidate
- WHERE candidate.database_id = ia.database_id
- AND candidate.object_id = ia.object_id
- AND candidate.key_columns = ia.key_columns
- AND ISNULL(candidate.filter_definition, '') = ISNULL(ia.filter_definition, '')
- AND candidate.action = N'MERGE INCLUDES'
- AND candidate.consolidation_rule = N'Key Duplicate'
- ORDER BY
- /* Then prefer indexes with more included columns (by length as a proxy) */
- LEN(ISNULL(candidate.included_columns, '')) DESC,
- /* Then alphabetically for stability */
- candidate.index_name
- ),
- /* Build a list of other indexes in this group */
- index_list =
- STUFF
- (
- (
- SELECT
- N', ' +
- inner_ia.index_name
- FROM #index_analysis AS inner_ia
- WHERE inner_ia.database_id = ia.database_id
- AND inner_ia.object_id = ia.object_id
- AND inner_ia.key_columns = ia.key_columns
- AND ISNULL(inner_ia.filter_definition, '') = ISNULL(ia.filter_definition, '')
- AND inner_ia.action = N'MERGE INCLUDES'
- AND inner_ia.consolidation_rule = N'Key Duplicate'
- GROUP BY
- inner_ia.index_name
- ORDER BY
- inner_ia.index_name
- FOR
- XML
- PATH(''),
- TYPE
- ).value('.', 'nvarchar(max)'),
- 1,
- 2,
- ''
- )
- FROM #index_analysis AS ia
- WHERE ia.action = N'MERGE INCLUDES'
- AND ia.consolidation_rule = N'Key Duplicate'
- GROUP BY
- ia.database_id,
- ia.object_id,
- ia.key_columns,
- ia.filter_definition
- HAVING
- COUNT_BIG(*) > 1
- OPTION(RECOMPILE); /* Only groups with multiple MERGE INCLUDES */
-
- /* Update the index_analysis table to make only one index the winner in each group */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_analysis updates', 0, 0) WITH NOWAIT;
- END;
-
- UPDATE
- ia
- SET
- ia.action = N'DISABLE',
- ia.target_index_name = kdd.winning_index_name,
- ia.superseded_by = NULL
- FROM #index_analysis AS ia
- JOIN #key_duplicate_dedupe AS kdd
- ON ia.database_id = kdd.database_id
- AND ia.object_id = kdd.object_id
- AND ia.key_columns = kdd.base_key_columns
- AND ISNULL(ia.filter_definition, N'') = kdd.filter_definition
- WHERE ia.index_name <> kdd.winning_index_name
- AND ia.action = N'MERGE INCLUDES'
- AND ia.consolidation_rule = N'Key Duplicate'
- OPTION(RECOMPILE);
-
- /* Update the winning index's superseded_by to list all other indexes */
- UPDATE
- ia
- SET
- ia.superseded_by = N'Supersedes ' +
- REPLACE
- (
- kdd.index_list,
- ia.index_name + N', ',
- N''
- ) /* Remove self from list if present */
- FROM #index_analysis AS ia
- JOIN #key_duplicate_dedupe AS kdd
- ON ia.database_id = kdd.database_id
- AND ia.object_id = kdd.object_id
- AND ia.key_columns = kdd.base_key_columns
- AND ISNULL(ia.filter_definition, '') = kdd.filter_definition
- WHERE ia.index_name = kdd.winning_index_name
- OPTION(RECOMPILE);
-
- /* Find indexes with same key columns where one has includes that are a subset of another */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #include_subset_dedupe insert', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #include_subset_dedupe
- WITH
- (TABLOCK)
- (
- database_id,
- object_id,
- subset_index_name,
- superset_index_name,
- subset_included_columns,
- superset_included_columns
- )
- SELECT
- ia1.database_id,
- ia1.object_id,
- ia1.index_name AS subset_index_name,
- ia2.index_name AS superset_index_name,
- ia1.included_columns AS subset_included_columns,
- ia2.included_columns AS superset_included_columns
- FROM #index_analysis AS ia1
- JOIN #index_analysis AS ia2
- ON ia1.database_id = ia2.database_id
- AND ia1.object_id = ia2.object_id
- AND ia1.key_columns = ia2.key_columns
- AND ISNULL(ia1.filter_definition, N'') = ISNULL(ia2.filter_definition, N'')
- AND ia1.index_name <> ia2.index_name
- AND ia1.action = N'MERGE INCLUDES'
- AND ia2.action = N'MERGE INCLUDES'
- AND ia1.consolidation_rule = N'Key Duplicate'
- AND ia2.consolidation_rule = N'Key Duplicate'
- /* Find where subset's includes are contained within superset's includes */
- AND
- (
- ia1.included_columns IS NULL
- OR CHARINDEX(ia1.included_columns, ia2.included_columns) > 0
- )
- /* Don't match if lengths are the same (would be exact duplicates) */
- AND
- (
- ia1.included_columns IS NULL
- OR ia2.included_columns IS NULL
- OR LEN(ia1.included_columns) < LEN(ia2.included_columns)
- )
- OPTION(RECOMPILE);
-
- /* Update the subset indexes to be disabled, since supersets already contain their columns */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_analysis updates', 0, 0) WITH NOWAIT;
- END;
-
- UPDATE
- ia
- SET
- ia.action = N'DISABLE',
- ia.target_index_name = isd.superset_index_name,
- ia.superseded_by = NULL
- FROM #index_analysis AS ia
- JOIN #include_subset_dedupe AS isd
- ON ia.database_id = isd.database_id
- AND ia.object_id = isd.object_id
- AND ia.index_name = isd.subset_index_name
- OPTION(RECOMPILE);
-
- /* Update the superset indexes to indicate they supersede the subset indexes */
- UPDATE
- ia
- SET
- ia.superseded_by =
- CASE
- WHEN ia.superseded_by IS NULL
- THEN N'Supersedes ' +
- isd.subset_index_name
- ELSE ia.superseded_by +
- N', ' +
- isd.subset_index_name
- END
- FROM #index_analysis AS ia
- JOIN #include_subset_dedupe AS isd
- ON ia.database_id = isd.database_id
- AND ia.object_id = isd.object_id
- AND ia.index_name = isd.superset_index_name
- OPTION(RECOMPILE);
-
- /* Update winning indexes that don't actually need changes to have action = N'KEEP' */
- UPDATE
- ia
- SET
- /* Change action to 'KEEP' for indexes that don't need to be modified */
- ia.action = N'KEEP'
- FROM #index_analysis AS ia
- WHERE ia.action = N'MERGE INCLUDES'
- AND ia.superseded_by IS NOT NULL
- /* This should indicate it already has all the needed includes */
- AND NOT EXISTS
- (
- /* Find any indexes it supersedes that have includes not in this index */
- SELECT
- 1/0
- FROM #index_analysis AS ia_subset
- WHERE ia_subset.database_id = ia.database_id
- AND ia_subset.object_id = ia.object_id
- AND ia_subset.key_columns = ia.key_columns
- AND ia_subset.action = N'DISABLE'
- AND ia_subset.target_index_name = ia.index_name
- /* This complex check handles cases where the superset doesn't contain all subset columns */
- AND CHARINDEX(ISNULL(ia_subset.included_columns, N''), ISNULL(ia.included_columns, N'')) = 0
- AND ISNULL(ia_subset.included_columns, N'') <> N''
- )
- OPTION(RECOMPILE);
-
- /* Insert merge scripts for indexes */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, MERGE', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- consolidation_rule,
- target_index_name,
- script,
- additional_info,
- superseded_info,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'MERGE',
- /* Put merge target indexes higher in sort order (5) so they appear before
- indexes that will be disabled (20) */
- sort_order = 5,
- ia.database_name,
- ia.schema_name,
- ia.table_name,
- ia.index_name,
- script_type = N'MERGE SCRIPT',
- ia.consolidation_rule,
- ia.target_index_name,
- script =
- CASE
- WHEN ia.action = N'MAKE UNIQUE'
- THEN N'CREATE UNIQUE '
- WHEN ia.action = N'MERGE INCLUDES'
- THEN N'CREATE '
- ELSE N'CREATE '
- END +
- N'INDEX ' +
- QUOTENAME(ia.index_name) +
- N' ON ' +
- QUOTENAME(ia.database_name) +
- N'.' +
- QUOTENAME(ia.schema_name) +
- N'.' +
- QUOTENAME(ia.table_name) +
- N' (' +
- ia.key_columns +
- N')' +
- CASE
- WHEN ia.included_columns IS NOT NULL
- AND LEN(ia.included_columns) > 0
- AND ia.action = N'MERGE INCLUDES'
- THEN N' INCLUDE (' +
- ia.included_columns +
- N')'
- WHEN ia.included_columns IS NOT NULL
- AND LEN(ia.included_columns) > 0
- THEN N' INCLUDE (' +
- ia.included_columns +
- N')'
- ELSE N''
- END +
- CASE
- WHEN ia.filter_definition IS NOT NULL
- THEN N' WHERE ' +
- ia.filter_definition
- ELSE N''
- END +
- N' WITH (DROP_EXISTING = ON, FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ' +
- CASE
- WHEN @online = 1
- THEN N'ON'
- ELSE N'OFF'
- END +
- CASE
- WHEN ce.can_compress = 1
- THEN ', DATA_COMPRESSION = PAGE'
- ELSE N''
- END +
- N')' +
- CASE
- WHEN ps.partition_function_name IS NOT NULL
- THEN N' ON ' +
- QUOTENAME(ps.partition_function_name) +
- N'(' +
- ISNULL(ps.partition_columns, N'') +
- N')'
- WHEN ps.built_on IS NOT NULL
- THEN N' ON ' +
- QUOTENAME(ps.built_on)
- ELSE N''
- END + N';',
- /* Additional info about what this script does */
- additional_info =
- CASE
- WHEN ia.action = N'MERGE INCLUDES'
- THEN N'This index will absorb includes from duplicate indexes'
- WHEN ia.action = N'MAKE UNIQUE'
- THEN N'This index will replace a unique constraint'
- ELSE NULL
- END,
- /* Add superseded_by information if available */
- ia.superseded_by,
- /* Original index definition for validation */
- ia.original_index_definition,
- NULL,
- NULL,
- NULL,
- NULL
- FROM #index_analysis AS ia
- LEFT JOIN
- (
- /* Get the partition info for each index */
- SELECT
- ps.database_id,
- ps.object_id,
- ps.index_id,
- ps.built_on,
- ps.partition_function_name,
- ps.partition_columns
- FROM #partition_stats ps
- GROUP BY
- ps.database_id,
- ps.object_id,
- ps.index_id,
- ps.built_on,
- ps.partition_function_name,
- ps.partition_columns
- ) AS ps
- ON ia.database_id = ps.database_id
- AND ia.object_id = ps.object_id
- AND ia.index_id = ps.index_id
- JOIN #compression_eligibility AS ce
- ON ia.database_id = ce.database_id
- AND ia.object_id = ce.object_id
- AND ia.index_id = ce.index_id
- WHERE ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
- AND ce.can_compress = 1
- /* Only create merge scripts for the indexes that should remain after merging */
- AND ia.target_index_name IS NULL
- OPTION(RECOMPILE);
-
- /* Debug which indexes are getting MERGE scripts */
- IF @debug = 1
- BEGIN
- RAISERROR('Indexes getting MERGE scripts:', 0, 0) WITH NOWAIT;
- SELECT
- ia.index_name,
- ia.action,
- ia.consolidation_rule,
- ia.target_index_name,
- script_type = 'WILL GET MERGE SCRIPT',
- ia.included_columns
- FROM #index_analysis AS ia
- JOIN #compression_eligibility AS ce
- ON ia.database_id = ce.database_id
- AND ia.object_id = ce.object_id
- AND ia.index_id = ce.index_id
- WHERE ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
- AND ce.can_compress = 1
- AND ia.target_index_name IS NULL
- ORDER BY
- ia.index_name
- OPTION(RECOMPILE);
- END;
-
- /* Insert disable scripts for unneeded indexes */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, DISABLE', 0, 0) WITH NOWAIT;
-
- /* Debug for indexes that should get DISABLE scripts */
- RAISERROR('Indexes that should get DISABLE scripts:', 0, 0) WITH NOWAIT;
- SELECT
- ia.index_name,
- ia.consolidation_rule,
- ia.action,
- ia.target_index_name,
- ia.is_unique,
- ia.index_priority,
- is_unique_constraint =
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id
- WHERE id.database_id = ia.database_id
- AND id.object_id = ia.object_id
- AND id.index_id = ia.index_id
- AND id.is_unique_constraint = 1
- )
- THEN 'YES'
- ELSE 'NO'
- END,
- make_unique_target =
- CASE
- WHEN EXISTS
- (
- SELECT
- 1/0
- FROM #index_analysis AS ia_make
- WHERE ia_make.database_id = ia.database_id
- AND ia_make.object_id = ia.object_id
- AND ia_make.action = N'MAKE UNIQUE'
- AND ia_make.target_index_name = ia.index_name
- )
- THEN 'YES'
- ELSE 'NO'
- END,
- will_get_script =
- CASE
- WHEN ia.action = N'DISABLE'
- AND NOT EXISTS
- (
- SELECT 1
- FROM #index_details AS id_uc
- WHERE id_uc.database_id = ia.database_id
- AND id_uc.object_id = ia.object_id
- AND id_uc.index_id = ia.index_id
- AND id_uc.is_unique_constraint = 1
- )
- THEN 'YES'
- ELSE 'NO'
- END
- FROM #index_analysis AS ia
- WHERE ia.index_name LIKE 'ix_filtered_%'
- OR ia.index_name LIKE 'ix_desc_%'
- ORDER BY
- ia.index_name
- OPTION(RECOMPILE);
-
- /* Debug for all indexes marked with action = DISABLE */
- RAISERROR('All indexes with action = DISABLE:', 0, 0) WITH NOWAIT;
- SELECT
- ia.index_name,
- ia.consolidation_rule,
- ia.action,
- ia.target_index_name
- FROM #index_analysis AS ia
- WHERE ia.action = N'DISABLE'
- ORDER BY
- ia.index_name
- OPTION(RECOMPILE);
- END;
-
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- consolidation_rule,
- script,
- additional_info,
- target_index_name,
- superseded_info,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'DISABLE',
- /* Sort duplicate/subset indexes first (20), then unused indexes last (25) */
- sort_order =
- CASE
- WHEN ia.consolidation_rule LIKE 'Unused Index%' THEN 25
- ELSE 20
- END,
- ia.database_name,
- ia.schema_name,
- ia.table_name,
- ia.index_name,
- script_type = 'DISABLE SCRIPT',
- ia.consolidation_rule,
- script =
- /* Use regular DISABLE syntax for indexes */
- N'ALTER INDEX ' +
- QUOTENAME(ia.index_name) +
- N' ON ' +
- QUOTENAME(ia.database_name) +
- N'.' +
- QUOTENAME(ia.schema_name) +
- N'.' +
- QUOTENAME(ia.table_name) +
- N' DISABLE;',
- CASE
- WHEN ia.consolidation_rule = N'Key Subset'
- THEN N'This index is superseded by a wider index: ' +
- ISNULL(ia.target_index_name, N'(unknown)')
- WHEN ia.consolidation_rule = N'Exact Duplicate'
- THEN N'This index is an exact duplicate of: ' +
- ISNULL(ia.target_index_name, N'(unknown)')
- WHEN ia.consolidation_rule = N'Key Duplicate'
- THEN N'This index has the same keys as: ' +
- ISNULL(ia.target_index_name, N'(unknown)')
- WHEN ia.consolidation_rule LIKE 'Unused Index%'
- THEN ia.consolidation_rule
- WHEN ia.action = N'DISABLE'
- THEN N'This index is redundant and will be disabled'
- ELSE N'This index is redundant'
- END,
- ia.target_index_name, /* Include the target index name */
- superseded_info = NULL, /* Don't need superseded_by info for disabled indexes */
- /* Original index definition for validation */
- ia.original_index_definition,
- ps.total_space_gb,
- ps.total_rows,
- index_reads =
- (id.user_seeks + id.user_scans + id.user_lookups),
- id.user_updates
- FROM #index_analysis AS ia
- LEFT JOIN #partition_stats AS ps
- ON ia.database_id = ps.database_id
- AND ia.object_id = ps.object_id
- AND ia.index_id = ps.index_id
- LEFT JOIN #index_details AS id
- ON id.database_id = ia.database_id
- AND id.object_id = ia.object_id
- AND id.index_id = ia.index_id
- AND id.is_included_column = 0 /* Get only one row per index */
- AND id.key_ordinal > 0
- WHERE ia.action = N'DISABLE'
- /* Exclude unique constraints - they are handled by DISABLE CONSTRAINT scripts */
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM #index_details AS id_uc
- WHERE id_uc.database_id = ia.database_id
- AND id_uc.object_id = ia.object_id
- AND id_uc.index_id = ia.index_id
- AND id_uc.is_unique_constraint = 1
- )
- /* Also exclude any index that is also going to be made unique in rule 7.5 */
- AND NOT EXISTS
- (
- SELECT
- 1/0
- FROM #index_analysis AS ia_unique
- WHERE ia_unique.database_id = ia.database_id
- AND ia_unique.object_id = ia.object_id
- AND ia_unique.index_name = ia.index_name
- AND ia_unique.action = N'MAKE UNIQUE'
- )
- OPTION(RECOMPILE);
-
- /* Insert compression scripts for remaining indexes */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, COMPRESS', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- script,
- additional_info,
- target_index_name,
- superseded_info,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'COMPRESS',
- sort_order = 40,
- ia.database_name,
- ia.schema_name,
- ia.table_name,
- ia.index_name,
- script_type = 'COMPRESSION SCRIPT',
- script =
- N'ALTER INDEX ' +
- QUOTENAME(ia.index_name) +
- N' ON ' +
- QUOTENAME(ia.database_name) +
- N'.' +
- QUOTENAME(ia.schema_name) +
- N'.' +
- QUOTENAME(ia.table_name) +
- CASE
- WHEN ps.partition_function_name IS NOT NULL
- THEN N' REBUILD PARTITION = ALL'
- ELSE N' REBUILD'
- END +
- N' WITH (FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ' +
- CASE
- WHEN @online = 1
- THEN N'ON'
- ELSE N'OFF'
- END +
- CASE
- WHEN ce.can_compress = 1
- THEN ', DATA_COMPRESSION = PAGE'
- ELSE N''
- END +
- N')',
- additional_info = N'Compression type: All Partitions',
- superseded_info = NULL, /* No target index for compression scripts */
- ia.superseded_by, /* Include superseded_by info for compression scripts */
- /* Original index definition for validation */
- ia.original_index_definition,
- ps_full.total_space_gb,
- ps_full.total_rows,
- index_reads =
- (id.user_seeks + id.user_scans + id.user_lookups),
- id.user_updates
- FROM #index_analysis AS ia
- LEFT JOIN
- (
- /* Get the partition info for each index */
- SELECT
- ps.database_id,
- ps.object_id,
- ps.index_id,
- ps.built_on,
- ps.partition_function_name,
- ps.partition_columns
- FROM #partition_stats ps
- GROUP BY
- ps.database_id,
- ps.object_id,
- ps.index_id,
- ps.built_on,
- ps.partition_function_name,
- ps.partition_columns
- )
- AS ps
- ON ia.database_id = ps.database_id
- AND ia.object_id = ps.object_id
- AND ia.index_id = ps.index_id
- LEFT JOIN #partition_stats AS ps_full
- ON ia.database_id = ps_full.database_id
- AND ia.object_id = ps_full.object_id
- AND ia.index_id = ps_full.index_id
- LEFT JOIN #index_details AS id
- ON id.database_id = ia.database_id
- AND id.object_id = ia.object_id
- AND id.index_id = ia.index_id
- AND id.is_included_column = 0 /* Get only one row per index */
- AND id.key_ordinal > 0
- JOIN #compression_eligibility AS ce
- ON ia.database_id = ce.database_id
- AND ia.object_id = ce.object_id
- AND ia.index_id = ce.index_id
- WHERE
- /* Indexes that are not being disabled or merged */
- (ia.action IS NULL OR ia.action = N'KEEP')
- /* Only indexes eligible for compression */
- AND ce.can_compress = 1
- OPTION(RECOMPILE);
-
- /* Insert disable scripts for unique constraints */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, CONSTRAINT', 0, 0) WITH NOWAIT;
- END;
-
- /* Add code to insert KEPT indexes into the results - THESE WERE MISSING! */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, KEPT', 0, 0) WITH NOWAIT;
- END;
-
- /* Insert KEPT indexes into results */
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- consolidation_rule,
- additional_info,
- script,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'KEPT',
- sort_order = 95, /* Put kept indexes at the end */
- ia.database_name,
- ia.schema_name,
- ia.table_name,
- ia.index_name,
- script_type = NULL,
- ia.consolidation_rule,
- additional_info =
- CASE
- WHEN ia.consolidation_rule IS NOT NULL
- THEN 'This index is being kept'
- ELSE NULL
- END,
- script = NULL, /* No script for kept indexes */
- /* Original index definition for validation */
- ia.original_index_definition,
- ps.total_space_gb,
- ps.total_rows,
- index_reads =
- (id.user_seeks + id.user_scans + id.user_lookups),
- id.user_updates
- FROM #index_analysis AS ia
- LEFT JOIN #partition_stats AS ps
- ON ia.database_id = ps.database_id
- AND ia.object_id = ps.object_id
- AND ia.index_id = ps.index_id
- LEFT JOIN #index_details AS id
- ON id.database_id = ia.database_id
- AND id.object_id = ia.object_id
- AND id.index_id = ia.index_id
- AND id.is_included_column = 0 /* Get only one row per index */
- AND id.key_ordinal > 0
- /* Check that this index is not already in the results */
- WHERE NOT EXISTS
- (
- SELECT
- 1/0
- FROM #index_cleanup_results AS ir
- WHERE ir.database_name = ia.database_name
- AND ir.schema_name = ia.schema_name
- AND ir.table_name = ia.table_name
- AND ir.index_name = ia.index_name
- )
- /* And include only indexes that should be kept */
- AND
- (
- /* Include indexes marked KEEP */
- ia.action = N'KEEP'
- /* And all indexes we haven't determined an action for (not disable, merge, etc.) */
- OR
- (
- ia.action IS NULL
- AND ia.index_id > 0
- )
- )
- OPTION(RECOMPILE);
-
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- additional_info,
- script,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'CONSTRAINT',
- sort_order = 30,
- ia_uc.database_name,
- ia_uc.schema_name,
- ia_uc.table_name,
- ia_uc.index_name,
- script_type = 'DISABLE CONSTRAINT SCRIPT',
- additional_info =
- N'This constraint is being replaced by: ' +
- ISNULL(ia_uc.target_index_name, N'(unknown)'),
- script =
- N'ALTER TABLE ' +
- QUOTENAME(ia_uc.database_name) +
- N'.' +
- QUOTENAME(ia_uc.schema_name) +
- N'.' +
- QUOTENAME(ia_uc.table_name) +
- N' NOCHECK CONSTRAINT ' +
- QUOTENAME(ia_uc.index_name) +
- N';',
- /* Original index definition for validation */
- original_index_definition = ia_uc.original_index_definition,
- ps.total_space_gb,
- ps.total_rows,
- index_reads =
- (id2.user_seeks + id2.user_scans + id2.user_lookups),
- id2.user_updates
- FROM #index_analysis AS ia_uc
- JOIN #index_details AS id
- ON id.database_id = ia_uc.database_id
- AND id.object_id = ia_uc.object_id
- AND id.index_id = ia_uc.index_id
- AND id.is_unique_constraint = 1
- LEFT JOIN #index_details AS id2
- ON id2.database_id = ia_uc.database_id
- AND id2.object_id = ia_uc.object_id
- AND id2.index_id = ia_uc.index_id
- AND id2.is_included_column = 0 /* Get only one row per index */
- AND id2.key_ordinal > 0
- LEFT JOIN #partition_stats AS ps
- ON ia_uc.database_id = ps.database_id
- AND ia_uc.object_id = ps.object_id
- AND ia_uc.index_id = ps.index_id
- WHERE
- /* Only constraints that are marked for disabling */
- ia_uc.action = N'DISABLE'
- /* That have consolidation_rule of 'Unique Constraint Replacement' */
- AND ia_uc.consolidation_rule = N'Unique Constraint Replacement'
- OPTION(RECOMPILE);
-
- /* Insert per-partition compression scripts */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, COMPRESS_PARTITION', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- script,
- additional_info,
- target_index_name,
- superseded_info,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'COMPRESS_PARTITION',
- sort_order = 50,
- ia.database_name,
- ia.schema_name,
- ia.table_name,
- ia.index_name,
- script_type = 'PARTITION COMPRESSION SCRIPT',
- script =
- N'ALTER INDEX ' +
- QUOTENAME(ia.index_name) +
- N' ON ' +
- QUOTENAME(ia.database_name) +
- N'.' +
- QUOTENAME(ia.schema_name) +
- N'.' +
- QUOTENAME(ia.table_name) +
- N' REBUILD PARTITION = ' +
- CONVERT
- (
- nvarchar(20),
- ps.partition_number
- ) +
- N' WITH (FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ' +
- CASE
- WHEN @online = 1
- THEN N'ON'
- ELSE N'OFF'
- END +
- CASE
- WHEN ce.can_compress = 1
- THEN ', DATA_COMPRESSION = PAGE'
- ELSE N''
- END +
- N')',
- N'Compression type: Per Partition | Partition: ' +
- CONVERT
- (
- nvarchar(20),
- ps.partition_number
- ) +
- N' | Rows: ' +
- CONVERT
- (
- nvarchar(20),
- ps.total_rows
- ) +
- N' | Size: ' +
- CONVERT
- (
- nvarchar(20),
- CONVERT
- (
- decimal(10,4),
- ps.total_space_gb
- )
- ) +
- N' GB',
- target_index_name = NULL,
- superseded_info = NULL,
- ia.original_index_definition,
- ps.total_space_gb,
- ps.total_rows,
- index_reads =
- (id.user_seeks + id.user_scans + id.user_lookups),
- id.user_updates
- FROM #index_analysis AS ia
- JOIN #partition_stats AS ps
- ON ia.database_id = ps.database_id
- AND ia.object_id = ps.object_id
- AND ia.index_id = ps.index_id
- LEFT JOIN #index_details AS id
- ON id.database_id = ia.database_id
- AND id.object_id = ia.object_id
- AND id.index_id = ia.index_id
- AND id.is_included_column = 0 /* Get only one row per index */
- AND id.key_ordinal > 0
- JOIN #compression_eligibility AS ce
- ON ia.database_id = ce.database_id
- AND ia.object_id = ce.object_id
- AND ia.index_id = ce.index_id
- WHERE
- /* Only partitioned indexes */
- ps.partition_function_name IS NOT NULL
- /* Indexes that are not being disabled or merged */
- AND (ia.action IS NULL OR ia.action = N'KEEP')
- /* Only indexes eligible for compression */
- AND ce.can_compress = 1
- OPTION(RECOMPILE);
-
- /* Insert compression ineligible info */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, INELIGIBLE', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- additional_info,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'INELIGIBLE',
- sort_order = 90,
- ce.database_name,
- ce.schema_name,
- ce.table_name,
- ce.index_name,
- script_type = 'INELIGIBLE FOR COMPRESSION',
- ce.reason,
- /* Original index definition for validation */
- original_index_definition =
- (
- SELECT TOP (1)
- ia.original_index_definition
- FROM #index_analysis AS ia
- WHERE ia.database_id = ce.database_id
- AND ia.object_id = ce.object_id
- AND ia.index_id = ce.index_id
- ),
- ps.total_space_gb,
- ps.total_rows,
- index_reads =
- (id.user_seeks + id.user_scans + id.user_lookups),
- id.user_updates
- FROM #compression_eligibility AS ce
- LEFT JOIN #partition_stats AS ps
- ON ce.database_id = ps.database_id
- AND ce.object_id = ps.object_id
- AND ce.index_id = ps.index_id
- LEFT JOIN #index_details AS id
- ON id.database_id = ce.database_id
- AND id.object_id = ce.object_id
- AND id.index_id = ce.index_id
- AND id.is_included_column = 0 /* Get only one row per index */
- AND id.key_ordinal > 0
- WHERE ce.can_compress = 0
- OPTION(RECOMPILE);
-
-
- /* Insert indexes identified for manual review */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, REVIEW', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- consolidation_rule,
- target_index_name,
- additional_info,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'REVIEW',
- sort_order = 93, /* Just before KEPT indexes */
- ia.database_name,
- ia.schema_name,
- ia.table_name,
- ia.index_name,
- script_type = 'NEEDS REVIEW',
- ia.consolidation_rule,
- ia.target_index_name,
- additional_info =
- CASE
- WHEN ia.consolidation_rule = N'Same Keys Different Order'
- THEN N'This index has the same key columns as ' +
- ISNULL(ia.target_index_name, N'(unknown)') +
- N' but in a different order. May be redundant depending on query patterns.'
- ELSE N'This index needs manual review'
- END,
- /* Original index definition for validation */
- ia.original_index_definition,
- ps.total_space_gb,
- ps.total_rows,
- index_reads =
- (id.user_seeks + id.user_scans + id.user_lookups),
- id.user_updates
- FROM #index_analysis AS ia
- LEFT JOIN #partition_stats AS ps
- ON ia.database_id = ps.database_id
- AND ia.object_id = ps.object_id
- AND ia.index_id = ps.index_id
- LEFT JOIN #index_details AS id
- ON id.database_id = ia.database_id
- AND id.object_id = ia.object_id
- AND id.index_id = ia.index_id
- AND id.is_included_column = 0 /* Get only one row per index */
- AND id.key_ordinal > 0
- WHERE ia.action = N'REVIEW'
- OPTION(RECOMPILE);
-
-
- /* Insert indexes that are being kept (superset indexes and others) */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results insert, KEEP', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_cleanup_results
- WITH
- (TABLOCK)
- (
- result_type,
- sort_order,
- database_name,
- schema_name,
- table_name,
- index_name,
- script_type,
- consolidation_rule,
- superseded_info,
- additional_info,
- original_index_definition,
- index_size_gb,
- index_rows,
- index_reads,
- index_writes
- )
- SELECT DISTINCT
- result_type = 'KEEP',
- sort_order = 95, /* Just before END OF REPORT at 99 */
- ia.database_name,
- ia.schema_name,
- ia.table_name,
- ia.index_name,
- script_type = 'KEPT',
- ia.consolidation_rule,
- ia.superseded_by,
- additional_info =
- CASE
- WHEN ia.superseded_by IS NOT NULL
- THEN 'This index supersedes other indexes and already has all needed columns'
- WHEN ia.action = N'KEEP'
- THEN 'This index is being kept'
- ELSE NULL
- END,
- /* Original index definition for validation */
- ia.original_index_definition,
- ps.total_space_gb,
- ps.total_rows,
- index_reads =
- (id.user_seeks + id.user_scans + id.user_lookups),
- id.user_updates
- FROM #index_analysis AS ia
- LEFT JOIN #partition_stats AS ps
- ON ia.database_id = ps.database_id
- AND ia.object_id = ps.object_id
- AND ia.index_id = ps.index_id
- LEFT JOIN #index_details AS id
- ON id.database_id = ia.database_id
- AND id.object_id = ia.object_id
- AND id.index_id = ia.index_id
- AND id.is_included_column = 0 /* Get only one row per index */
- AND id.key_ordinal > 0
- WHERE ia.action = N'KEEP'
- OR
- (
- ia.action IS NULL
- AND ia.consolidation_rule IS NULL
- )
- OPTION(RECOMPILE);
-
- /* Insert database-level summaries */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_reporting_stats insert, DATABASE', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_reporting_stats
- WITH
- (TABLOCK)
- (
- summary_level,
- database_name,
- index_count,
- total_size_gb,
- total_rows,
- indexes_to_merge,
- unused_indexes,
- unused_size_gb,
- total_reads,
- total_writes,
- user_seeks,
- user_scans,
- user_lookups,
- user_updates,
- range_scan_count,
- singleton_lookup_count,
- row_lock_count,
- row_lock_wait_count,
- row_lock_wait_in_ms,
- page_lock_count,
- page_lock_wait_count,
- page_lock_wait_in_ms,
- page_latch_wait_count,
- page_latch_wait_in_ms,
- page_io_latch_wait_count,
- page_io_latch_wait_in_ms,
- forwarded_fetch_count,
- leaf_insert_count,
- leaf_update_count,
- leaf_delete_count
- )
- SELECT
- summary_level =
- 'DATABASE',
- ps.database_name,
- index_count =
- COUNT_BIG(DISTINCT CONCAT(ps.object_id, N'.', ps.index_id)),
- total_size_gb = SUM(ps.total_space_gb),
- /* Use a simple aggregation to avoid double-counting */
- /* Get actual row count by grabbing the real row count from clustered index/heap per table */
- total_rows = SUM(DISTINCT d.actual_rows),
- indexes_to_merge =
- (
- SELECT
- COUNT_BIG(*)
- FROM #index_analysis AS ia
- WHERE ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
- AND ia.database_id = ps.database_id
- ),
- /* Use count from analysis to keep consistent with SUMMARY level */
- unused_indexes =
- (
- SELECT
- COUNT_BIG(*)
- FROM #index_analysis AS ia
- WHERE ia.action = N'DISABLE'
- AND ia.database_id = ps.database_id
- ),
- unused_size_gb =
- SUM
- (
- CASE
- WHEN id.user_seeks + id.user_scans + id.user_lookups = 0
- THEN ps.total_space_gb
- ELSE 0
- END
- ),
- total_reads = SUM(id.user_seeks + id.user_scans + id.user_lookups),
- total_writes = SUM(id.user_updates),
- user_seeks = SUM(id.user_seeks),
- user_scans = SUM(id.user_scans),
- user_lookups = SUM(id.user_lookups),
- user_updates = SUM(id.user_updates),
- range_scan_count = SUM(os.range_scan_count),
- singleton_lookup_count = SUM(os.singleton_lookup_count),
- row_lock_count = SUM(os.row_lock_count),
- row_lock_wait_count = SUM(os.row_lock_wait_count),
- row_lock_wait_in_ms = SUM(os.row_lock_wait_in_ms),
- page_lock_count = SUM(os.page_lock_count),
- page_lock_wait_count = SUM(os.page_lock_wait_count),
- page_lock_wait_in_ms = SUM(os.page_lock_wait_in_ms),
- page_latch_wait_count = SUM(os.page_latch_wait_count),
- page_latch_wait_in_ms = SUM(os.page_latch_wait_in_ms),
- page_io_latch_wait_count = SUM(os.page_io_latch_wait_count),
- page_io_latch_wait_in_ms = SUM(os.page_io_latch_wait_in_ms),
- forwarded_fetch_count = SUM(os.forwarded_fetch_count),
- leaf_insert_count = SUM(os.leaf_insert_count),
- leaf_update_count = SUM(os.leaf_update_count),
- leaf_delete_count = SUM(os.leaf_delete_count)
- FROM #partition_stats AS ps
- LEFT JOIN #index_details AS id
- ON id.database_id = ps.database_id
- AND id.object_id = ps.object_id
- AND id.index_id = ps.index_id
- AND id.is_included_column = 0
- AND id.key_ordinal > 0
- LEFT JOIN #operational_stats AS os
- ON os.database_id = ps.database_id
- AND os.object_id = ps.object_id
- AND os.index_id = ps.index_id
- OUTER APPLY
- (
- /* Get actual row count per table using MAX from clustered index/heap */
- SELECT
- actual_rows =
- MAX
- (
- CASE
- WHEN ps2.index_id IN (0, 1)
- THEN ps2.total_rows
- ELSE 0
- END
- )
- FROM #partition_stats AS ps2
- WHERE ps2.database_id = ps.database_id
- AND ps2.object_id = ps.object_id
- AND ps2.index_id IN (0, 1)
- GROUP BY
- ps2.object_id
- ) AS d
- GROUP BY
- ps.database_name,
- ps.database_id
- OPTION(RECOMPILE);
-
- /* Insert table-level summaries */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_reporting_stats insert, TABLE', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_reporting_stats
- WITH
- (TABLOCK)
- (
- summary_level,
- database_name,
- schema_name,
- table_name,
- index_count,
- total_size_gb,
- total_rows,
- indexes_to_merge,
- unused_indexes,
- unused_size_gb,
- total_reads,
- total_writes,
- user_seeks,
- user_scans,
- user_lookups,
- user_updates,
- range_scan_count,
- singleton_lookup_count,
- row_lock_count,
- row_lock_wait_count,
- row_lock_wait_in_ms,
- page_lock_count,
- page_lock_wait_count,
- page_lock_wait_in_ms,
- page_latch_wait_count,
- page_latch_wait_in_ms,
- page_io_latch_wait_count,
- page_io_latch_wait_in_ms,
- forwarded_fetch_count,
- leaf_insert_count,
- leaf_update_count,
- leaf_delete_count
- )
- SELECT
- summary_level = 'TABLE',
- ps.database_name,
- ps.schema_name,
- ps.table_name,
- index_count = COUNT_BIG(DISTINCT ps.index_id),
- total_size_gb = SUM(ps.total_space_gb),
- /* Use MAX to get the row count from the clustered index or heap */
- total_rows =
- MAX
- (
- CASE
- WHEN ps.index_id IN (0, 1)
- THEN ps.total_rows
- ELSE 0
- END
- ),
- indexes_to_merge =
- (
- SELECT
- COUNT_BIG(*)
- FROM #index_analysis AS ia
- WHERE ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
- AND ia.database_id = ps.database_id
- AND ia.schema_id = ps.schema_id
- AND ia.object_id = ps.object_id
- ),
- /* Use count from analysis to keep consistent with SUMMARY level */
- unused_indexes =
- (
- SELECT
- COUNT_BIG(*)
- FROM #index_analysis AS ia
- WHERE ia.action = N'DISABLE'
- AND ia.database_id = ps.database_id
- AND ia.schema_id = ps.schema_id
- AND ia.object_id = ps.object_id
- ),
- unused_size_gb =
- SUM
- (
- CASE
- WHEN id.user_seeks + id.user_scans + id.user_lookups = 0
- THEN ps.total_space_gb
- ELSE 0
- END
- ),
- total_reads = SUM(id.user_seeks + id.user_scans + id.user_lookups),
- total_writes = SUM(id.user_updates),
- user_seeks = SUM(id.user_seeks),
- user_scans = SUM(id.user_scans),
- user_lookups = SUM(id.user_lookups),
- user_updates = SUM(id.user_updates),
- range_scan_count = SUM(os.range_scan_count),
- singleton_lookup_count = SUM(os.singleton_lookup_count),
- row_lock_count = SUM(os.row_lock_count),
- row_lock_wait_count = SUM(os.row_lock_wait_count),
- row_lock_wait_in_ms = SUM(os.row_lock_wait_in_ms),
- page_lock_count = SUM(os.page_lock_count),
- page_lock_wait_count = SUM(os.page_lock_wait_count),
- page_lock_wait_in_ms = SUM(os.page_lock_wait_in_ms),
- page_latch_wait_count = SUM(os.page_latch_wait_count),
- page_latch_wait_in_ms = SUM(os.page_latch_wait_in_ms),
- page_io_latch_wait_count = SUM(os.page_io_latch_wait_count),
- page_io_latch_wait_in_ms = SUM(os.page_io_latch_wait_in_ms),
- forwarded_fetch_count = SUM(os.forwarded_fetch_count),
- leaf_insert_count = SUM(os.leaf_insert_count),
- leaf_update_count = SUM(os.leaf_update_count),
- leaf_delete_count = SUM(os.leaf_delete_count)
- FROM #partition_stats AS ps
- LEFT JOIN #index_details AS id
- ON id.database_id = ps.database_id
- AND id.object_id = ps.object_id
- AND id.index_id = ps.index_id
- AND id.is_included_column = 0
- AND id.key_ordinal > 0
- LEFT JOIN #operational_stats AS os
- ON os.database_id = ps.database_id
- AND os.object_id = ps.object_id
- AND os.index_id = ps.index_id
- GROUP BY
- ps.database_name,
- ps.database_id,
- ps.schema_name,
- ps.schema_id,
- ps.table_name,
- ps.object_id
- OPTION(RECOMPILE);
-
- /* We're not doing index-level summaries - focusing on database and table level reports */
-
- /*
- Return the consolidated results in a single result set
- Results are ordered by:
- 1. Summary information (overall stats, savings estimates)
- 2. Merge scripts (includes merges and unique conversions) - sort_order 5
- 3. Disable scripts (for redundant indexes) - sort_order 20
- 4. Constraint scripts (for unique constraints to disable)
- 5. Compression scripts (for tables eligible for compression)
- 6. Partition-specific compression scripts
- 7. Ineligible objects (tables that can't be compressed)
- 8. Kept indexes - sort_order 95
-
- Note: Merge target scripts are sorted higher in the results (sort_order 5)
- so that new merged indexes are created before subset indexes are disabled.
-
- Within each category, indexes are sorted by size and impact for better prioritization.
- */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_cleanup_results, RESULTS', 0, 0) WITH NOWAIT;
- END;
-
- SELECT
- /* First, show the information needed to understand the script */
- script_type = CASE WHEN ir.result_type = 'KEPT' AND ir.script_type IS NULL THEN 'KEPT' ELSE ir.script_type END,
- ir.additional_info,
- /* Then show identifying information for the index */
- ir.database_name,
- ir.schema_name,
- ir.table_name,
- ir.index_name,
- /* Then show relationship information */
- ir.consolidation_rule,
- ir.target_index_name,
- /* Include superseded_by info for winning indexes */
- superseded_info =
- CASE
- WHEN ia.superseded_by IS NOT NULL
- THEN ia.superseded_by
- ELSE ir.superseded_info
- END,
- /* Add size and usage metrics */
- index_size_gb =
- CASE
- WHEN ir.result_type = 'SUMMARY'
- THEN '0.0000'
- ELSE FORMAT(ISNULL(ir.index_size_gb, 0), 'N4')
- END,
- index_rows =
- CASE
- WHEN ir.result_type = 'SUMMARY'
- THEN '0'
- ELSE FORMAT(ISNULL(ir.index_rows, 0), 'N0')
- END,
- index_reads =
- CASE
- WHEN ir.result_type = 'SUMMARY'
- THEN '0'
- ELSE FORMAT(ISNULL(ir.index_reads, 0), 'N0')
- END,
- index_writes =
- CASE
- WHEN ir.result_type = 'SUMMARY'
- THEN '0'
- ELSE FORMAT(ISNULL(ir.index_writes, 0), 'N0')
- END,
- ia.original_index_definition,
- /* Finally show the actual script */
- ir.script
- FROM
- (
- /* Use a subquery with ROW_NUMBER to ensure we only get one row per index */
- SELECT *,
- ROW_NUMBER() OVER(
- PARTITION BY database_name, schema_name, table_name, index_name
- ORDER BY result_type DESC /* Prefer non-NULL result types */
- ) AS rn
- FROM #index_cleanup_results
- ) AS ir
- LEFT JOIN #index_analysis AS ia
- ON ir.database_name = ia.database_name
- AND ir.schema_name = ia.schema_name
- AND ir.table_name = ia.table_name
- AND ir.index_name = ia.index_name
- WHERE ir.rn = 1 /* Take only the first row for each index */
- ORDER BY
- ir.sort_order,
- ir.database_name,
- /* Within each sort_order group, prioritize by size and usage */
- CASE
- /* For SUMMARY, keep the original order */
- WHEN ir.result_type = 'SUMMARY'
- THEN 0
- /* For script categories, order by size and impact */
- ELSE ISNULL(ir.index_size_gb, 0)
- END DESC,
- CASE
- /* For SUMMARY, keep the original order */
- WHEN ir.result_type = 'SUMMARY'
- THEN 0
- /* For script categories, consider rows as secondary sort */
- ELSE ISNULL(ir.index_rows, 0)
- END DESC,
- /* Then by database, schema, table, index name for consistent ordering */
- ir.schema_name,
- ir.table_name,
- ir.index_name
- OPTION(RECOMPILE);
-
- /* Insert overall summary information */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_reporting_stats insert, SUMMARY', 0, 0) WITH NOWAIT;
- END;
-
- INSERT INTO
- #index_reporting_stats
- WITH
- (TABLOCK)
- (
- summary_level,
- server_uptime_days,
- uptime_warning,
- tables_analyzed,
- index_count,
- indexes_to_disable,
- indexes_to_merge,
- avg_indexes_per_table,
- space_saved_gb,
- compression_min_savings_gb,
- compression_max_savings_gb,
- total_min_savings_gb,
- total_max_savings_gb,
- total_rows
- )
- SELECT
- summary_level = 'SUMMARY',
- server_uptime_days = @uptime_days,
- uptime_warning = @uptime_warning,
- tables_analyzed =
- COUNT_BIG(DISTINCT CONCAT(ia.database_id, N'.', ia.schema_id, N'.', ia.object_id)),
- index_count =
- COUNT_BIG(*),
- indexes_to_disable =
- SUM
- (
- CASE
- WHEN ia.action = N'DISABLE'
- THEN 1
- ELSE 0
- END
- ),
- indexes_to_merge =
- SUM
- (
- CASE
- WHEN ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
- THEN 1
- ELSE 0
- END
- ),
- avg_indexes_per_table =
- COUNT_BIG(*) * 1.0 /
- NULLIF
- (
- COUNT_BIG(DISTINCT CONCAT(ia.database_id, N'.', ia.schema_id, N'.', ia.object_id)),
- 0
- ),
- /* Space savings from cleanup */
- space_saved_gb =
- SUM
- (
- CASE
- WHEN ia.action IN (N'DISABLE', N'MERGE INCLUDES', N'MAKE UNIQUE')
- THEN ps.total_space_gb
- ELSE 0
- END
- ),
- /* Conservative compression savings estimate (20%) */
- compression_min_savings_gb =
- SUM
- (
- CASE
- WHEN (ia.action IS NULL OR ia.action = N'KEEP')
- AND ce.can_compress = 1
- THEN ps.total_space_gb * 0.20
- ELSE 0
- END
- ),
- /* Optimistic compression savings estimate (60%) */
- compression_max_savings_gb =
- SUM
- (
- CASE
- WHEN (ia.action IS NULL OR ia.action = N'KEEP')
- AND ce.can_compress = 1
- THEN ps.total_space_gb * 0.60
- ELSE 0
- END
- ),
- /* Total conservative savings */
- total_min_savings_gb =
- SUM
- (
- CASE
- WHEN ia.action IN (N'DISABLE', N'MERGE INCLUDES', N'MAKE UNIQUE')
- THEN ps.total_space_gb
- WHEN (ia.action IS NULL OR ia.action = N'KEEP')
- AND ce.can_compress = 1
- THEN ps.total_space_gb * 0.20
- ELSE 0
- END
- ),
- /* Total optimistic savings */
- total_max_savings_gb =
- SUM
- (
- CASE
- WHEN ia.action IN (N'DISABLE', N'MERGE INCLUDES', N'MAKE UNIQUE')
- THEN ps.total_space_gb
- WHEN (ia.action IS NULL OR ia.action = N'KEEP')
- AND ce.can_compress = 1
- THEN ps.total_space_gb * 0.60
- ELSE 0
- END
- ),
- /* Get total rows from database unique tables */
- total_rows =
- (
- SELECT
- SUM(t.row_count)
- FROM
- (
- SELECT
- ps_distinct.object_id,
- row_count =
- MAX
- (
- CASE
- WHEN ps_distinct.index_id IN (0, 1)
- THEN ps_distinct.total_rows
- ELSE 0
- END
- )
- FROM #partition_stats AS ps_distinct
- WHERE ps_distinct.index_id IN (0, 1)
- GROUP BY
- ps_distinct.object_id
- ) AS t
- )
- FROM #index_analysis AS ia
- LEFT JOIN #partition_stats AS ps
- ON ia.database_id = ps.database_id
- AND ia.object_id = ps.object_id
- AND ia.index_id = ps.index_id
- LEFT JOIN #compression_eligibility AS ce
- ON ia.database_id = ce.database_id
- AND ia.object_id = ce.object_id
- AND ia.index_id = ce.index_id
- OPTION(RECOMPILE);
-
- /* Return enhanced database impact summaries */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating enhanced summary reports', 0, 0) WITH NOWAIT;
- END;
-
- /*
- This section now REPLACES the existing summary view rather than supplementing it
- We'll modify the existing query below rather than creating new output panes
- */
-
- /* Return streamlined reporting statistics focused on key metrics */
- IF @debug = 1
- BEGIN
- RAISERROR('Generating #index_reporting_stats, REPORT', 0, 0) WITH NOWAIT;
- END;
-
- SELECT
- /* Basic identification with enhanced naming */
- level =
- CASE
- WHEN irs.summary_level = 'SUMMARY'
- THEN '=== OVERALL ANALYSIS ==='
- ELSE irs.summary_level
- END,
-
- /* Server info (for summary) or database name */
- database_info =
- CASE
- WHEN irs.summary_level = 'SUMMARY'
- AND irs.uptime_warning = 1
- THEN 'WARNING: Server uptime only ' +
- CONVERT(varchar(10), irs.server_uptime_days) +
- ' days - usage data may be incomplete!'
- WHEN irs.summary_level = 'SUMMARY'
- THEN 'Server uptime: ' +
- CONVERT(varchar(10), irs.server_uptime_days) +
- ' days'
- ELSE irs.database_name
- END,
-
- /* Schema and table names (except for summary) */
- schema_name = ISNULL(irs.schema_name, 'N/A'),
- table_name = ISNULL(irs.table_name, 'N/A'),
-
- /* ===== Section 1: Index Counts ===== */
- /* Tables analyzed (summary only) */
- tables_analyzed =
- CASE
- WHEN irs.summary_level = 'SUMMARY'
- THEN FORMAT(irs.tables_analyzed, 'N0')
- ELSE FORMAT(0, 'N0') /* Show 0 instead of NULL */
- END,
-
- /* Total indexes */
- total_indexes = FORMAT(ISNULL(irs.index_count, 0), 'N0'),
-
- /* Removable indexes - report consistent values across levels */
- removable_indexes =
- CASE
- WHEN irs.summary_level = 'SUMMARY'
- THEN FORMAT(ISNULL(irs.indexes_to_disable, 0), 'N0') /* Indexes that will be disabled based on analysis */
- ELSE FORMAT(ISNULL(irs.unused_indexes, 0), 'N0') /* Unused indexes at database/table level */
- END,
-
- /* Show mergeable indexes across all levels */
- mergeable_indexes = FORMAT(ISNULL(irs.indexes_to_merge, 0), 'N0'),
-
- /* Percent of indexes that can be removed */
- percent_removable =
- CASE
- WHEN irs.summary_level = 'SUMMARY' AND irs.index_count > 0
- THEN FORMAT(100.0 * ISNULL(irs.indexes_to_disable, 0) / NULLIF(irs.index_count, 0), 'N1') + '%'
- WHEN irs.index_count > 0
- THEN FORMAT(100.0 * ISNULL(irs.unused_indexes, 0) / NULLIF(irs.index_count, 0), 'N1') + '%'
- ELSE '0.0%'
- END,
-
- /* ===== Section 2: Size and Space Savings with Before/After comparison ===== */
- /* Current size in GB */
- current_size_gb = FORMAT(ISNULL(irs.total_size_gb, 0), 'N2'),
-
- /* Size after cleanup - added this as new metric */
- size_after_cleanup_gb = FORMAT(ISNULL(irs.total_size_gb, 0) - ISNULL(irs.space_saved_gb, 0), 'N2'),
-
- /* Size that can be saved through cleanup */
- space_saved_gb =
- CASE
- WHEN irs.summary_level = 'SUMMARY'
- THEN FORMAT(ISNULL(irs.space_saved_gb, 0), 'N2')
- ELSE FORMAT(ISNULL(irs.unused_size_gb, 0), 'N2')
- END,
-
- /* Space reduction percentage - added this as new metric */
- space_reduction_percent =
- CASE
- WHEN ISNULL(irs.total_size_gb, 0) > 0
- THEN FORMAT((ISNULL(irs.space_saved_gb, 0) / NULLIF(irs.total_size_gb, 0)) * 100, 'N1') + '%'
- ELSE '0.0%'
- END,
-
- /* ===== Section 3: Table and Usage Statistics ===== */
- /* Row count */
- total_rows = FORMAT(ISNULL(irs.total_rows, 0), 'N0'),
-
- /* Total reads - combined total and breakdown */
- reads_breakdown =
- CASE
- WHEN irs.summary_level <> 'SUMMARY'
- THEN FORMAT(ISNULL(irs.total_reads, 0), 'N0') +
- ' (' +
- FORMAT(ISNULL(irs.user_seeks, 0), 'N0') +
- ' seeks, ' +
- FORMAT(ISNULL(irs.user_scans, 0), 'N0') +
- ' scans, ' +
- FORMAT(ISNULL(irs.user_lookups, 0), 'N0') +
- ' lookups)'
- ELSE 'N/A'
- END,
-
- /* Total writes */
- writes =
- CASE
- WHEN irs.summary_level <> 'SUMMARY'
- THEN FORMAT(ISNULL(irs.total_writes, 0), 'N0')
- ELSE 'N/A'
- END,
-
- /* Daily write operations saved - added as new metric */
- daily_write_ops_saved =
- CASE
- WHEN irs.summary_level <> 'SUMMARY'
- THEN FORMAT(ISNULL(irs.user_updates / NULLIF(CONVERT(DECIMAL(10,2),
- (SELECT TOP (1) irs2.server_uptime_days FROM #index_reporting_stats AS irs2 WHERE irs2.summary_level = 'DATABASE')), 0) *
- (ISNULL(irs.unused_indexes, 0) / NULLIF(CONVERT(DECIMAL(10,2), irs.index_count), 0)), 0), 'N0')
- ELSE 'N/A'
- END,
-
- /* ===== Section 4: Consolidated Performance Metrics ===== */
- /* Total count of lock waits (row + page) */
- lock_wait_count =
- CASE
- WHEN irs.summary_level <> 'SUMMARY'
- THEN FORMAT(ISNULL(irs.row_lock_wait_count, 0) +
- ISNULL(irs.page_lock_wait_count, 0), 'N0')
- ELSE '0'
- END,
-
- /* Average lock wait time in ms */
- avg_lock_wait_ms =
- CASE
- WHEN irs.summary_level <> 'SUMMARY'
- AND (ISNULL(irs.row_lock_wait_count, 0) +
- ISNULL(irs.page_lock_wait_count, 0)) > 0
- THEN FORMAT(1.0 * (ISNULL(irs.row_lock_wait_in_ms, 0) +
- ISNULL(irs.page_lock_wait_in_ms, 0)) /
- NULLIF(ISNULL(irs.row_lock_wait_count, 0) +
- ISNULL(irs.page_lock_wait_count, 0), 0), 'N2')
- ELSE '0.00'
- END,
-
- /* Combined latch wait time in ms */
- avg_latch_wait_ms =
- CASE
- WHEN irs.summary_level <> 'SUMMARY'
- AND (ISNULL(irs.page_latch_wait_count, 0) +
- ISNULL(irs.page_io_latch_wait_count, 0)) > 0
- THEN FORMAT(1.0 * (ISNULL(irs.page_latch_wait_in_ms, 0) +
- ISNULL(irs.page_io_latch_wait_in_ms, 0)) /
- NULLIF(ISNULL(irs.page_latch_wait_count, 0) +
- ISNULL(irs.page_io_latch_wait_count, 0), 0), 'N2')
- ELSE '0.00'
- END
- FROM #index_reporting_stats AS irs
- WHERE irs.summary_level IN ('SUMMARY', 'DATABASE', 'TABLE') /* Filter out INDEX level */
- ORDER BY
- /* Order by level - summary first */
- CASE
- WHEN irs.summary_level = 'SUMMARY' THEN 0
- WHEN irs.summary_level = 'DATABASE' THEN 1
- WHEN irs.summary_level = 'TABLE' THEN 2
- ELSE 3
- END,
- /* Then by database name */
- irs.database_name,
- /* For tables, sort by potential savings and size */
- CASE
- WHEN irs.summary_level = 'TABLE' THEN irs.unused_size_gb
- ELSE 0
- END DESC,
- CASE
- WHEN irs.summary_level = 'TABLE' THEN irs.total_size_gb
- ELSE 0
- END DESC,
- /* Then by schema, table */
- irs.schema_name,
- irs.table_name
- OPTION(RECOMPILE);
-
-END TRY
-BEGIN CATCH
- THROW;
-END CATCH;
-END; /*Final End*/
-GO