-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-- Written by Kimberly L. Tripp - all rights reserved.
-- UPDATE: AUGUST 25, 2004
-- RE: Fix for DESC clustered indexes. There are no errors for DESC in 
-- nonclustered indexes. Thanks!
-- 
-- For more scripts and sample code, check out 
-- 	http://www.SQLSkills.com
--
-- Disclaimer - Thoroughly test this script, execute at your own risk.
-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

-- Execute this whole script to create the sp_RebuildIndexes stored procedure in Master.
-- Best Viewed with Courier New 12pt. and Tabs saved as 4 spaces not 8. (Tools, Options, Editor)

-- To use the sp_RebuildIndexes procedure once created use:
-- sp_RebuildIndexes 
--		To Rebuild All Indexes on All Tables for all that have a Scan Density < 100%
-- sp_RebuildIndexes @ScanDensity = 80
--		To Rebuild All Indexes on All Tables with a Scan Density of < 80%
-- sp_RebuildIndexes 'Authors'
--		To Rebuild All Indexes on the authors table - for a Scan Density of < 100%
-- sp_RebuildIndexes 'Authors', 80
--		To Rebuild All Indexes on the authors table - for a Scan Density of < 80%
-- Object Name and ScanDensity are both optional parameters. 
-- ScanDensity must be a whole number between 1 and 100.

USE master
go
IF OBJECTPROPERTY(object_id('sp_RebuildClusteredIndex'), 'IsProcedure') = 1
	DROP PROCEDURE sp_RebuildClusteredIndex
go

IF OBJECTPROPERTY(object_id('sp_RebuildIndexes'), 'IsProcedure') = 1
	DROP PROCEDURE sp_RebuildIndexes
go

CREATE PROCEDURE sp_RebuildClusteredIndex
(
	@TableName		sysname		= NULL,
	@IndexName		sysname		= NULL
)		
AS
-- Written by Kimberly L. Tripp of SYSolutions, Inc.
-- For more code samples go to http://www.sqlskills.com
-- NOTE: If your clustered index is NOT unique then rebuilding the clustered
-- index will cause the non-clustered indexes to be rebuilt. If the nonclustered
-- indexes were fragmented then this series of scripts will build them again.
-- This is something this script DOES NOT look for...because you should not create
-- clustered indexes on a non-unique column or composite set of columns.
IF @TableName IS NOT NULL
	BEGIN
		IF (OBJECTPROPERTY(object_id(@TableName), 'IsUserTable') = 0 
				AND OBJECTPROPERTY(object_id(@TableName), 'IsView') = 0) 
			BEGIN
				RAISERROR('Object: %s exists but is NOT a User-defined Table. This procedure only accepts valid table names to process for index rebuilds.', 16, 1, @TableName)
				RETURN
			END
		ELSE
			BEGIN
				IF OBJECTPROPERTY(object_id(@TableName), 'IsTable') IS NULL
					BEGIN
						RAISERROR('Object: %s does not exist within this database. Please check the table name and location (which database?). This procedure only accepts existing table names to process for index rebuilds.', 16, 1, @TableName)
						RETURN
					END
			END
    END

IF @IndexName IS NOT NULL
	BEGIN
		IF INDEXPROPERTY(object_id(@TableName), @IndexName, 'IsClustered') = 0
			BEGIN
				RAISERROR('Index: %s exists but is a Clustered Index. This procedure only accepts valid table names and their clustered indexes for rebuilds.', 16, 1, @IndexName)
				RETURN
			END
		ELSE
			BEGIN
				IF INDEXPROPERTY(object_id(@TableName), @IndexName, 'IsClustered') IS NULL
					BEGIN
						SELECT @TableName, @IndexName, INDEXPROPERTY(object_id(@TableName), @IndexName, 'IsClustered')
						RAISERROR('There is no index with name:%s on this table. Please check the table name and index name as well as location (which database?). This procedure only accepts existing table names and their clustered indexes for rebuilds.', 16, 1, @IndexName)
						RETURN
					END
			END
    END

-- So now we have a valid table, a valid CLUSTERED index and we're ready to rebuild.
	-- Here's a quick overview of what this code will do:
		-- Get the Column List and Index Defintion (Use the output from sp_helpindex)
		-- Figure out if it's UNIQUE - to specify in CREATE INDEX statement
		-- Build and Execute the CREATE INDEX command through dynamic string execution

DECLARE @ExecStr		nvarchar(4000) -- more than enough even if 16 cols of 128 chars, 
									   -- Tablename of 128 and Indexname of 128...
									   -- but if this is the case you have other problems :).
		, @ColList		nvarchar(3000)
		, @Unique		nvarchar(7)	   -- Will be either '' or 'Unique ' and added to CR Index String
		, @FillFactor	nvarchar(100)

CREATE TABLE #IndexInfo
(
	IndexName	sysname,
	IndexDesc	varchar(210),
	IndexKeys	nvarchar(2126)
)

INSERT INTO #IndexInfo 
	EXEC sp_helpindex @TableName

SELECT @ColList = IndexKeys
		 , @Unique = CASE 
						WHEN IndexDesc LIKE 'clustered, unique%' 
							THEN 'Unique '
						ELSE ''
					END --CASE Expression
		 , @FillFactor = ', FILLFACTOR = ' + NULLIF(convert(nvarchar(3), 
						(SELECT OrigFillFactor 
							FROM sysindexes 
							WHERE id = object_id(@TableName) 
								AND Name = @IndexName)), 0)
FROM #IndexInfo
WHERE IndexName = @IndexName

SELECT @ExecStr = 'CREATE ' + @Unique + 'CLUSTERED INDEX ' 
						+ QUOTENAME(@IndexName, ']') + ' ON ' 
						+ QUOTENAME(@TableName, ']') + '(' + @collist 
						+ ') WITH DROP_EXISTING ' + ISNULL(@FillFactor, '')

-- Added Aug 25, 04: FIX FOR DESC Indexes.
SELECT @ExecStr = REPLACE(@ExecStr, '(-)', ' DESC')

-- For testing the String
-- SELECT @ExecStr

-- Create the Clustered Index
EXEC(@ExecStr)
go

CREATE PROCEDURE sp_RebuildIndexes
(
	@TableName		sysname		= NULL,
	@ScanDensity 	tinyint		= 100
)
AS
-- Written by Kimberly L. Tripp of SYSolutions, Inc.
-- For more code samples go to http://www.sqlskills.com
--
-- This procedure will get the Fragmentation information
-- for all tables and indexes within the database. 
-- Programmatically it will then walk the list rebuilding all
-- indexes that have a scan density less than the value 
-- passed in - by default any less than 100% contiguous.
-- 
-- Use this script as a starting point. Modify it for your
-- options, ideas, etc. - and then schedule it to run regularly.
-- 
-- NOTE - This gathers density information for all tables 
-- and all indexes. This might be time consuming on large
-- databases. 
-- 
-- DISCLAIMER - Execute at your own risk. TEST THIS FIRST. 
SET NOCOUNT ON

IF @ScanDensity IS NULL
	SET @ScanDensity = 100
 
IF @ScanDensity NOT BETWEEN 1 AND 100
	BEGIN
		RAISERROR('Value supplied:%i is not valid. @ScanDensity is a percentage. Please supply a value for Scan Density between 1 and 100.', 16, 1, @ScanDensity)
		RETURN
	END
IF @TableName IS NOT NULL
	BEGIN
		IF OBJECTPROPERTY(object_id(@TableName), 'IsUserTable') = 0 
			BEGIN
				RAISERROR('Object: %s exists but is NOT a User-defined Table. This procedure only accepts valid table names to process for index rebuilds.', 16, 1, @TableName)
				RETURN
			END
		ELSE
			BEGIN
				IF OBJECTPROPERTY(object_id(@TableName), 'IsTable') IS NULL
					BEGIN
						RAISERROR('Object: %s does not exist within this database. Please check the table name and location (which database?). This procedure only accepts existing table names to process for index rebuilds.', 16, 1, @TableName)
						RETURN
					END
			END
    END

-- Otherwise the Object Exists and it is a table so we'll continue from here. 
-- First thing to do is create a temp location for the data returned from DBCC SHOWCONTIG

CREATE TABLE #ShowContigOutput
(
	ObjectName			sysname,
	ObjectId				int,
	IndexName				sysname,
	IndexId					tinyint,
	[Level]					tinyint,
	Pages					int,
	[Rows]					bigint,
	MinimumRecordSize		smallint,
	MaximumRecordSize	smallint,
	AverageRecordSize		smallint,
	ForwardedRecords		bigint,
	Extents					int,
	ExtentSwitches			numeric(10,2),
	AverageFreeBytes		numeric(10,2),
	AveragePageDensity	numeric(10,2),
	ScanDensity			numeric(10,2),
	BestCount				int,
	ActualCount			int,
	LogicalFragmentation	numeric(10,2),
	ExtentFragmentation	numeric(10,2)
)                            

IF @TableName IS NOT NULL -- then we only need the showcontig output for that table
	INSERT #ShowContigOutput
		EXEC('DBCC SHOWCONTIG (' + @TableName + ') WITH FAST, ALL_INDEXES, TABLERESULTS') 
ELSE -- All Tables, All Indexes Will be processed.
	INSERT #ShowContigOutput
		EXEC('DBCC SHOWCONTIG WITH FAST, ALL_INDEXES, TABLERESULTS') 
PRINT N' '

-- Quick test to see if everything is getting here correctly
-- SELECT * FROM #ShowContigOutput

-- Walk the showcontig output table skipping all replication tables as well as all tables necessary for
-- the UI. This is also where you can list large tables that you don't want to rebuild all at one time.
-- NOTE: If you take out a large table from rebuilding this script may have already checked density
-- meaning that the expense in terms of time may have been expensive.
-- Also, you should use a different procedure to rebuild a large table specifically. 
-- Even when you pass in the tablename it will be avoided here if MANUALLY added to the 
-- list by you. 
-- Test, Test, Test!

DECLARE @ObjectName				sysname,
			@IndexName			sysname,
			@QObjectName			nvarchar(258),
			@QIndexName			nvarchar(258),
			@IndexID				tinyint,
			@ActualScanDensity	numeric(10,2),
			@InformationalOutput	nvarchar(4000),
			@StartTime				datetime,
			@EndTime				datetime

DECLARE TableIndexList CURSOR FAST_FORWARD FOR 
	SELECT ObjectName, IndexName, IndexID, ScanDensity 
	FROM #ShowContigOutput AS sc
		JOIN sysobjects AS so ON sc.ObjectID = so.id
	WHERE sc.ScanDensity < @ScanDensity 
		AND (OBJECTPROPERTY(sc.ObjectID, 'IsUserTable') = 1 
				OR OBJECTPROPERTY(sc.ObjectID, 'IsView') = 1)
		AND so.STATUS > 0
		AND sc.IndexID BETWEEN 1 AND 250 
		AND sc.ObjectName NOT IN ('dtproperties') 
			-- Here you can list large tables you do not WANT rebuilt.
	ORDER BY sc.ObjectName, sc.IndexID

OPEN TableIndexList

FETCH NEXT FROM TableIndexList 
	INTO @ObjectName, @IndexName, @IndexID, @ActualScanDensity

WHILE (@@fetch_status <> -1)
BEGIN
	IF (@@fetch_status <> -2)
	BEGIN
		SELECT @QObjectName = QUOTENAME(@ObjectName, ']')
		SELECT @QIndexName = QUOTENAME(@IndexName, ']')
		SELECT @InformationalOutput = N'Processing Table: ' + RTRIM(UPPER(@QObjectName)) 
											+ N' Rebuilding Index: ' + RTRIM(UPPER(@QIndexName)) 
		PRINT @InformationalOutput
		IF @IndexID = 1 
		BEGIN
			SELECT @StartTime = getdate()
			EXEC sp_RebuildClusteredIndex @ObjectName, @IndexName
			SELECT @EndTime = getdate()
			SELECT @InformationalOutput = N'Total Time to process = ' + convert(nvarchar, datediff(ms, @StartTime, @EndTime)) + N' ms'
			PRINT @InformationalOutput 
		END
		ELSE
		BEGIN
			SELECT @StartTime = getdate()
			EXEC('DBCC DBREINDEX(' + @QObjectName + ', ' + @QIndexName + ') WITH NO_INFOMSGS')
			SELECT @EndTime = getdate()
			SELECT @InformationalOutput = N'Total Time to process = ' + convert(nvarchar, datediff(ms, @StartTime, @EndTime)) + N' ms'
			PRINT @InformationalOutput 
		END
		PRINT N' '
		FETCH NEXT FROM TableIndexList 
			INTO @ObjectName, @IndexName, @IndexID, @ActualScanDensity
	END
END
PRINT N' '
SELECT @InformationalOutput = N'***** All Indexes have been rebuilt.  ***** ' 
PRINT @InformationalOutput 
DEALLOCATE TableIndexList 
go
