SQL: Retrieve a list of Table Indexes

SQL Server contains several Catalog Views that return a wide range of Database Engine data. The Object Views can be used to list, among other things, structural information on database objects.  The following example creates a list of table indexes for SQL Server databases.

-- ##################################################
-- get a list of table indexes
-- ##################################################
 
SELECT 
	ao.object_id AS table_id
	,ao.name AS table_name
	,i.index_id
	,i.is_primary_key
	,i.name AS index_name
FROM
	-- sys.tables can be used to retrieve a list of tables of type 'U'
	sys.all_objects ao LEFT JOIN sys.indexes i 
		ON (ao.object_id = i.object_id)
WHERE 
	(ao.type = N'U')

-- ##################################################



   

SQL: DBCC CHECKTABLE on multiple tables

Part of decent SQL database maintenence is periodically checking the integrity, both logical and physical, of database objects. SQL Server has the DBCC CHECKDB command that will check the integrity of the objects and report back the results. DBCC CHECKDB runs CHECKALLOC, CHECKTABLE and CHECKCATALOG on the specified database.



DBCC CHECKTABLE allows you to check the integrity of a specified table or indexed view. If there are a large number of tables to check, the task of running the command for each tablename could be time consuming. Creating a script that loops through a set of database tables and runs DBCC CHECKTABLE for each, is a bit easier.

The sys.objects contains each of the user-defined schema objects in a database. Rows that are of [Type] = 'U' are tables. This makes it easy to get all the tables within a database (of course other filters can be applied). The following script will loop through and run the DBCC CHECKTABLE command for each table in the database (the command can be easily replaced to perform another action on each returned table):

-- set options
SET NOCOUNT ON;
SET ANSI_PADDING ON;
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;

-- declare table to store table names
-- in SQL 2005 you can use table variables
-- if you do not need or want to store the data you can store it in a table
-- variable instead of a temporary table
DECLARE @tablenames TABLE (
	TableName VARCHAR(255)
	);

-- populate table with tablenames
INSERT INTO @tablenames
	SELECT
		name
	FROM
	sys.objects
	-- additional filters can be set to narrow table selection 
	WHERE
		([type] = N'U');

-- Declare a cursor to loop through table
DECLARE tablenames INSENSITIVE SCROLL CURSOR
FOR
	SELECT
		TableName
	FROM
		@tablenames;

-- Open the cursor.
OPEN tablenames

-- declare variables
DECLARE @curtablename AS VARCHAR(255);
DECLARE @cmd AS VARCHAR(1024);

-- Loop through all the tables
WHILE (1=1)
	BEGIN
		FETCH NEXT FROM tablenames INTO @curtablename;
		IF @@fetch_status <> 0 BREAK;
		-- run the command
		SET @cmd = 'DBCC CHECKTABLE ([' + rtrim(@curtablename) + '])';
		EXEC (@cmd)
		PRINT @cmd + CHAR(10) + CHAR(13);
	END

-- Close cursor
CLOSE tablenames;
DEALLOCATE tablenames;



   

SQL: Index Fragmentation Maintenance

In a previous post, I discussed SQL Server's built in ability for suggesting missing indexes. As records are inserted, updated and deleted from tables indexes can become fragmented. A fragmented index affects performance (table scans and index seeks) when the DBMS needs to search for records.

This probably leaves the question, "How can one defragment the indexes of SQL tables?"

Within SQL Server 2005 and greater you can setup a maintenance plan to defragment all the indexes within all or a particular database. You can also right click on an index and select to either Rebuild or Reorganize and index (rebuilding will re-create the index whereas reorganize reorders the index pages). I prefer to have a little more control and use the following script as part of my maintenance plan. This script allows for a little more control in the selecting of tables, indexes and databases to maintain. I tend to rebuild indexes that are more than 30% fragmented and reorganize those that are greater than 5%.

SQL Server 2005 (and greater) has index information more readily accessible. The key to this script is the index information that is contained in sys.dm_db_index_physical_stats. This table returns the size and fragmentation information for each index.

-- ########################################
-- Create table to hold index information
SET NOCOUNT ON;

CREATE TABLE _IndexInformation (
	database_id SMALLINT
	,ObjectName varchar(255)
	,[object_id] int
	,IndexName varchar(255)
	,[index_id] int
	,index_level int
	,index_type_desc varchar(60)
	,avg_fragmentation_in_percent float
	,fragment_count float
	,page_count bigint
	,record_count bigint
	,index_depth int
);
-- ########################################

-- ########################################
-- clear table contents
TRUNCATE TABLE _IndexInformation;

-- populate table
INSERT INTO _IndexInformation
	SELECT
		database_id
		,object_name(i.object_id) AS [ObjectName]
		,i.[object_id]
		,i.name AS [IndexName]
		,dm.index_id
		,dm.index_level
		,dm.index_type_desc
		,dm.avg_fragmentation_in_percent
		,dm.fragment_count
		,dm.page_count
		,dm.record_count
		,dm.index_depth
	FROM
	-- database, table and index information can be specified
	/*
	sys.dm_db_index_physical_stats (
		{ database_id NULL 0 DEFAULT }
		,{ object_id NULL 0 DEFAULT }
		,{ index_id NULL 0 -1 DEFAULT }
		,{ partition_number NULL 0 DEFAULT }
		,{ mode NULL DEFAULT }
	)
	This example uses database 1; you should replace this with the ID of the database you wish to process
	*/
	sys.dm_db_index_physical_stats(1,null,null,null,'detailed') dm INNER JOIN sys.indexes i 
		ON (i.object_id = dm.object_id) AND (i.index_id = dm.index_id);
-- ########################################


-- ########################################
-- read index table and maintain indexes
SET NOCOUNT ON;
SET ANSI_PADDING ON;
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;


-- Declare variables.
DECLARE @tablename AS varchar(255)
DECLARE @indexname AS varchar(255)
DECLARE @frag AS float
DECLARE @cmd AS varchar(1024)

-- Declare a cursor.
DECLARE indexes INSENSITIVE SCROLL CURSOR
	FOR
		SELECT
			ObjectName,
			IndexName,
			avg_fragmentation_in_percent
		FROM
			_IndexInformation
		WHERE
			(avg_fragmentation_in_percent >= 5);

-- Open the cursor.
OPEN indexes

-- Loop through all the indexes.
WHILE (1=1)
	BEGIN
		FETCH NEXT FROM indexes INTO @tablename, @indexname, @frag;
		IF @@fetch_status <> 0 BREAK;
		-- if fragmentation is above 30% rebuild index
		-- if fragmentation is above 5% reorganize index
		IF (@frag >= 30)
			BEGIN
				SET @cmd = 'ALTER INDEX [' + rtrim(@indexname) + '] ON [' + @tablename +'] REBUILD;'
				EXEC (@cmd)
				PRINT @cmd;
			END
		ELSE IF (@frag >= 5)
			BEGIN
				SET @cmd = 'ALTER INDEX [' + rtrim(@indexname) + '] ON [' + @tablename +'] REORGANIZE;'
				EXEC (@cmd)
				PRINT @cmd;
			END;
		END

-- Close cursor
CLOSE indexes;
DEALLOCATE indexes;
-- ########################################


-- ########################################
-- delete index table
DROP TABLE _IndexInformation;
-- ########################################


I have the script schedule to as part of my database maintenance plan. I run the script several times with additional selection criteria limiting it to groups of tables in a particular database.