As someone who deals with SQL Server replication “on the regs” I’m no stranger to replication snapshots. Dealing with snapshots (wanted or otherwise) means I’m probably going to need to re-apply indexes once it’s finished. I can’t actually do that unless I know what indexes exist for a given set of tables (or even views) unless I script them out ahead of time.
The thing is: just how many indexes do I actually have? Even if you’re not having to deal with stripping them off and/or putting them back, even just knowing what your index situation is can be mighty helpful. So strap in, enjoy the “Aliens” quotes, and read about my favorite script in today’s contribution to #tsql2sday which was hosted by Bert Wagner.
The premise is simple: it will generate a series of DROP and then CREATE INDEX commands for every index. The process is a little more complex in practice, but at a high level it:
Nifty.
I can’t take all the credit (or even much credit) for this script: I found it ages ago over on SQL Server Central’s Script’s repository, and it was an already modified version of a script that was written and posted on the SQL Solace blog way back in 2010. I’ve made a few small tweaks to it (mostly formatting related) but otherwise it’s mostly unchanged. I love it though, and it’s served me well through the years. Here’s my version of it:
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'index_maint' AND SCHEMA_OWNER = 'dbo') BEGIN EXEC('CREATE SCHEMA index_maint') END GO IF NOT EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'GenerateIndexesScript' AND ROUTINE_TYPE = N'PROCEDURE') BEGIN EXEC ('CREATE PROCEDURE [index_maint].[GenerateIndexesScript] AS BEGIN SELECT 1 END') END GO ALTER PROCEDURE index_maint.GenerateIndexesScript ( @IncludeFileGroup bit = 1, @IncludeDrop bit = 1, @IncludeFillFactor bit = 1 ) AS BEGIN DECLARE Indexes_cursor CURSOR FOR SELECT SC.Name AS SchemaName, SO.Name AS TableName, SI.OBJECT_ID AS TableId, SI.[Name] AS IndexName, SI.Index_ID AS IndexId, FG.[Name] AS FileGroupName, CASE WHEN SI.Fill_Factor = 0 THEN 100 ELSE SI.Fill_Factor END Fill_Factor FROM sys.indexes SI LEFT JOIN sys.filegroups FG ON SI.data_space_id = FG.data_space_id INNER JOIN sys.objects SO ON SI.OBJECT_ID = SO.OBJECT_ID INNER JOIN sys.schemas SC ON SC.schema_id = SO.schema_id WHERE OBJECTPROPERTY(SI.OBJECT_ID, 'IsUserTable') = 1 AND SI.[Name] IS NOT NULL AND SI.is_primary_key = 0 AND SI.is_unique_constraint = 0 AND INDEXPROPERTY(SI.OBJECT_ID, SI.[Name], 'IsStatistics') = 0 ORDER BY OBJECT_NAME(SI.OBJECT_ID), SI.Index_ID DECLARE @SchemaName sysname DECLARE @TableName sysname DECLARE @TableId int DECLARE @IndexName sysname DECLARE @FileGroupName sysname DECLARE @IndexId int DECLARE @FillFactor int DECLARE @NewLine nvarchar(4000) SET @NewLine = char(13) + char(10) DECLARE @Tab nvarchar(4000) SET @Tab = SPACE(4) OPEN Indexes_cursor FETCH NEXT FROM Indexes_cursor INTO @SchemaName, @TableName, @TableId, @IndexName, @IndexId, @FileGroupName, @FillFactor WHILE (@@FETCH_STATUS = 0) BEGIN DECLARE @sIndexDesc nvarchar(4000) DECLARE @sCreateSql nvarchar(4000) DECLARE @sDropSql nvarchar(4000) SET @sIndexDesc = '-- Index ' + @IndexName + ' on table ' + @TableName SET @sDropSql = 'IF EXISTS(SELECT 1' + @NewLine + ' FROM sysindexes si' + @NewLine + ' INNER JOIN sysobjects so' + @NewLine + ' ON so.id = si.id' + @NewLine + ' WHERE si.[Name] = N''' + @IndexName + ''' -- Index Name' + @NewLine + ' AND so.[Name] = N''' + @TableName + ''') -- Table Name' + @NewLine + 'BEGIN' + @NewLine + ' DROP INDEX [' + @IndexName + '] ON [' + @SchemaName + '].[' + @TableName + ']' + @NewLine + 'END' + @NewLine SET @sCreateSql = 'CREATE ' IF (IndexProperty(@TableId, @IndexName, 'IsUnique') = 1) BEGIN SET @sCreateSql = @sCreateSql + 'UNIQUE ' END IF (IndexProperty(@TableId, @IndexName, 'IsClustered') = 1) BEGIN SET @sCreateSql = @sCreateSql + 'CLUSTERED ' END SET @sCreateSql = @sCreateSql + 'INDEX [' + @IndexName + '] ON [' + @SchemaName + '].[' + @TableName + ']' + @NewLine + '(' + @NewLine DECLARE IndexColumns_cursor CURSOR FOR SELECT SC.[Name], IC.[is_included_column], IC.is_descending_key FROM sys.index_columns IC INNER JOIN sys.columns SC ON IC.OBJECT_ID = SC.OBJECT_ID AND IC.Column_ID = SC.Column_ID WHERE IC.OBJECT_ID = @TableId AND Index_ID = @IndexId ORDER BY IC.[is_included_column], IC.key_ordinal DECLARE @IxColumn sysname DECLARE @IxIncl bit DECLARE @Desc bit DECLARE @IxIsIncl bit SET @IxIsIncl = 0 DECLARE @IxFirstColumn bit SET @IxFirstColumn = 1 OPEN IndexColumns_cursor FETCH NEXT FROM IndexColumns_cursor INTO @IxColumn, @IxIncl, @Desc WHILE (@@FETCH_STATUS = 0) BEGIN IF (@IxFirstColumn = 1) BEGIN SET @IxFirstColumn = 0 END ELSE BEGIN IF (@IxIsIncl = 0) AND (@IxIncl = 1) BEGIN SET @IxIsIncl = 1 SET @sCreateSql = @sCreateSql + @NewLine + ')' + @NewLine + 'INCLUDE' + @NewLine + '(' + @NewLine END ELSE BEGIN SET @sCreateSql = @sCreateSql + ',' + @NewLine END END SET @sCreateSql = @sCreateSql + @Tab + '[' + @IxColumn + ']' IF @IxIsIncl = 0 BEGIN IF @Desc = 1 BEGIN SET @sCreateSql = @sCreateSql + ' DESC' END ELSE BEGIN SET @sCreateSql = @sCreateSql + ' ASC' END END FETCH NEXT FROM IndexColumns_cursor INTO @IxColumn, @IxIncl, @Desc END CLOSE IndexColumns_cursor DEALLOCATE IndexColumns_cursor SET @sCreateSql = @sCreateSql + @NewLine + ') ' IF @IncludeFillFactor = 1 BEGIN SET @sCreateSql = @sCreateSql + @NewLine + 'WITH (FillFactor = ' + CAST(@FillFactor AS varchar(13)) + ')' + @NewLine END IF @IncludeFileGroup = 1 BEGIN SET @sCreateSql = @sCreateSql + 'ON ['+ @FileGroupName + ']' + @NewLine END ELSE BEGIN SET @sCreateSql = @sCreateSql + @NewLine END PRINT '-- **********************************************************************' PRINT @sIndexDesc PRINT '-- **********************************************************************' IF @IncludeDrop = 1 BEGIN PRINT @sDropSql PRINT 'GO' END PRINT @sCreateSql PRINT 'GO' + @NewLine + @NewLine FETCH NEXT FROM Indexes_cursor INTO @SchemaName, @TableName, @TableId, @IndexName, @IndexId, @FileGroupName, @FillFactor END CLOSE Indexes_cursor DEALLOCATE Indexes_cursor END GO EXEC index_maint.GenerateIndexesScript 1, 0, 1 GO DROP PROCEDURE [index_maint].[GenerateIndexesScript] GO DROP SCHEMA [index_maint] GO
Here’s the thing: while I rely on this script to generate my indexes for my replicas, I can use this script for almost everything related to every index in a database. The script itself is very useful for when you need to script them out, sure… but I like to run it from time to time on my databases to get a pulse check of just how many indexes exist. After all, no indexes are (usually) bad, and too many can be just as bad. But you don’t know what you don’t know, as they say, so even if you don’t plan on actually doing anything with indexes, just having this in your hip pocket is good. From here, once you get a check of what’s there, maybe you could use an index usage script to find ones to drop, assuming you have a lot.
Oh, and what if you wanted to take a backup of just your index creation scripts? Same thing: you could schedule this to run, and I bet without too much modification, have it write the results to a table. That way, if indexes are changed or dropped, you can easily get them back to where they were.
Of course, sometimes I need to move quick or add in some additional info in a hurry for times like that, I still rely on PowerShell. While you can’t easily get an automation “DROP INDEX” statement like the T-SQL above does, if you want to quickly script out all your indexes in a database, you can do so with just a few lines:
import-module sqlserver $Database = Get-ChildItem -Path SQLSERVER:\SQL\servername\instance\databases\databasename $Indexes = @() $Indexes += $Database.Tables.Indexes $Indexes += $Database.Views.Indexes $Indexes.Script()
For this to work you’ll need to SQL Server PowerShell module from the PowerShell gallery, and replace “servername” with your server’s host name, “instance” with the name of your instance (or “default” if it’s a default/unnamed instance), and the name of your database in place of “databasename.” This will just output to the screen, but piping the results to something Out-File will work well too.
You got my upvote for the Aliens quotes – well done!
Pingback: T-SQL Tuesday #104 Roundup – SQL with Bert