@@ -21,27 +21,27 @@ sp_estindex - Estimate a new index's size and statistics.
2121
2222Part of the DBA MultiTool http://dba-multitool.org
2323
24- Version: 2020121
24+ Version: 20210405
2525
2626MIT License
2727
2828Copyright (c) 2020 John McCall
2929
30- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
31- documentation files (the "Software"), to deal in the Software without restriction, including without limitation
32- the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
30+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
31+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation
32+ the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
3333and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3434
35- The above copyright notice and this permission notice shall be included in all copies or substantial
35+ The above copyright notice and this permission notice shall be included in all copies or substantial
3636portions of the Software.
3737
38- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
39- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
40- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
41- CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
38+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
39+ TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
40+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
41+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
4242DEALINGS IN THE SOFTWARE.
4343
44- -- TODO:
44+ -- TODO:
4545 -- Handle clustered indexes - https://docs.microsoft.com/en-us/sql/relational-databases/databases/estimate-the-size-of-a-clustered-index?view=sql-server-ver15
4646
4747=========
@@ -72,7 +72,7 @@ DECLARE @Sql NVARCHAR(MAX) = N''
7272 ,@PageSize BIGINT = 8192
7373 ,@FreeBytesPerPage BIGINT = 8096;
7474
75- BEGIN TRY
75+ BEGIN TRY
7676 -- Find Version
7777 IF (@SqlMajorVersion = 0)
7878 BEGIN;
@@ -93,6 +93,13 @@ BEGIN TRY
9393 THROW 51000, @Msg, 1;
9494 END;
9595
96+ /* Validate Filter */
97+ IF (@Filter <> '' AND LEFT(@Filter, 5) <> 'WHERE')
98+ BEGIN;
99+ SET @Msg = 'Filter must start with ''WHERE''.';
100+ THROW 51000, @Msg, 1;
101+ END;
102+
96103 /* Validate Database */
97104 IF (@DatabaseName IS NULL)
98105 BEGIN;
@@ -149,8 +156,8 @@ BEGIN TRY
149156 SET @Sql = CONCAT(@UseDatabase,
150157 N'SELECT @IsHeap = CASE [type] WHEN 0 THEN 1 ELSE 0 END
151158 ,@IsClusterUnique = [is_unique]
152- FROM [sys].[indexes]
153- WHERE [object_id] = OBJECT_ID(@QualifiedTable)
159+ FROM [sys].[indexes]
160+ WHERE [object_id] = OBJECT_ID(@QualifiedTable)
154161 AND [type] IN (1, 0)');
155162 SET @ParmDefinition = N'@QualifiedTable NVARCHAR(257), @IsHeap BIT OUTPUT, @IsClusterUnique BIT OUTPUT';
156163 EXEC sp_executesql @Sql
@@ -160,25 +167,25 @@ BEGIN TRY
160167 ,@IsClusterUnique OUTPUT;
161168
162169 -- Safety check for leftover index from previous run
163- SET @DropIndexSql = CONCAT(@UseDatabase,
170+ SET @DropIndexSql = CONCAT(@UseDatabase,
164171 N'IF EXISTS (SELECT 1 FROM [sys].[indexes] WHERE [object_id] = OBJECT_ID(''',@QualifiedTable,''') AND [name] = ''',@IndexName,''')
165- DROP INDEX ', QUOTENAME(@IndexName), ' ON ', @QualifiedTable);
172+ DROP INDEX ', QUOTENAME(@IndexName), ' ON ', @QualifiedTable);
166173 EXEC sp_executesql @DropIndexSql;
167174
168175 -- Fetch missing index stats before creation
169- IF OBJECT_ID('tempdb..##TempMissingIndex') IS NOT NULL
176+ IF OBJECT_ID('tempdb..##TempMissingIndex') IS NOT NULL
170177 BEGIN;
171178 DROP TABLE ##TempMissingIndex;
172179 END;
173-
180+
174181 SET @Sql = CONCAT(@UseDatabase,
175- N'SELECT [id].[statement]
176- ,[id].[equality_columns]
177- ,[id].[inequality_columns]
178- ,[id].[included_columns]
179- ,[gs].[unique_compiles]
182+ N'SELECT [id].[statement]
183+ ,[id].[equality_columns]
184+ ,[id].[inequality_columns]
185+ ,[id].[included_columns]
186+ ,[gs].[unique_compiles]
180187 ,[gs].[user_seeks]
181- ,[gs].[user_scans]
188+ ,[gs].[user_scans]
182189 ,[gs].[avg_total_user_cost] -- Average cost of the user queries that could be reduced
183190 ,[gs].[avg_user_impact] -- %
184191 INTO ##TempMissingIndex
@@ -196,11 +203,11 @@ BEGIN TRY
196203 -- Create the hypothetical index
197204 SET @Sql = CONCAT(@UseDatabase, 'CREATE ', @UniqueSql, @IndexType, ' INDEX ', QUOTENAME(@IndexName), ' ON ', @QualifiedTable, ' (', @IndexColumns, ') ',@IncludeSql, @Filter, ' WITH (STATISTICS_ONLY = -1)');
198205 EXEC sp_executesql @Sql;
199-
206+
200207 /*******************/
201208 /* Get index stats */
202209 /*******************/
203- -- Use DBCC to avoid various inconsistencies
210+ -- Use DBCC to avoid various inconsistencies
204211 -- in equivalent DMVs between 2012-2016
205212 SET @Sql = CONCAT(@UseDatabase, 'DBCC SHOW_STATISTICS ("', @QualifiedTable,'", ', QUOTENAME(@IndexName), ')');
206213 EXEC sp_executesql @Sql;
@@ -213,21 +220,21 @@ BEGIN TRY
213220
214221 --Get index columns in same format as dmv table
215222 SET @Sql = CONCAT(@UseDatabase,
216- N'SELECT @QuotedKeyColumns = CASE WHEN [ic].[is_included_column] = 0
223+ N'SELECT @QuotedKeyColumns = CASE WHEN [ic].[is_included_column] = 0
217224 THEN CONCAT(COALESCE(@QuotedKeyColumns COLLATE DATABASE_DEFAULT + '', '', ''''), QUOTENAME([ac].[name]))
218225 ELSE @QuotedKeyColumns
219226 END,
220227 @QuotedInclColumns = CASE WHEN [ic].[is_included_column] = 1
221228 THEN CONCAT(COALESCE(@QuotedInclColumns COLLATE DATABASE_DEFAULT + '', '', ''''), QUOTENAME([ac].[name]))
222229 ELSE @QuotedInclColumns
223- END
224- FROM [sys].[indexes] AS [i]
230+ END
231+ FROM [sys].[indexes] AS [i]
225232 INNER JOIN [sys].[index_columns] AS [ic] ON [i].[index_id] = [ic].[index_id]
226233 AND [ic].object_id = [i].object_id
227234 INNER JOIN [sys].[all_columns] AS [ac] ON [ac].[object_id] = [ic].[object_id]
228235 AND [ac].[column_id] = [ic].[column_id]
229236 WHERE [i].[name] = @IndexName
230- AND [i].[object_id] = @ObjectID
237+ AND [i].[object_id] = @ObjectID
231238 AND [i].[is_hypothetical] = 1;');
232239 SET @ParmDefinition = N'@IndexName SYSNAME, @ObjectID INT, @QuotedKeyColumns NVARCHAR(2048) OUTPUT, @QuotedInclColumns NVARCHAR(2048) OUTPUT';
233240 EXEC sp_executesql @Sql
@@ -239,15 +246,15 @@ BEGIN TRY
239246
240247 -- Search missing index dmv for a match
241248 SELECT 'Missing index stats' AS [description]
242- ,[statement]
243- ,[equality_columns]
244- ,[inequality_columns]
245- ,[included_columns]
246- ,[unique_compiles]
249+ ,[statement]
250+ ,[equality_columns]
251+ ,[inequality_columns]
252+ ,[included_columns]
253+ ,[unique_compiles]
247254 ,[user_seeks]
248- ,[user_scans]
255+ ,[user_scans]
249256 ,[avg_total_user_cost]
250- ,[avg_user_impact]
257+ ,[avg_user_impact]
251258 FROM ##TempMissingIndex
252259 WHERE COALESCE([equality_columns] + ', ', '') + [inequality_columns] = @QuotedKeyColumns
253260 AND ([included_columns] = @QuotedInclColumns OR [included_columns] IS NULL);
@@ -264,7 +271,7 @@ BEGIN TRY
264271 /* Estimate index size - does NOT consider: */
265272 /* Partitioning, allocation pages, LOB values, */
266273 /* compression, or sparse columns */
267- /************************************************/
274+ /************************************************/
268275 IF (@IndexType = 'NONCLUSTERED') -- http://dba-multitool.org/est-nonclustered-index-size
269276 BEGIN;
270277 DECLARE @NumVariableKeyCols INT = 0
@@ -290,9 +297,9 @@ BEGIN TRY
290297 -- Row count
291298 SET @Sql = CONCAT(@UseDatabase,
292299 N'SELECT @NumRows = [sp].[rows] -- Accounts for index filter if in use
293- FROM [sys].[objects] AS [o]
294- INNER JOIN [sys].[stats] AS [stat] ON [stat].[object_id] = [o].[object_id]
295- CROSS APPLY [sys].[dm_db_stats_properties]([stat].[object_id], [stat].[stats_id]) AS [sp]
300+ FROM [sys].[objects] AS [o]
301+ INNER JOIN [sys].[stats] AS [stat] ON [stat].[object_id] = [o].[object_id]
302+ CROSS APPLY [sys].[dm_db_stats_properties]([stat].[object_id], [stat].[stats_id]) AS [sp]
296303 WHERE [o].[object_id] = @ObjectID
297304 AND [stat].[name] = @IndexName;');
298305 SET @ParmDefinition = N'@ObjectID INT, @IndexName SYSNAME, @NumRows BIGINT OUTPUT';
@@ -323,12 +330,12 @@ BEGIN TRY
323330 ELSE COL_LENGTH(OBJECT_NAME([i].object_id), [ac].[name])
324331 END
325332 ELSE 0
326- END), 0),
333+ END), 0),
327334 @NumFixedKeyCols = ISNULL(SUM(CASE
328335 WHEN TYPE_NAME([ac].[user_type_id]) NOT IN(''varchar'', ''nvarchar'', ''text'', ''ntext'', ''image'', ''varbinary'', ''xml'')
329336 THEN 1
330337 ELSE 0
331- END), 0),
338+ END), 0),
332339 @FixedKeySize = ISNULL(SUM(CASE
333340 WHEN TYPE_NAME([ac].[user_type_id]) NOT IN(''varchar'', ''nvarchar'', ''text'', ''ntext'', ''image'', ''varbinary'', ''xml'')
334341 THEN COL_LENGTH(OBJECT_NAME([i].object_id), [ac].[name])
@@ -411,12 +418,12 @@ BEGIN TRY
411418 ELSE COL_LENGTH(OBJECT_NAME([i].object_id), [ac].[name])
412419 END
413420 ELSE 0
414- END), 0),
421+ END), 0),
415422 @ClusterNumFixedKeyCols = ISNULL(SUM(CASE
416423 WHEN TYPE_NAME([ac].[user_type_id]) NOT IN(''varchar'', ''nvarchar'', ''text'', ''ntext'', ''image'', ''varbinary'', ''xml'')
417424 THEN 1
418425 ELSE 0
419- END), 0),
426+ END), 0),
420427 @MaxClusterFixedKeySize = ISNULL(SUM(CASE
421428 WHEN TYPE_NAME([ac].[user_type_id]) NOT IN(''varchar'', ''nvarchar'', ''text'', ''ntext'', ''image'', ''varbinary'', ''xml'')
422429 THEN COL_LENGTH(OBJECT_NAME([i].object_id), [ac].[name])
@@ -458,7 +465,7 @@ BEGIN TRY
458465 RAISERROR(@Msg, 10, 1) WITH NOWAIT;
459466 END;
460467
461- -- Add counts from clustered index cols
468+ -- Add counts from clustered index cols
462469 SET @NumKeyCols = @NumKeyCols + (@ClusterNumVarKeyCols + @ClusterNumFixedKeyCols);
463470 SET @FixedKeySize = @FixedKeySize + @MaxClusterFixedKeySize;
464471 SET @NumVariableKeyCols = @NumVariableKeyCols + @ClusterNumVarKeyCols;
@@ -492,7 +499,7 @@ BEGIN TRY
492499 BEGIN;
493500 SET @IndexNullBitmap = 2 + ((@NullCols + 7) / 8);
494501 END;
495-
502+
496503 -- Calculate variable length data size
497504 -- Assumes each col is 100% full
498505 IF (@NumVariableKeyCols > 0)
@@ -557,12 +564,12 @@ BEGIN TRY
557564 ELSE COL_LENGTH(OBJECT_NAME([i].object_id), [ac].[name])
558565 END
559566 ELSE 0
560- END), 0),
567+ END), 0),
561568 @NumFixedInclCols = ISNULL(SUM(CASE
562569 WHEN TYPE_NAME([ac].[user_type_id]) NOT IN(''varchar'', ''nvarchar'', ''text'', ''ntext'', ''image'', ''varbinary'', ''xml'')
563570 THEN 1
564571 ELSE 0
565- END), 0),
572+ END), 0),
566573 @FixedInclSize = ISNULL(SUM(CASE
567574 WHEN TYPE_NAME([ac].[user_type_id]) NOT IN(''varchar'', ''nvarchar'', ''text'', ''ntext'', ''image'', ''varbinary'', ''xml'')
568575 THEN COL_LENGTH(OBJECT_NAME([i].object_id), [ac].[name])
@@ -594,7 +601,7 @@ BEGIN TRY
594601 SET @NumVariableLeafCols = @NumVariableLeafCols + @NumVariableInclCols;
595602 SET @MaxVarLeafSize = @MaxVarLeafSize + @MaxVarInclSize;
596603 END;
597-
604+
598605 -- Account for data row locator for unique indexes
599606 -- If non-unique, already accounted for above
600607 IF (@IsUnique = 1)
@@ -619,7 +626,7 @@ BEGIN TRY
619626 SET @MaxVarLeafSize = @MaxVarLeafSize + 4;
620627 END;
621628 END;
622- END;
629+ END;
623630
624631 IF (@Verbose = 1)
625632 BEGIN
@@ -632,7 +639,7 @@ BEGIN TRY
632639 SET @Msg = CONCAT('MaxVarLeafSize: ', @MaxVarLeafSize);
633640 RAISERROR(@Msg, 10, 1) WITH NOWAIT;
634641 END;
635-
642+
636643 -- Account for index null bitmap
637644 SET @LeafNullBitmap = 2 + ((@NumLeafCols + 7) / 8);
638645
@@ -703,7 +710,7 @@ BEGIN TRY
703710
704711 -- Calculate the number of non-leaf levels in the index
705712 SET @NonLeafLevels = CEILING(1 + LOG(@IndexRowsPerPage) * (@NumLeafPages / @IndexRowsPerPage));
706-
713+
707714 IF (@Verbose = 1)
708715 BEGIN
709716 SET @Msg = CONCAT('NonLeafLevels: ', @NonLeafLevels);
@@ -725,7 +732,7 @@ BEGIN TRY
725732 SET @NonLeafLevels = @NonLeafLevels - 1;
726733 END CATCH;
727734 END;
728-
735+
729736 -- Calculate size of the index
730737 SET @IndexSpaceUsed = @PageSize * @NumIndexPages;
731738
0 commit comments