Just a day ago, I received email from regular reader Rajiv Kayasthy about a script which demonstrates the A Simple Way To Defragment All Indexes In A Database That Is Fragmented Above A Declared Threshold. He found this script on TechNet BOL and was attempting to run on SQL Server but was getting continuous error
Msg 2501, Level 16, State 45, Line 1
Cannot find a table or object with the name “TableName”. Check the system catalog.
After looking at the script provided on BOL I found that it has very small error. It was retrieving data without prefixing database schema. I have modified original script and corrected it to run on SQL Server 2005.
Following corrected script demonstrates Simple Way To Defragment All Indexes In A Database That Is Fragmented Above A Declared Threshold.
/* Originally created by Microsoft */
/* Error corrected by Pinal Dave (http://www.SQLAuthority.com) */
-- Specify your Database Name
USE AdventureWorks;
GO
-- Declare variables
SET NOCOUNT ON;
DECLARE @tablename VARCHAR(128);
DECLARE @execstr VARCHAR(255);
DECLARE @objectid INT;
DECLARE @indexid INT;
DECLARE @frag decimal;
DECLARE @maxfrag decimal;
-- Decide on the maximum fragmentation to allow for.
SELECT @maxfrag = 30.0;
-- Declare a cursor.
DECLARE tables CURSOR FOR
SELECT CAST(TABLE_SCHEMA AS VARCHAR(100))
+'.'+CAST(TABLE_NAME AS VARCHAR(100))
AS Table_Name
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE';
-- Create the table.
CREATE TABLE #fraglist (
ObjectName CHAR(255),
ObjectId INT,
IndexName CHAR(255),
IndexId INT,
Lvl INT,
CountPages INT,
CountRows INT,
MinRecSize INT,
MaxRecSize INT,
AvgRecSize INT,
ForRecCount INT,
Extents INT,
ExtentSwitches INT,
AvgFreeBytes INT,
AvgPageDensity INT,
ScanDensity decimal,
BestCount INT,
ActualCount INT,
LogicalFrag decimal,
ExtentFrag decimal);
-- Open the cursor.
OPEN tables;
-- Loop through all the tables in the database.
FETCH NEXT
FROM tables
INTO @tablename;
WHILE @@FETCH_STATUS = 0
BEGIN;
-- Do the showcontig of all indexes of the table
INSERT INTO #fraglist
EXEC ('DBCC SHOWCONTIG (''' + @tablename + ''')
WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS');
FETCH NEXT
FROM tables
INTO @tablename;
END;
-- Close and deallocate the cursor.
CLOSE tables;
DEALLOCATE tables;
-- Declare the cursor for the list of indexes to be defragged.
DECLARE indexes CURSOR FOR
SELECT ObjectName, ObjectId, IndexId, LogicalFrag
FROM #fraglist
WHERE LogicalFrag >= @maxfrag
AND INDEXPROPERTY (ObjectId, IndexName, 'IndexDepth') > 0;
-- Open the cursor.
OPEN indexes;
-- Loop through the indexes.
FETCH NEXT
FROM indexes
INTO @tablename, @objectid, @indexid, @frag;
WHILE @@FETCH_STATUS = 0
BEGIN;
PRINT 'Executing DBCC INDEXDEFRAG (0, ' + RTRIM(@tablename) + ',
' + RTRIM(@indexid) + ') - fragmentation currently '
+ RTRIM(CONVERT(VARCHAR(15),@frag)) + '%';
SELECT @execstr = 'DBCC INDEXDEFRAG (0, ' + RTRIM(@objectid) + ',
' + RTRIM(@indexid) + ')';
EXEC (@execstr);
FETCH NEXT
FROM indexes
INTO @tablename, @objectid, @indexid, @frag;
END;
-- Close and deallocate the cursor.
CLOSE indexes;
DEALLOCATE indexes;
-- Delete the temporary table.
DROP TABLE #fraglist;
GO
Reference : Pinal Dave (http://www.SQLAuthority.com), TechNet BOL






Since INDEXDEFRAG will be not supported in SS2008, how about a version of this scrip t with ALTER INDEX rather.
Thanks, Pinal. I would like to share a solution that I’ve made that works a little differently. It does index rebuild online or offline, index reorganization, statistics update, index reorganization and statistics update or nothing based on fragmentation level and lob existence.
http://blog.ola.hallengren.com/blog/_archives/2008/1/1/3440068.html
http://blog.ola.hallengren.com/_attachments/3440068/Documentation.html
http://blog.ola.hallengren.com/_attachments/3440068/IndexOptimize.sql
Ola Hallengren
http://ola.hallengren.com
hello
found you solution helpfull. don’t want to seem ungratefull but i have a few additions:
1st - i’d check for the existence of the temp table:
if exists (select name from tempdb.dbo.sysobjects where name like ‘#fraglist%’)
drop table #fraglist
2nd - id use the alter index aproach as dbcc indexdefrag is deprecated in 2005 and unsupported in 2008. here si the cod snippet for those lazy as me :) :
PRINT ‘Executing ALTER INDEX ‘ + RTRIM(@IdxName) + ‘ ON ‘ + RTRIM(@tablename) + ‘REORGANIZE WITH ( LOB_COMPACTION = ON ) - fragmentation currently ‘ + RTRIM(CONVERT(VARCHAR(15),@frag)) + ‘%’;
SELECT @execstr = ‘ALTER INDEX ‘ + RTRIM(@IdxName) + ‘ ON ‘ + RTRIM(@tablename) + ‘ REORGANIZE WITH ( LOB_COMPACTION = ON )’
EXEC (@execstr);
instead of
PRINT ‘Executing DBCC INDEXDEFRAG (0, ‘ + RTRIM(@tablename) + ‘,
‘ + RTRIM(@indexid) + ‘) - fragmentation currently ‘
+ RTRIM(CONVERT(VARCHAR(15),@frag)) + ‘%’;
SELECT @execstr = ‘DBCC INDEXDEFRAG (0, ‘ + RTRIM(@objectid) + ‘,
‘ + RTRIM(@indexid) + ‘)’;
EXEC (@execstr);
oops! forgot to tell.
should first declare @IdxName and then use it with the selector
I Just like to share this… It defrag index with 50% fragment or more then print a report.
– Index Fragmentation: Delete unused pages
– ICDB
Declare @SQL nvarchar(4000),
@acursor_TableName cursor,
@TableName varchar(150),
@acursor_Indexes cursor,
@Indexes varchar(150)
Create Table #Report
(
[Table Name] varchar(150),
Fragment decimal(18,2),
Defragment decimal(18,2),
Percentage decimal(18,2)
)
Create Table #TableIndex
(
Indexes varchar(150),
Fragment decimal(18,2)
)
–Start Looping (Get table)
set @acursor_TableName = cursor for SELECT TABLE_NAME AS Table_Name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ‘BASE TABLE’ And TABLE_SCHEMA = ‘dbo’
open @acursor_TableName
fetch next from @acursor_TableName into @TableName
while (@@fetch_status = 0)
begin
—————————————————————————————————
INSERT INTO #Report
Values(@TableName,’0′,’0′,’0′)
Truncate Table #TableIndex
Set @SQL = ‘Select Name, Avg_fragmentation_in_Percent
FROM sys.dm_db_index_physical_stats (db_id(N”ICDB”),
Object_id(”’ + @TableName + ”’),null,null,null) as A
JOIN
sys.indexes as B on a.Object_ID = b.Object_id
AND a.index_id = b.index_id
AND name is not null
AND name ””’
Insert INTO #TableIndex(Indexes,Fragment)
Execute sp_Executesql @sql
UPDATE A SET Fragment = (SELECT Sum(Fragment) / (Select Count(*) FROM #TableIndex) FROM #TableIndex)
FROM #Report A
Where [Table Name] = @TableName
–Start Looping (Get Indexes)
set @acursor_Indexes = cursor for SELECT Indexes FROM #TableIndex Where Fragment >= 50
open @acursor_Indexes
fetch next from @acursor_Indexes into @Indexes
while (@@fetch_status = 0)
begin
—————————————————————————————————
Set @SQL = ‘Alter INDEX ‘ + @Indexes + ‘ on ‘ + @TableName + ‘ Rebuild’
Execute sp_Executesql @sql
—————————————————————————————————
fetch next from @acursor_Indexes into @Indexes
end
close @acursor_Indexes
deallocate @acursor_Indexes
–End Looping
Truncate Table #TableIndex
Set @SQL = ‘Select Name, Avg_fragmentation_in_Percent
FROM sys.dm_db_index_physical_stats (db_id(N”ICDB”),
Object_id(”’ + @TableName + ”’),null,null,null) as A
JOIN
sys.indexes as B on a.Object_ID = b.Object_id
AND a.index_id = b.index_id
AND name is not null
AND name ””’
Insert INTO #TableIndex(Indexes,Fragment)
Execute sp_Executesql @sql
UPDATE A SET Defragment = (SELECT Sum(Fragment) / (Select Count(*) FROM #TableIndex) FROM #TableIndex)
FROM #Report A
Where [Table Name] = @TableName
—————————————————————————————————
fetch next from @acursor_TableName into @TableName
end
close @acursor_TableName
deallocate @acursor_TableName
–End Looping
Update A Set Percentage = (Defragment/Fragment) * 100
FROM #Report A
Where Defragment 0
and Defragment is not null
and Fragment 0
and Fragment is not null
Select *
FROM #Report
Where Defragment is not null
and Defragment ‘0.00′
and Defragment = Fragment
Drop table #TableIndex
Drop Table #Report
HalfHuman’s two-part suggestion results in the following complete script. I also added a @ViewOnly variable that will allow you to run this script as a test only. I am also wrapping this post with <CODE> </CODE>to keep the webserver from mangling the script.
-- Specify your Database Name
USE DatabaseName
GO
-- Declare variables
SET NOCOUNT ON
DECLARE @tablename VARCHAR(128)
DECLARE @execstr VARCHAR(255)
DECLARE @objectid INT
DECLARE @indexid INT
DECLARE @frag decimal
DECLARE @maxfrag decimal
DECLARE @IdxName varchar(128)
DECLARE @ViewOnly bit
-- Set to 1 to view proposed actions, set to 0 to Execute proposed actions:
SET @ViewOnly=1
-- Decide on the maximum fragmentation to allow for.
SET @maxfrag = 30.0
-- Declare a cursor.
DECLARE tables CURSOR FOR
SELECT CAST(TABLE_SCHEMA AS VARCHAR(100))
+'.'+CAST(TABLE_NAME AS VARCHAR(100))
AS Table_Name
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
-- Create the temporary table.
if exists (select name from tempdb.dbo.sysobjects where name like '#fraglist%')
drop table #fraglist
CREATE TABLE #fraglist (
ObjectName CHAR(255),
ObjectId INT,
IndexName CHAR(255),
IndexId INT,
Lvl INT,
CountPages INT,
CountRows INT,
MinRecSize INT,
MaxRecSize INT,
AvgRecSize INT,
ForRecCount INT,
Extents INT,
ExtentSwitches INT,
AvgFreeBytes INT,
AvgPageDensity INT,
ScanDensity decimal,
BestCount INT,
ActualCount INT,
LogicalFrag decimal,
ExtentFrag decimal)
-- Open the cursor.
OPEN tables
-- Loop through all the tables in the database.
FETCH NEXT
FROM tables
INTO @tablename
WHILE @@FETCH_STATUS = 0
BEGIN
-- Do the showcontig of all indexes of the table
INSERT INTO #fraglist
EXEC ('DBCC SHOWCONTIG (''' + @tablename + ''')
WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS')
FETCH NEXT
FROM tables
INTO @tablename
END
-- Close and deallocate the cursor.
CLOSE tables
DEALLOCATE tables
-- Declare the cursor for the list of indexes to be defragged.
DECLARE indexes CURSOR FOR
SELECT ObjectName, ObjectId, IndexId, LogicalFrag, IndexName
FROM #fraglist
WHERE LogicalFrag >= @maxfrag
AND INDEXPROPERTY (ObjectId, IndexName, 'IndexDepth') > 0
-- Open the cursor.
OPEN indexes
-- Loop through the indexes.
FETCH NEXT
FROM indexes
INTO @tablename, @objectid, @indexid, @frag, @IdxName
WHILE @@FETCH_STATUS = 0
BEGIN
IF (@ViewOnly=1)
BEGIN
PRINT 'WOULD be executing ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REORGANIZE WITH ( LOB_COMPACTION = ON ) - Fragmentation currently ' + RTRIM(CONVERT(VARCHAR(15),@frag)) + '%'
END
ELSE
BEGIN
PRINT 'Now executing ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REORGANIZE WITH ( LOB_COMPACTION = ON ) - Fragmentation currently ' + RTRIM(CONVERT(VARCHAR(15),@frag)) + '%'
SELECT @execstr = 'ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REORGANIZE WITH ( LOB_COMPACTION = ON )'
EXEC (@execstr)
END
FETCH NEXT
FROM indexes
INTO @tablename, @objectid, @indexid, @frag, @IdxName
END
-- Close and deallocate the cursor.
CLOSE indexes
DEALLOCATE indexes
-- Delete the temporary table.
DROP TABLE #fraglist
GO
This is a different version than the prior post - it provides the ability to reorg or rebuild indexes, based on fragmentation value.
/*
Added a @ViewOnly variable that will allow you to run this script as a test only and review proposed actions.
I also enhanced to perform either a reorg or rebuild, based on range of fragmentation value.
~Paul DeBrino : infinityrd.com
--INSPECT INDEXES' FILL FACTOR VALUES:
use YourDBName
select * from sys.indexes order by name
--VIEW THE DEFAULT FILL FACTOR OF YOUR SERVER:
use YourDBname
exec sp_configure 'show advanced options', 1
go
reconfigure
go
exec sp_configure 'fill factor'
go
*/
-- Specify your Database Name
USE YourDBname
GO
-- Declare variables
SET NOCOUNT ON
DECLARE @tablename VARCHAR(128)
DECLARE @execstr VARCHAR(255)
DECLARE @objectid INT
DECLARE @indexid INT
DECLARE @frag decimal
DECLARE @maxreorg decimal
DECLARE @maxrebuild decimal
DECLARE @IdxName varchar(128)
DECLARE @ViewOnly bit
DECLARE @ReorgOptions varchar(255)
DECLARE @RebuildOptions varchar(255)
-- Set to 1 to view proposed actions, set to 0 to Execute proposed actions:
SET @ViewOnly=1
-- Decide on the maximum fragmentation to allow for a reorganize.
-- AVAILABLE OPTIONS: http://technet.microsoft.com/en-us/library/ms188388(SQL.90).aspx
SET @maxreorg = 20.0
SET @ReorgOptions = ‘LOB_COMPACTION=ON’
– Decide on the maximum fragmentation to allow for a rebuild.
SET @maxrebuild = 30.0
– NOTE: only specifiy FILLFACTOR=x if x is something other than zero:
SET @RebuildOptions = ‘PAD_INDEX=OFF, SORT_IN_TEMPDB=ON, STATISTICS_NORECOMPUTE=OFF, ALLOW_ROW_LOCKS=ON, ALLOW_PAGE_LOCKS=ON’
– Declare a cursor.
DECLARE tables CURSOR FOR
SELECT CAST(TABLE_SCHEMA AS VARCHAR(100))
+’.'+CAST(TABLE_NAME AS VARCHAR(100))
AS Table_Name
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = ‘BASE TABLE’
– Create the temporary table.
if exists (select name from tempdb.dbo.sysobjects where name like ‘#fraglist%’)
drop table #fraglist
CREATE TABLE #fraglist (
ObjectName CHAR(255),
ObjectId INT,
IndexName CHAR(255),
IndexId INT,
Lvl INT,
CountPages INT,
CountRows INT,
MinRecSize INT,
MaxRecSize INT,
AvgRecSize INT,
ForRecCount INT,
Extents INT,
ExtentSwitches INT,
AvgFreeBytes INT,
AvgPageDensity INT,
ScanDensity decimal,
BestCount INT,
ActualCount INT,
LogicalFrag decimal,
ExtentFrag decimal)
– Open the cursor.
OPEN tables
– Loop through all the tables in the database.
FETCH NEXT
FROM tables
INTO @tablename
WHILE @@FETCH_STATUS = 0
BEGIN
– Do the showcontig of all indexes of the table
INSERT INTO #fraglist
EXEC (’DBCC SHOWCONTIG (”’ + @tablename + ”’)
WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS’)
FETCH NEXT
FROM tables
INTO @tablename
END
– Close and deallocate the cursor.
CLOSE tables
DEALLOCATE tables
– Declare the cursor for the list of indexes to be defragged.
DECLARE indexes CURSOR FOR
SELECT ObjectName, ObjectId, IndexId, LogicalFrag, IndexName
FROM #fraglist
WHERE ((LogicalFrag >= @maxreorg) OR (LogicalFrag >= @maxrebuild))
AND INDEXPROPERTY (ObjectId, IndexName, ‘IndexDepth’) > 0
– Open the cursor.
OPEN indexes
– Loop through the indexes.
FETCH NEXT
FROM indexes
INTO @tablename, @objectid, @indexid, @frag, @IdxName
WHILE @@FETCH_STATUS = 0
BEGIN
IF (@frag >= @maxrebuild)
BEGIN
IF (@ViewOnly=1)
BEGIN
PRINT ‘WOULD be executing ALTER INDEX ‘ + RTRIM(@IdxName) + ‘ ON ‘ + RTRIM(@tablename) + ‘ REBUILD WITH ( ‘ + @RebuildOptions + ‘ ) — Fragmentation currently ‘ + RTRIM(CONVERT(VARCHAR(15),@frag)) + ‘%’
END
ELSE
BEGIN
PRINT ‘Now executing ALTER INDEX ‘ + RTRIM(@IdxName) + ‘ ON ‘ + RTRIM(@tablename) + ‘ REBUILD WITH ( ‘ + @RebuildOptions + ‘ ) — Fragmentation currently ‘ + RTRIM(CONVERT(VARCHAR(15),@frag)) + ‘%’
SELECT @execstr = ‘ALTER INDEX ‘ + RTRIM(@IdxName) + ‘ ON ‘ + RTRIM(@tablename) + ‘ REBUILD WITH ( ‘ + @RebuildOptions + ‘ )’
EXEC (@execstr)
END
END
ELSE IF (@frag >= @maxreorg)
BEGIN
IF (@ViewOnly=1)
BEGIN
PRINT ‘WOULD be executing ALTER INDEX ‘ + RTRIM(@IdxName) + ‘ ON ‘ + RTRIM(@tablename) + ‘ REORGANIZE WITH ( ‘ + @ReorgOptions + ‘ ) — Fragmentation currently ‘ + RTRIM(CONVERT(VARCHAR(15),@frag)) + ‘%’
END
ELSE
BEGIN
PRINT ‘Now executing ALTER INDEX ‘ + RTRIM(@IdxName) + ‘ ON ‘ + RTRIM(@tablename) + ‘ REORGANIZE WITH ( ‘ + @ReorgOptions + ‘ ) — Fragmentation currently ‘ + RTRIM(CONVERT(VARCHAR(15),@frag)) + ‘%’
SELECT @execstr = ‘ALTER INDEX ‘ + RTRIM(@IdxName) + ‘ ON ‘ + RTRIM(@tablename) + ‘ REORGANIZE WITH ( ‘ + @ReorgOptions + ‘ )’
EXEC (@execstr)
END
END
FETCH NEXT
FROM indexes
INTO @tablename, @objectid, @indexid, @frag, @IdxName
END
– Close and deallocate the cursor.
CLOSE indexes
DEALLOCATE indexes
– Delete the temporary table.
DROP TABLE #fraglist
GO
Hello Pinal Dave,
Thank for the great topic.
A few requests -
1. Please reformat the scripts in this post to show indentation and colorization, as in your original script, and correct the single- and double-quotes to be accepted by SQL when pasting the scripts into SMS or QA.
2. Please explain why, after running the script with SET @ViewOnly=0 the indexes did not appear to defragment (running the script afterward with SET @ViewOnly=1 showed the same exact frag level).
Thanks!
WCM
Hello,
Perhaps the following post will lend clarity:
http://blog.sqlauthority.com/2007/06/24/sql-server-comparison-index-fragmentation-index-de-fragmentation-index-rebuild-sql-server-2000-and-sql-server-2005/
Kind Regards,
Paul