@@ -146,6 +146,10 @@ IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL
146146IF OBJECT_ID (' tempdb..#DatabaseList' ) IS NOT NULL
147147 DROP TABLE #DatabaseList;
148148
149+ IF OBJECT_ID (' tempdb..#Statistics' ) IS NOT NULL
150+ DROP TABLE #Statistics ;
151+
152+
149153 RAISERROR (N ' Create temp tables.' ,0 ,1 ) WITH NOWAIT ;
150154 CREATE TABLE #BlitzIndexResults
151155 (
@@ -509,6 +513,27 @@ IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL
509513 DatabaseName NVARCHAR (256 )
510514 )
511515
516+ CREATE TABLE #Statistics (
517+ database_name NVARCHAR (256 ) NOT NULL ,
518+ table_name NVARCHAR (128 ) NULL ,
519+ index_name sysname NOT NULL ,
520+ column_name sysname NOT NULL ,
521+ statistics_name NVARCHAR (128 ) NOT NULL ,
522+ last_statistics_update DATETIME NULL ,
523+ days_since_last_stats_update INT NULL ,
524+ rows BIGINT NULL ,
525+ rows_sampled BIGINT NULL ,
526+ percent_sampled DECIMAL (18 , 1 ) NULL ,
527+ histogram_steps INT NULL ,
528+ modification_counter BIGINT NULL ,
529+ percent_modifications DECIMAL (18 , 1 ) NULL ,
530+ modifications_before_auto_update INT NULL ,
531+ index_type_desc NVARCHAR (128 ) NOT NULL ,
532+ table_create_date DATETIME NULL ,
533+ table_modify_date DATETIME NULL
534+ );
535+
536+
512537IF @GetAllDatabases = 1
513538 BEGIN
514539 INSERT INTO #DatabaseList (DatabaseName)
@@ -1353,8 +1378,110 @@ BEGIN TRY
13531378 AS create_tsql
13541379 FROM #IndexSanity
13551380 WHERE database_id = @DatabaseID;
1356-
1357- END
1381+
1382+ IF ((PARSENAME (@SQLServerProductVersion, 4 ) >= 12 )
1383+ OR (PARSENAME (@SQLServerProductVersion, 4 ) = 11 AND PARSENAME (@SQLServerProductVersion, 2 ) >= 3000 )
1384+ OR (PARSENAME (@SQLServerProductVersion, 4 ) = 10 AND PARSENAME (@SQLServerProductVersion, 3 ) = 50 AND PARSENAME (@SQLServerProductVersion, 2 ) >= 2500 ))
1385+ BEGIN
1386+ RAISERROR (N ' Gathering Statistics Info With Newer Syntax.' ,0 ,1 ) WITH NOWAIT ;
1387+ SET @dsql= N' SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
1388+ SELECT ' + QUOTENAME (@DatabaseName,' '' ' ) + N' AS database_name,
1389+ OBJECT_NAME(s.object_id) AS table_name,
1390+ ISNULL(i.name, '' System Statistic'' ) AS index_name,
1391+ c.name AS column_name,
1392+ s.name AS statistics_name,
1393+ CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update,
1394+ DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update,
1395+ ddsp.rows,
1396+ ddsp.rows_sampled,
1397+ CAST(ddsp.rows_sampled / ( 1. * ddsp.rows ) * 100 AS DECIMAL(18, 1)) AS percent_sampled,
1398+ ddsp.steps AS histogram_steps,
1399+ ddsp.modification_counter,
1400+ CASE WHEN ddsp.modification_counter > 0
1401+ THEN CAST(ddsp.modification_counter / ( 1. * ddsp.rows ) * 100 AS DECIMAL(18, 1))
1402+ ELSE ddsp.modification_counter
1403+ END AS percent_modifications,
1404+ CASE WHEN ddsp.rows < 500 THEN 500
1405+ ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT)
1406+ END AS modifications_before_auto_update,
1407+ ISNULL(i.type_desc, '' System Statistic - N/A'' ) AS index_type_desc,
1408+ CONVERT(DATETIME, obj.create_date) AS table_create_date,
1409+ CONVERT(DATETIME, obj.modify_date) AS table_modify_date
1410+ FROM ' + QUOTENAME (@DatabaseName) + N' .sys.stats AS s
1411+ JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.stats_columns sc
1412+ ON sc.object_id = s.object_id
1413+ AND sc.stats_id = s.stats_id
1414+ JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.columns c
1415+ ON c.object_id = sc.object_id
1416+ AND c.column_id = sc.column_id
1417+ JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.objects obj
1418+ ON s.object_id = obj.object_id
1419+ LEFT JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.indexes AS i
1420+ ON i.object_id = s.object_id
1421+ AND i.index_id = s.stats_id
1422+ CROSS APPLY ' + QUOTENAME (@DatabaseName) + N' .sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp
1423+ WHERE obj.is_ms_shipped = 0;'
1424+
1425+ IF @dsql IS NULL
1426+ RAISERROR (' @dsql is null' ,16 ,1 );
1427+
1428+ RAISERROR (N ' Inserting data into #Statistics' ,0 ,1 ) WITH NOWAIT ;
1429+ INSERT #Statistics ( database_name , table_name, index_name, column_name, statistics_name, last_statistics_update,
1430+ days_since_last_stats_update, rows , rows_sampled, percent_sampled, histogram_steps , modification_counter,
1431+ percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date)
1432+
1433+ EXEC sp_executesql @dsql;
1434+
1435+ END
1436+ ELSE
1437+ BEGIN
1438+ RAISERROR (N ' Gathering Statistics Info With Older Syntax.' ,0 ,1 ) WITH NOWAIT ;
1439+ SET @dsql= N' SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
1440+ SELECT ' + QUOTENAME (@DatabaseName,' '' ' ) + N' AS DatabaseName,
1441+ OBJECT_NAME(s.object_id) AS table_name,
1442+ ISNULL(i.name, '' System Statistic'' ) AS index_name,
1443+ c.name AS column_name,
1444+ s.name AS statistics_name,
1445+ CONVERT(DATETIME, STATS_DATE(obj.object_id, i.index_id)) AS last_statistics_update,
1446+ DATEDIFF(DAY, STATS_DATE(obj.object_id, i.index_id), GETDATE()) AS days_since_last_stats_update,
1447+ si.rowcnt,
1448+ si.rowmodctr,
1449+ CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * si.rowcnt ) * 100 AS DECIMAL(18, 1))
1450+ ELSE si.rowmodctr
1451+ END AS percent_modifications,
1452+ CASE WHEN si.rowcnt < 500 THEN 500
1453+ ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT)
1454+ END AS modifications_before_auto_update,
1455+ ISNULL(i.type_desc, '' System Statistic - N/A'' ) AS index_type_desc,
1456+ CONVERT(DATETIME, obj.create_date) AS table_create_date,
1457+ CONVERT(DATETIME, obj.modify_date) AS table_modify_date
1458+ FROM ' + QUOTENAME (@DatabaseName) + N' .sys.stats AS s
1459+ JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.sysindexes si
1460+ ON si.name = s.name
1461+ JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.stats_columns sc
1462+ ON sc.object_id = s.object_id
1463+ AND sc.stats_id = s.stats_id
1464+ JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.columns c
1465+ ON c.object_id = sc.object_id
1466+ AND c.column_id = sc.column_id
1467+ JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.objects obj
1468+ ON s.object_id = obj.object_id
1469+ LEFT JOIN ' + QUOTENAME (@DatabaseName) + N' .sys.indexes AS i
1470+ ON i.object_id = s.object_id
1471+ AND i.index_id = s.stats_id
1472+ WHERE obj.is_ms_shipped = 0;'
1473+
1474+ IF @dsql IS NULL
1475+ RAISERROR (' @dsql is null' ,16 ,1 );
1476+
1477+ RAISERROR (N ' Inserting data into #Statistics' ,0 ,1 ) WITH NOWAIT ;
1478+ INSERT #Statistics (database_name , table_name, index_name, column_name, statistics_name,
1479+ last_statistics_update, days_since_last_stats_update, rows , modification_counter,
1480+ percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date)
1481+
1482+ EXEC sp_executesql @dsql;
1483+ END
1484+ END
13581485END TRY
13591486BEGIN CATCH
13601487 RAISERROR (N ' Failure populating temp tables.' , 0 ,1 ) WITH NOWAIT ;
@@ -2824,6 +2951,58 @@ BEGIN;
28242951
28252952
28262953 END
2954+
2955+ -- --------------------------------------
2956+ -- Statistics Info: Check_id 90-99
2957+ -- --------------------------------------
2958+ BEGIN
2959+
2960+ RAISERROR (N ' check_id 90: Outdated statistics' , 0 ,1 ) WITH NOWAIT ;
2961+ INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL , details, index_definition,
2962+ secret_columns, index_usage_summary, index_size_summary )
2963+ SELECT 90 AS check_id,
2964+ 200 AS Priority,
2965+ ' Functioning Statistaholics' AS findings_group,
2966+ ' Statistic Abandonment Issues' ,
2967+ s .database_name ,
2968+ ' ' AS URL ,
2969+ ' Statistics on this table were last updated ' +
2970+ CASE s .last_statistics_update WHEN NULL THEN N ' NEVER '
2971+ ELSE CONVERT (NVARCHAR (20 ), s .last_statistics_update ) +
2972+ ' have had ' + CONVERT (NVARCHAR (100 ), s .modification_counter ) +
2973+ ' modifications in that time, which is ' +
2974+ CONVERT (NVARCHAR (100 ), s .percent_modifications ) +
2975+ ' % of the table.'
2976+ END ,
2977+ QUOTENAME (database_name ) + ' .' + QUOTENAME (s .index_name ) + ' .' + QUOTENAME (s .statistics_name ) + ' .' + QUOTENAME (s .column_name ) AS index_definition,
2978+ ' N/A' AS secret_columns,
2979+ ' N/A' AS index_usage_summary,
2980+ ' N/A' AS index_size_summary
2981+ FROM #Statistics AS s
2982+ WHERE s .last_statistics_update <= CONVERT (DATETIME , GETDATE () - 7 )
2983+ AND s .percent_modifications >= 10 .
2984+ AND s .rows >= 10000
2985+
2986+ RAISERROR (N ' check_id 91: Statistics with a low sample rate' , 0 ,1 ) WITH NOWAIT ;
2987+ INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL , details, index_definition,
2988+ secret_columns, index_usage_summary, index_size_summary )
2989+ SELECT 91 AS check_id,
2990+ 200 AS Priority,
2991+ ' Functioning Statistaholics' AS findings_group,
2992+ ' Antisocial Samples' ,
2993+ s .database_name ,
2994+ ' ' AS URL ,
2995+ ' Only ' + CONVERT (NVARCHAR (100 ), s .percent_sampled ) + ' % of the rows were samplped during the last statistics update. This may lead to poor cardinality estimates.' ,
2996+ QUOTENAME (database_name ) + ' .' + QUOTENAME (s .index_name ) + ' .' + QUOTENAME (s .statistics_name ) + ' .' + QUOTENAME (s .column_name ) AS index_definition,
2997+ ' N/A' AS secret_columns,
2998+ ' N/A' AS index_usage_summary,
2999+ ' N/A' AS index_size_summary
3000+ FROM #Statistics AS s
3001+ WHERE s .rows_sampled < 1 .
3002+ AND s .rows >= 10000
3003+
3004+
3005+ END
28273006
28283007 RAISERROR (N ' Insert a row to help people find help' , 0 ,1 ) WITH NOWAIT ;
28293008 IF DATEDIFF (MM, @VersionDate, GETDATE ()) > 6
0 commit comments