How to Find Outdated Statistics in SQL Server 2008

By:   |   Comments (7)   |   Related: > Indexing


In the previous tip we learned that outdated statistics can cause SQL Server performance problems. My question is: How can I find out which statistics are outdated in my SQL Server database?


We can use the STATS_DATE (object_id, stats_id) function to check when the last update happened for the given statistics. The input is the ID of the table and the ID of the statistics. This function returns the last update date in datetime format. In the next section I will show how to collect data about the outdated statistics into a table.

First, we will create a table for the results:

--Create a table for the outdated statistics
CREATE TABLE Outdated_statistics
([Table name] sysname,
[Index name] sysname,
[Last updated] datetime NULL,
[Rows modified] int NULL)

Now we will query every statistics object which was not updated in the last day and has rows modified since the last update. We will use the rowmodctr field of sys.sysindexes because it shows how many rows were inserted, updated or deleted since the last update occurred. Please note that it is not always 100% accurate in SQL Server 2005 and later, but it can be used to check if any rows were modified.

--Get the list of outdated statistics
INSERT INTO Outdated_statistics
SELECT OBJECT_NAME(id),name,STATS_DATE(id, indid),rowmodctr
FROM sys.sysindexes
AND rowmodctr>0 
AND id IN (SELECT object_id FROM sys.tables)

After collecting this information, we can decide which statistics require an update.

If we are sure that all out dated statistics should be immediately updated, then we can write a cursor to update them:

--Set the thresholds when to consider the statistics outdated
DECLARE @hours int
DECLARE @modified_rows int
DECLARE @update_statement nvarchar(300);

SET @hours=24
SET @modified_rows=10

--Update all the outdated statistics
DECLARE statistics_cursor CURSOR FOR
FROM sys.sysindexes
AND rowmodctr>=@modified_rows 
AND id IN (SELECT object_id FROM sys.tables)
OPEN statistics_cursor;
FETCH NEXT FROM statistics_cursor INTO @update_statement;
  EXECUTE (@update_statement);
  PRINT @update_statement;
 FETCH NEXT FROM statistics_cursor INTO @update_statement;
 PRINT 'The outdated statistics have been updated.';
CLOSE statistics_cursor;
DEALLOCATE statistics_cursor;

As you can see, you can define the thresholds as to when to consider the statistics outdated. You can run this as an ad-hoc query or you can create a stored procedure and execute it with a scheduled job as needed. Either way, after you execute this query, the statistics should be up to date.

Next Steps

sql server categories

sql server webinars

subscribe to mssqltips

sql server tutorials

sql server white papers

next tip

About the author
MSSQLTips author Tibor Nagy Tibor Nagy is a SQL Server professional in the financial industry with experience in SQL 2000-2012, DB2 and MySQL.

This author pledges the content of this article is based on professional experience and not AI generated.

View all my tips

Comments For This Article

Friday, April 11, 2014 - 7:02:12 AM - Parker Back To Top (30043)

My goodness. This is outdated and a now deprecated methodology, yet was sent out in their newsletter today. Wild.

Tuesday, May 14, 2013 - 8:56:54 AM - Igor Micev Back To Top (23940)


In your code intended for forming the "UPDATE STATISTICS" strings you do not have the schema name. That would not work on databases with different schema from the default ('dbo').

You can make it as the following:

SELECT 'UPDATE STATISTICS '+schema_name(so.schema_id)+'.'+OBJECT_NAME(id)+' '
FROM sys.sysindexes ssi
join sys.tables st on
join sys.objects so on and st.object_id=so.object_id
AND rowmodctr > 0 




Sunday, September 9, 2012 - 11:36:31 PM - Pete Back To Top (19445)

sp_updatestats was introduced in SQL Server 2005 - does it all for you


To run it on all databases:




use [?];

print ''-----------------------'';

print ''begin updating statistics for [?]'';

execute sp_updatestats;

print ''end updating statistics for [?]'';



Thursday, August 9, 2012 - 7:08:01 AM - Tibor Nagy Back To Top (18973)

Hi Eddy,

Here is my tip about _WA_SYSxxx stats:


Friday, August 3, 2012 - 4:34:32 PM - Tibor Nagy Back To Top (18919)

Hi Eddy,

Thanks for the updated code.

The _WA_SYSxxx statistics are automatically created by the Query Optimizer for the columns which do not have existing statistics. It is an estimation which helps to choose between the possible Query Plans when the statistics do not exist. I already submitted a tip on this topic and it will be published soon :)



Friday, August 3, 2012 - 9:00:12 AM - Martyn Back To Top (18905)

As I understand, stats are created automatically (if enabled) to help with query plans. They can also be added after analysing trace data. They are used in query planning so just because there is no index doesn't mean it should go.... at least that's what I understand to be correct, feel free to correct me if I'm wrong.

I'd be interested to know how to check how/when the stats are used, similar to index useage. If stats exist but aren't used, presumably they can be cleared. Some code to provide this information, with a related drop statement, allowing confident and easy removal would be fantastic!




Friday, August 3, 2012 - 8:13:03 AM - Van Heghe Eddy Back To Top (18902)

Hi Tibor,

Nice post and was really helpfull to,
I have been taken the permission to alter your code a little bit so it would also serve for those who are using different 'schemas'  whereas your code would fail in those cases.

Altered code:
SELECT OBJECT_NAME(id) AS Tabel ,st.[name] AS Tnaam,,STATS_DATE(id, indid) AS DateLastUpdate,rowmodctr,st.*
FROM sys.sysindexes AS si INNER JOIN sys.[tables] AS st ON si.[id] = st.[object_id]
INNER JOIN sys.schemas AS ss ON st.[schema_id] = [ss].[schema_id]
WHERE STATS_DATE(id, indid)AND rowmodctr>10
AND st.[type] ='U' -- user tables only
ORDER BY [rowmodctr] DESC

And the new update statement for your cursor:
--Update all the outdated statistics
DECLARE statistics_cursor CURSOR FOR
SELECT 'UPDATE STATISTICS ' + ss.[name] +'.'+ OBJECT_NAME(id) + ' ' +
FROM sys.sysindexes AS si INNER JOIN sys.[tables] AS st ON si.[id] = st.[object_id]
INNER JOIN sys.schemas AS ss ON st.[schema_id] = [ss].[schema_id]
WHERE STATS_DATE(id, indid)AND rowmodctr>=@modified_rows
AND st.[type] ='U' -- user tables only

Still have 1 question for witch i don't seem to find an answer:
Some of my tables have multiple statistics al starting with ' _WAxxxxxx_SYS'
For example i have a table with 2 indexes (1 clustered PK, 1 non-clustered) and i find 12 statistics
Is this normal or could it be the left over from old indexes.?





get free sql tips
agree to terms