Index Statistics are important for query optimizer to generate optimal query plans. If indexes have outdated or stale statistics, your query’s performance will suffer. It is important to update the statistics to make sure query optimizer always generates the best and optimal plan.

This script allows you to report and update outdated statistics.

In reporting mode, this script provides the following information:

1. Name of the Database
2. Table Name
3. Index Name
4. Last date when Statistics was Updated
5. Row Count of the Table
6. Number of Rows Changed
7. Percentage of Row Changes Occurred Since Last time Statistics were Updated
8. Script for you to manually execute Update Statistics on individual objects

Create Procedure [dbo].[SQLOPS_UpdateStatistics] (@cmd Varchar(10)=null,@DBName Varchar(255)=null,@Update Varchar(5)='NoFix') as
SET NOCOUNT ON
--Author: Saleem Hakani (Http://sqlcommunity.com)
IF (@cmd is null or @cmd <> 'Execute' or @DBName is null)
Begin
	Print 'SQLOPS_UpdateStatistics (Author: Saleem Hakani)'
	Print '=========================================='
	Print ' '
	Print 'This procedure allows you to report and/or update outdated/stale statistics for individual indexes,'
	Print 'for all indexes in a databases and for all indexes across all user databases. When generating the report, it provides:'
	Print '1. Name of the Database'
	Print '2. Table Name'
	Print '3. Index Name'
	Print '4. Last date when Statistics was Updated'
	Print '5. Row Count of the Table'
	Print '6. Number of Rows Changed'
	Print '7. Percentage of Row Changes Occurred Since Last time Statistics were Updated'
	Print '8. Script for you to manually execute Update Statistics on individual objects'
	Print ' '
	Print 'Syntax: SQLOPS_UpdateStatistics <@Cmd>, <@DBName>, <@Update>'
	Print '@Cmd: Can be EXECUTE (To execute this SPROC) or HELP (To obtain this Help)'
	Print '@DBName: Can be the name of an individual database or ''ALL'' for all databases'
	Print '@Update: Can be ''Fix'' (to Update Statistics) or ''NoFix'' (Just provide the Report)'
	Print ' '
	Print 'To Get a list of outdated statistics from SQLOPS database:'
	Print 'Exec SQLOPS_UpdateStatistics ''Execute'',''SQLOPS'''
	Print ' '
	Print 'To Get a List and to fix Outdated Statistics from SQLOPS database:'
	Print 'Exec SQLOPS_UpdateStatistics ''EXECUTE'',''SQLOPS'',''Fix'''
	Print ' '
	Print 'To Get a List of Outdated Statistics from all User Databases:'
	Print 'Exec SQLOPS_UpdateStatistics ''Execute'',''ALL'''
	Print ' '
	Print 'To Get a list and to fix Outdated Statistics for all User Databases'
	Print 'Exec SQLOPS_UpdateStatistics ''Execute'',''ALL'', ''Fix'''
	Return
End
 
Declare
    @DBNameALL Varchar(255),
    @SQLStr1 NVarchar(4000),
    @SQLStr NVarchar(4000),
    @ExedbSTR NVARCHAR(4000)
 
Declare @DBStats Table
  (
     DBName Varchar(255),
     TableName Varchar(255),
     IndexName Varchar(255),
     Statistics_Last_Update Varchar(30),
     ROW_COUNT BigInt,
     ROWS_CHANGED BIGINT,
     Percent_Changed Decimal(16,2),
     Script Varchar(2000)
  )
If (@cmd ='Execute') and (@DBName <> 'ALL')
Begin
   IF Not Exists (Select Name from Sys.Databases where Name=@DBName)
      Begin
          Print 'Database ['+Upper(@DBName)+'] Does Not Exist in the System. Please Re-Enter a Valid Database Name'
          Return
      End
   Set @SQLStr='Use ['+@DBName+']; 
   SELECT db_Name() as DB_Name
   ,SYSSCH.name+''.''+SYSTAB.name AS TableName
   ,SYSIND.name AS IndexName
   ,ISNULL(Convert(Varchar,STATS_DATE(SYSIND.id,SYSIND.indid)),''DATE NOT FOUND'') AS ''Statistics_Last_Updated''
   ,SYSIND.rowcnt AS ''RowCount''
   ,SYSIND.rowmodctr AS ''Rows_Changed''
   ,CAST((CAST(SYSIND.rowmodctr AS DECIMAL(28,8))/CAST(SYSIND.rowcnt AS DECIMAL(28,2)) * 100.0) AS DECIMAL(28,2)) AS ''%_Changed''
   ,''USE [''+DB_Name()+'']; Update Statistics ''+SYSSCH.NAME+''.''+SYSTAB.Name+'' [''+SYSIND.name+''];'' as ''Script''
      FROM sys.sysindexes SYSIND
         INNER JOIN sys.tables SYSTAB ON SYSTAB.[object_id] = SYSIND.[id]
         INNER JOIN sys.schemas SYSSCH ON SYSSCH.[schema_id] = SYSTAB.[schema_id]
     WHERE SYSIND.id > 100
       AND SYSIND.indid > 0
       AND SYSIND.rowcnt >= 500
       AND SYSIND.rowmodctr > 0
    ORDER BY 7 DESC'
   Insert into @DBStats Exec SP_ExecuteSQL @SQLSTR
End
 
--If All Databases
If (@cmd='Execute' and @DBName = 'ALL')
Begin
   Declare CheckDB Cursor for Select Name from Sys.Databases where Name not in ('Master','Model','MSDB','TempDB') and State=0
   Open CheckDB
   Fetch Next from CheckDB into @DBNameALL
   While (@@FETCH_STATUS=0)
   Begin
      Set @SQLStr1='Use ['+@DBNameALL+']; SELECT db_Name() as DB_Name
      ,SYSSCH.name+''.''+SYSTAB.name AS TableName
      ,SYSIND.name AS IndexName 
      ,ISNULL(Convert(Varchar,STATS_DATE(SYSIND.id,SYSIND.indid)),''DATE NOT FOUND'') AS ''Statistics_Last_Updated''
      ,SYSIND.rowcnt AS ''RowCount''
      ,SYSIND.rowmodctr AS ''Rows_Changed''
      ,CAST((CAST(SYSIND.rowmodctr AS DECIMAL(28,8))/CAST(SYSIND.rowcnt AS DECIMAL(28,2)) * 100.0) AS DECIMAL(28,2)) AS ''%_Changed''
      ,''USE [''+DB_Name()+'']; Update Statistics ''+SYSSCH.NAME+''.''+SYSTAB.Name+'' [''+SYSIND.name+''];'' as ''Script''
      FROM sys.sysindexes SYSIND
         INNER JOIN sys.tables SYSTAB ON SYSTAB.[object_id] = SYSIND.[id]
         INNER JOIN sys.schemas SYSSCH ON SYSSCH.[schema_id] = SYSTAB.[schema_id]
      WHERE SYSIND.id > 100
         AND SYSIND.indid > 0
         AND SYSIND.rowcnt >= 500
         AND SYSIND.rowmodctr > 0
      ORDER BY 7 DESC'
   Insert into @DBStats Exec SP_ExecuteSQL @SQLSTR1
   Fetch Next from CheckDB into @DBNameALL
   End
   Close CheckDB
   Deallocate CheckDB
End
 
If (@Update='Fix')
Begin
   If (@DBName<>'ALL')
   Begin
      Declare CheckDBFix Cursor for Select Script from @DBStats
   End
 
   If (@DBName='ALL')
   Begin
      Declare CheckDBFix Cursor for Select Script from @DBStats
   End
 
   Open CheckDBFix
   Fetch Next from CheckDBFix into @ExedbSTR
 
   While (@@FETCH_STATUS=0)
   Begin
      Exec SP_ExecuteSQL @ExedbSTR
      Print 'Executed: '+@ExedbSTR
      Fetch Next from CheckDBFix into @ExedbSTR
   End
 
   Close CheckDBFix
   Deallocate CheckDBFix
End
 
If (@Update <> 'Fix')
Begin
   If (Select count(*) from @DBStats) >=1
   Begin
      Select * from @DBStats order by 7 DESC
      Return
   End
 
   If (Select count(*) from @DBStats) = 0
   Begin
      Print 'There are no statistics to be updated.'
      Return
   End
Return
End
GO