1 -- Identify "identity" columns nearing the maximum threshold
2
3 --Points to note:
4 --Observe the output columns [sample%], modCtr.
5 --I use these to decide if I "trust" the current statistics. If I don't, I run the
generated [UpdateStats] command.
6
7 --Observe the JOIN to sys.columns ON ISNULL(ic.column_id, sc.column_id.
8 --With multi-column indexes, it is quite rare for sys.stats_columns.stats_column_id
to not match sys.index_columns.key_ordinal.
9 --Unfortunately I have observed this mis-match in a few tables across the 4,000+
databases I manage.
10 --I have no idea what causes it and was unsuccessful in reproducing the issue.
11 --Bottom line, for indexed columns I use sys.index_columns.column_id where
key_ordinal = 1, to join to sys.columns.
12
13 --For columns having a NULL value in every row, sys.dm_db_stats_histogram returns
zero rows; DBCC SHOW_STATISTICS returns one row.
14 --For tables with zero rows, sys.dm_db_stats_properties and sys.dm_db_stats_histogram
return zero rows.
15
16 --I use CMS to execute the script across the entire fleet of 200+ SQL Server
instances,
17 --using sys.fn_hadr_is_primary_replica() to target databases on Primary replicas
only.
18 --For Azure SQL DB, execute the SELECT part of the script, replacing two single
quotes ('') with one (').
19
20 --Naturally, you need adequate permissions to execute the functions and DMVs used
within the script.
21
22 --Addressing the Identity problem: This blog post, I stumbled across, has some ideas.
23 --https://medium.com/@distillerytech/make-more-room-how-to-resolve-database-record-lim
itations-146035f3492e
24
25 --Query: Identify "identity" columns nearing the max threshold, across all databases
26 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
27
28 drop table IF EXISTS #dbStats
29 GO
30 CREATE TABLE #dbStats (
31 [Database] [sysname] NOT NULL,
32 [Table] [sysname] NULL,
33 [Column] [sysname] NULL,
34 [Statistic] [sysname] NULL,
35 [Cardinality] BIGINT NULL,
36 [sample%] NUMERIC(5,2) NULL,
37 [modCtr] [bigint] NULL,
38 [lastStatsUpdate] DATETIME2(3) NULL,
39 [dataType] [sysname] NULL,
40 [max_length] [smallint] NULL,
41 [maxValue] [sql_variant] NULL,
42 [distinctValues] [bigint] NULL,
43 [max%] NUMERIC(7,4) NULL
44 )
45 GO
46
47 DECLARE @command varchar(4096)
48 SELECT @command = 'IF ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'')
49 BEGIN
50 USE [?];
51 declare @isPrimary BIT = ISNULL(sys.fn_hadr_is_primary_replica (db_name()), 1)
52 -- ,@msg varchar(128) = db_name()+ '' at '' + convert(varchar(24), getdate(),
121);
53 --RAISERROR(@msg, 0, 1) WITH NOWAIT
54
55 INSERT INTO #dbStats
56 select [database]=DB_NAME(),
[table]=object_schema_name(ss.object_id)+''.''+object_name(ss.object_id)
57 ,c.name as columnName, ss.name as [Statistic]
58 ,CAST(OBJECTPROPERTYEX(ss.object_id, ''Cardinality'') AS BIGINT) AS Cardinality
59 ,[sample%]=cast(sp.rows_sampled*100./sp.rows as NUMERIC(5,2))
,modCtr=sp.modification_counter, CAST(sp.last_updated AS DATETIME2(3)) as
lastStatsUpdate
60 ,TYPE_NAME(c.user_type_id) AS [dataType], c.max_length
61 ,ssh.maxValue ,ssh.distinctValues
62 ,CAST(CAST(maxValue AS BIGINT)*(100.0/CASE max_length WHEN 1 THEN 255 WHEN 2 THEN
32767 WHEN 4 THEN 2147483647 WHEN 8 THEN 9223372036854775807 END) AS NUMERIC(7,4)) as
[max%]
63 FROM sys.stats ss
64 CROSS APPLY sys.dm_db_stats_properties(ss.object_id, ss.stats_id) AS sp
65 INNER JOIN sys.stats_columns sc ON sc.object_id = ss.object_id AND sc.stats_id =
ss.stats_id and sc.stats_column_id = 1 -- For indexes, the 1st column only
66 LEFT JOIN (sys.indexes i
67 INNER JOIN sys.index_columns ic on ic.object_id = i.object_id AND ic.index_id =
i.index_id and ic.key_ordinal = 1 -- For indexes, the 1st column only
68 ) on i.object_id = ss.object_id AND i.index_id = ss.stats_id
69 INNER JOIN sys.columns c ON c.object_id = sc.object_id AND c.column_id =
ISNULL(ic.column_id, sc.column_id) -- where stats_column_id does not match index
key_ordinal, histogram is built on the 1st index column
70 and c.is_identity = 1
71 OUTER APPLY (select maxValue=MAX(sh.range_high_key),
distinctValues=(COUNT(*)+SUM(sh.distinct_range_rows))
72 from sys.dm_db_stats_histogram(ss.object_id, ss.stats_id) sh
73 ) ssh
74 WHERE OBJECTPROPERTY(ss.object_id, ''IsMSShipped'') = 0
75 AND @isPrimary = 1
76 END'
77
78 EXEC sp_MSforeachdb @command
79
80 select *
81 ,UpdateStats = 'UPDATE STATISTICS '+[Table]+' '+QUOTENAME([Statistic])+' WITH
FULLSCAN; -- ,PERSIST_SAMPLE_PERCENT=ON; --'+[Column]
82 from #dbStats
83 --WHERE [max%] > 75 --< Uncomment & EDIT as you see fit
84 order by [database], maxValue
85
86 /*
87
88 Data Profiling examples you can explore:
89 distinctValues=(COUNT(*)+SUM(distinct_range_rows))
90 ,maxValue=MAX(range_high_key)
91 ,minValue=MIN(range_high_key)
92 ,[nulls]=MAX(IIF(range_high_key IS NULL, equal_rows, 0))
93 ,[empty]=MAX(IIF(range_high_key='', equal_rows, 0))
94 ,[zeros]=MAX(IIF(range_high_key=0, equal_rows, 0))
95 */