Skip to content

Commit 1b6030c

Browse files
authored
Merge pull request #196 from LowlyDBA/development
Development
2 parents 95d6803 + f188cb3 commit 1b6030c

File tree

9 files changed

+2132
-952
lines changed

9 files changed

+2132
-952
lines changed

appveyor/sqlcover/Coverage.opencoverxml

Lines changed: 344 additions & 341 deletions
Large diffs are not rendered by default.

appveyor/sqlcover/[dbo].[sp_doc]

Lines changed: 345 additions & 139 deletions
Large diffs are not rendered by default.

appveyor/sqlcover/[dbo].[sp_estindex]

Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,27 @@ sp_estindex - Estimate a new index's size and statistics.
2121

2222
Part of the DBA MultiTool http://dba-multitool.org
2323

24-
Version: 2020121
24+
Version: 20210405
2525

2626
MIT License
2727

2828
Copyright (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,
3333
and 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
3636
portions 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
4242
DEALINGS 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

Comments
 (0)