Script:

/****************The Lytic Group, copyright, 2021*****************/

/****************Published February, 2021 *****************/

/*********from http://www.LyticGroup.com by Edward Heraux*****************/


/************************************************************************************************

This pair of stored procs together will allow you to defrag/reindex all the tables in a database 

that you deem necessary, based on a fragmentation percentage that you pass in as a parameter. 

It also keeps a "Before" and "After" history of your tables' fragmentation 

every time you run it, so that you can actually report on this regular maintenance easily.

Schedule this beauty to run occasionally, and a good portion of your job of

keeping your tables streamlined and healthy gets done for you.

 *******************************************************************************************/


/****************IMPORTANT WARNING!!!!!*****************/

/**********Do not run the second stored proc, uspReindexAll,

 at just any old time of day on a production SQL Server!!!!!

This is an expensive stored procedure that will take ages to run 

(and tie up a lot of resources) on a large database, 

especially if little defragmentation has ever been done on it.


Pick the quietest, slowest part of your SQL Server's work week,

 and this script can be a handy administration automation tool.

*******************************************************************/



--1. uspTableContiguity

/*This first stored procedure makes all the data from DBCC SHOWCONTIG

available so that we can populate a table with its data

*/

SET QUOTED_IDENTIFIER ON 

GO

SET ANSI_NULLS ON 

GO



CREATE PROC dbo.uspTableContiguity 

--Pass a NULL @tablename if you want to capture the contiguity stats for all tables

@tablename varchar(255)=NULL

AS

--Show contiguity info for all tables if no table name is passed in

--(The next stored proc, however, will always call this to obtain info on all tables)

IF @tablename IS NULL

     BEGIN

DBCC SHOWCONTIG WITH TABLERESULTS

RETURN

     END

ELSE

--otherwise find the ID of the table passed in

    BEGIN

DECLARE @tableid int

SET @tableid=(SELECT id FROM sysobjects where type='u' and name = @tablename)


DBCC SHOWCONTIG (@tableid) WITH TABLERESULTS

    END

GO



SET QUOTED_IDENTIFIER OFF 

GO

SET ANSI_NULLS ON 

GO

-------------------------End of first proc------------------------






--2. uspReindexTables

/***********************************************************************/

/*This second  stored procedure grabs and looks through the data produced by

DBCC SHOWCONTIG, and REINDEXes all the tables that are above a fragmentation

percentage that you specify.

It will also create & update a table with time-stamped 

records of the fragmentation of those tables before and after the REINDEXing gets done.

***********************************************************************/


SET QUOTED_IDENTIFIER OFF 

GO

SET ANSI_NULLS ON 

GO

SET NOCOUNT ON

GO


ALTER  PROC [dbo].[uspReindexTables] 

@db sysname

, @FragThreshold int  --Minimum fragmentation percentage that you want 

--to trigger a reindexing of that table or index.

--We recommend 30.


AS

DECLARE @ErrorText varchar(2048)


SET @FragThreshold=100-@FragThreshold


BEGIN TRAN

BEGIN TRY

/*Make sure the table is in place to store all the fragmentation data*/

IF NOT EXISTS (SELECT * FROM sysobjects where type='u' AND name='tblFragmentationInfo')  

CREATE TABLE dbo.tblFragmentationInfo (

RowID int IDENTITY,

[ObjectName] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,

[ObjectID] [int] NULL ,

[IndexName] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,

[IndexID] [int] NULL ,

[Level] [int] NULL ,

[Pages] [int] NULL ,

[Rows] [int] NULL ,

[MinimumRecordSize] [int] NULL ,

[MaximumRecordSize] [int] NULL ,

[AverageRecordSize] [decimal](11, 5) NULL ,

[ForwardedRecords] [int] NULL ,

[Extents] [int] NULL ,

[ExtentSwitches] [int] NULL ,

[AverageFreeBytes] [decimal](11, 5) NULL ,

[AveragePageDensity] [decimal](11, 5) NULL ,

[ScanDensity] [decimal](11, 5) NULL ,

[BestCount] [int] NULL ,

[ActualCount] [int] NULL ,

[LogicalFragmentation] [decimal](18, 0) NULL ,

[ExtentFragmentation] [decimal](11, 5) NULL ,

[SchemaID] int NULL,

[SchemaName] sysname NULL,

[CheckDate] [datetime] NULL CONSTRAINT [DF_tblFragmentationInfo_DefragDate] DEFAULT (getdate()),

[DefragDate] [datetime] NULL, --the time-stamp of when this table was reindexed during this sp

[TableSize] AS ([Pages] * 8),

[InProcess] bit CONSTRAINT DF_InProcess_Yes DEFAULT (1) --Distinguishes records added during this running of the sp

-- from records already in the table from prior executions

) ON [PRIMARY]


--(Just for testing)

--ELSE PRINT 'tblFragmentationInfo exists'


/*Dump a 'before' snapshot of current fragmentation into tblFragmentationInfo*/

INSERT INTO dbo.tblFragmentationInfo (ObjectName, ObjectId, IndexName, IndexID, [Level], Pages, [rows], MinimumRecordSize,

MaximumRecordSize, AverageRecordSize, ForwardedRecords, Extents, ExtentSwitches, AverageFreeBytes, 

AveragePageDensity, ScanDensity, BestCount, ActualCount, LogicalFragmentation, ExtentFragmentation)

EXECUTE dbo.uspTableContiguity

IF @@Error<>0 

BEGIN

  RAISERROR ('Could not create snapshot of tables. tblFragmentationInfo was not populated.',16,1)

  ROLLBACK TRAN

  RETURN

END


/*Put the schema IDs in the table*/

UPDATE tblFragmentationInfo

SET SchemaName=S.name, SchemaID=S.schema_id

FROM sys.schemas S

INNER JOIN sys.all_objects A on A.Schema_id=S.schema_id

INNER JOIN tblFragmentationInfo F ON F.ObjectID=A.object_id 



/*Get rid of system tables in tblFragmentationInfo, which can't be REINDEXed*/

DELETE FROM dbo.tblFragmentationInfo WHERE LEFT(ObjectName,3)='sys'

END TRY

/*Error handling block*/

BEGIN CATCH

SELECT @ErrorText='Errors INSERTing/UPDATing tblFragmentationInfo table. ErrorNum ' + ERROR_NUMBER() +':  ' + ERROR_MESSAGE()

RAISERROR(@ErrorText,16,1)

ROLLBACK TRAN

RETURN

END CATCH

COMMIT


/*Create a temp table holding a list of the database's tables that are fragmented enough for concern,

 but just the ones called up for this procedure*/

SELECT RowID, ObjectName, SchemaName, ScanDensity  

INTO #tblFragmentedTables 

FROM dbo.tblFragmentationInfo 

WHERE ScanDensity<@FragThreshold AND InProcess<>0



DECLARE @rows int, @counter int, @CurrentRowID int

DECLARE @FQTableName sysname


SELECT @counter=1, @rows=COUNT(*) 

FROM #tblFragmentedTables


/*(Just for testing) A quick look at which tables we're about to REINDEX*/

--SELECT 'Tables About To Be Defragged' 

--SELECT * FROM #tblFragmentedTables


/*Begin a loop to defrag each table that made it into the #FragmentedTables table.

(This is SO much more efficient than a cursor!!)*/

BEGIN TRY

WHILE @counter<=@rows

BEGIN

SELECT Top 1 @CurrentRowID=RowID, @FQTableName=SchemaName + '.' + ObjectName

FROM #tblFragmentedTables

ORDER BY RowID ASC

--(Just for testing) Show which single table is about to be REINDEXed

--SELECT @CurrentRowID AS CurrentRowID, @FQTableName AS CurrentTableName


DBCC DBREINDEX(@FQTableName,'',0)

--Log that this table's been taken care of, back in tblFragmentationinfo.

UPDATE dbo.tblFragmentationInfo 

SET InProcess=0

FROM dbo.tblFragmentationInfo F

INNER JOIN (SELECT Top 1 SchemaName,ObjectName FROM #tblFragmentedTables) AS Sub

ON F.SchemaName=Sub.SchemaName AND F.ObjectName=Sub.ObjectName

WHERE InProcess<>0

--Prepare for the next iteration of the loop.

DELETE #tblFragmentedTables WHERE RowID=@CurrentRowID

SET @counter=@counter+1

END

END TRY

--Error handling block

BEGIN CATCH

SELECT @ErrorText='Failed reindexing a table. ErrorNum ' + ERROR_NUMBER() +':  ' + ERROR_MESSAGE()

RAISERROR(@ErrorText,16,1)

ROLLBACK TRAN

RETURN

END CATCH




/*A bunch of things to finalize the "After" info in tblFragmentationInfo, and clean up after ourselves*/

BEGIN TRAN

BEGIN TRY


/* Dump an 'after' snapshot of current fragmentation into tblFragmentationInfo */

INSERT dbo.tblFragmentationInfo (ObjectName, ObjectId, IndexName, IndexID, [Level], Pages, [rows], MinimumRecordSize,

MaximumRecordSize, AverageRecordSize, ForwardedRecords, Extents, ExtentSwitches, AverageFreeBytes, 

AveragePageDensity, ScanDensity, BestCount, ActualCount, LogicalFragmentation, ExtentFragmentation)

EXECUTE dbo.uspTableContiguity


/*Make sure the 'after' picture doesn't get processed in the future

, since InProcess defaults to TRUE*/

UPDATE dbo.tblFragmentationInfo

SET InProcess=0

WHERE InProcess<>0


/*Get rid of system tables in tblFragmentationInfo, which can't be REINDEXed*/

DELETE FROM dbo.tblFragmentationInfo WHERE LEFT(ObjectName,3)='sys'


--(Just for testing) Take a look at the reindexing history to-date

--SELECT ObjectName, ScanDensity, CheckDate, DefragDate, InProcess

--FROM dbo.tblFragmentationInfo

--ORDER BY CheckDate, ObjectName

END TRY

--Error handling block

BEGIN CATCH

SELECT @ErrorText='ErrorNum ' + ERROR_NUMBER() +':  ' + ERROR_MESSAGE()

RAISERROR(@ErrorText,16,1)

ROLLBACK TRAN

RETURN

END CATCH

COMMIT


--Get rid of the temp table

DROP TABLE #tblFragmentedTables

GO

SET QUOTED_IDENTIFIER OFF 

GO

SET ANSI_NULLS ON 

GO