-- Used often in my experience with new data models

-- Helps get me up to speed much quicker.

use AdventureWorks2008

GO

DECLARE @table_schema sysname

DECLARE @table sysname

DECLARE @column sysname

DECLARE @datatype sysname

DECLARE @designed_length int

DECLARE @all_count int

DECLARE @sql nvarchar(4000)

DECLARE @origfillfactor varchar(10)

SETNOCOUNTON

--EXEC sp_updatestats

SETTRANSACTIONISOLATIONLEVELREADUNCOMMITTED-- Will speed things up a bit

CREATETABLE #table_info

(table_schema sysnameNOTNULL

,table_name sysnameNOTNULL

,column_name sysnameNOTNULL

,data_type sysnameNOTNULL

,origfillfactor varchar(10)null

,designed_length intNULL

,max_length intNULL

,distinct_count intNULL

,all_count intNOTNULL

,cardinality AS

CASE

WHEN distinct_count ISNULLTHENCAST(data_type ASvarchar(7))

WHEN all_count = 0 THENCAST('No rows'ASvarchar(7))

ELSECAST(

CAST(CAST(distinct_count ASdecimal)/CAST(all_count ASdecimal)ASdecimal(18,4))ASvarchar(7))

END

)

DECLARE c CURSORFAST_FORWARDFOR

SELECT

ist.table_schema,

isc.table_name,

isc.column_name,

isc.data_type,

si.OrigFillFactor,

COALESCE(isc.character_maximum_length, isc.numeric_precision),

si.rowcnt

--si.name as IndexName,

FROMinformation_schema.columns isc

INNERJOINinformation_schema.tables ist

ON isc.table_name = ist.table_name

INNERJOINsysindexes si

ON isc.table_name =OBJECT_NAME(si.id)

WHERE ist.table_type ='base table'

AND ist.table_name notlike'dt%'

AND si.indid IN(0,1)

ORDERBY ist.table_schema, isc.table_name, isc.column_name

OPEN c

FETCHNEXTFROM c INTO @table_schema, @table, @column, @datatype, @origfillfactor, @designed_length, @all_count

WHILE@@FETCH_STATUS= 0

BEGIN

IF @datatype IN('text','ntext','image','xml','geography')

BEGIN

SET @sql ='SELECT '''+ @table_schema +''', '''+ @table +''', '''+ @column +''', '''+ @datatype +''', '''+ @origfillfactor +''''

SET @sql = @sql +', '+CAST(@designed_length ASvarchar(10))+', MAX(DATALENGTH(['+ @column +']))'

SET @sql = @sql +', NULL'+', '+CAST(@all_count ASvarchar(10))+' FROM ['+ @table_schema +'].['+ @table +']'

END

ELSE

BEGIN

SET @sql ='SELECT '''+ @table_schema +''','''+ @table +''', '''+ @column +''', '''+ @datatype +''', '''+ @origfillfactor +''''

SET @sql = @sql +', '+CAST(@designed_length ASvarchar(10))+', MAX(LEN(CAST(['+ @column +'] AS VARCHAR(8000))))'

SET @sql = @sql +', COUNT(DISTINCT ['+ @column +'])'

SET @sql = @sql +', '+CAST(@all_count ASvarchar(10))+' FROM ['+ @table_schema +'].['+ @table +']'

END

PRINT @sql

INSERTINTO #table_info(table_schema, table_name, column_name, data_type, origfillfactor, designed_length, max_length, distinct_count, all_count)

EXEC(@sql)

FETCHNEXTFROM c INTO @table_schema, @table, @column, @datatype, @origfillfactor, @designed_length, @all_count

END

CLOSE c

DEALLOCATE c

--SELECT table_schema, table_name, column_name, data_type, origfillfactor, designed_length, max_length, distinct_count, all_count, cardinality

--FROM #table_info

selecttab.name as TableName, idx.name as IndexName, idx.fill_factor, idx.type_desc,

col.name as columnname, col.is_computed, idxc.is_included_column, ius.user_seeks, ius.user_scans, ius.user_lookups,

ius.user_updates, ius.last_user_seek, ius.last_user_scan, ius.last_user_lookup, ius.last_user_update, ius.last_system_update

INTO #index_info

FROMsys.tables tab

INNERJOINsys.indexes idx on tab.object_id= idx.object_id

INNERJOINsys.index_columns idxc ON idxc.index_id = idx.index_id and idxc.object_id= tab.object_id

INNERJOINsys.columns col ON col.column_id = idxc.column_id and col.object_id= tab.object_id

INNERJOINsys.dm_db_index_usage_stats ius ON idx.object_id= ius.object_idAND idxc.index_id = ius.index_id

SELECTdistinct a.table_schema, a.table_name, a.column_name, a.data_type, a.origfillfactor, a.designed_length, a.max_length,

a.distinct_count, a.all_count, a.cardinality

--, b.indexName, b.Fill_Factor, b.Type_desc, b.is_computed, b.is_included_column,

--b.user_seeks, b.user_scans, b.user_lookups, b.user_updates, b.last_user_seek, b.last_user_scan, b.last_user_lookup, b.last_user_update,

--b.last_system_update

FROM

#table_info a

--LEFT JOIN #index_info b ON a.table_name = b.tablename AND a.column_name = b.columnname

SETTRANSACTIONISOLATIONLEVELREADCOMMITTED

DROPTABLE #table_info

DROPTABLE #index_info

-- Let's look at these results:

-- Key results :

-- Person.Person.Firstname (1018), LastName (1206), MiddleName (70)

-- Context... Name often searchable.

-- Cardinality of only 5 and 6 percent; will yield poor selectivity

selectdistinctCount(*)as Quantity, lastname from Person.Person groupby LastName havingCOUNT(*) 2 orderby Quantity DESC

-- out of 20k people, 211 have last name of "Diaz". 1%? not statistically accurate

-- Likely closer to .001

-- result record #179; why have an nchar datatype with a designed_length of 1, a max length of one and a distinct count of 3?

-- let's look

selectdistinct transactiontype from Production.TransactionHistory

-- is that Nchar datatype truly required? why isn't it an int back to a reference table? etc...

-- back to slide deck. results / takeway

------

use AdventureWorks2008

GO

DECLARE @table_schema sysname

DECLARE @table sysname

DECLARE @column sysname

DECLARE @datatype sysname

DECLARE @designed_length int

DECLARE @all_count int

DECLARE @sql nvarchar(4000)

DECLARE @origfillfactor varchar(10)

SETNOCOUNTON

--EXEC sp_updatestats

SETTRANSACTIONISOLATIONLEVELREADUNCOMMITTED-- Will speed things up a bit

CREATETABLE #table_info

(table_schema sysnameNOTNULL

,table_name sysnameNOTNULL

,column_name sysnameNOTNULL

,data_type sysnameNOTNULL

,origfillfactor varchar(10)null

,designed_length intNULL

,max_length intNULL

,distinct_count intNULL

,all_count intNOTNULL

,cardinality AS

CASE

WHEN distinct_count ISNULLTHENCAST(data_type ASvarchar(7))

WHEN all_count = 0 THENCAST('No rows'ASvarchar(7))

ELSECAST(

CAST(CAST(distinct_count ASdecimal)/CAST(all_count ASdecimal)ASdecimal(18,4))ASvarchar(7))

END

)

DECLARE c CURSORFAST_FORWARDFOR

SELECT

ist.table_schema,

isc.table_name,

isc.column_name,

isc.data_type,

si.OrigFillFactor,

COALESCE(isc.character_maximum_length, isc.numeric_precision),

si.rowcnt

--si.name as IndexName,

FROMinformation_schema.columns isc

INNERJOINinformation_schema.tables ist

ON isc.table_name = ist.table_name

INNERJOINsysindexes si

ON isc.table_name =OBJECT_NAME(si.id)

WHERE ist.table_type ='base table'

AND ist.table_name notlike'dt%'

AND si.indid IN(0,1)

ORDERBY ist.table_schema, isc.table_name, isc.column_name

OPEN c

FETCHNEXTFROM c INTO @table_schema, @table, @column, @datatype, @origfillfactor, @designed_length, @all_count

WHILE@@FETCH_STATUS= 0

BEGIN

IF @datatype IN('text','ntext','image','xml','geography')

BEGIN

SET @sql ='SELECT '''+ @table_schema +''', '''+ @table +''', '''+ @column +''', '''+ @datatype +''', '''+ @origfillfactor +''''

SET @sql = @sql +', '+CAST(@designed_length ASvarchar(10))+', MAX(DATALENGTH(['+ @column +']))'

SET @sql = @sql +', NULL'+', '+CAST(@all_count ASvarchar(10))+' FROM ['+ @table_schema +'].['+ @table +']'

END

ELSE

BEGIN

SET @sql ='SELECT '''+ @table_schema +''','''+ @table +''', '''+ @column +''', '''+ @datatype +''', '''+ @origfillfactor +''''

SET @sql = @sql +', '+CAST(@designed_length ASvarchar(10))+', MAX(LEN(CAST(['+ @column +'] AS VARCHAR(8000))))'

SET @sql = @sql +', COUNT(DISTINCT ['+ @column +'])'

SET @sql = @sql +', '+CAST(@all_count ASvarchar(10))+' FROM ['+ @table_schema +'].['+ @table +']'

END

PRINT @sql

INSERTINTO #table_info(table_schema, table_name, column_name, data_type, origfillfactor, designed_length, max_length, distinct_count, all_count)

EXEC(@sql)

FETCHNEXTFROM c INTO @table_schema, @table, @column, @datatype, @origfillfactor, @designed_length, @all_count

END

CLOSE c

DEALLOCATE c

--SELECT table_schema, table_name, column_name, data_type, origfillfactor, designed_length, max_length, distinct_count, all_count, cardinality

--FROM #table_info

selecttab.name as TableName, idx.name as IndexName, idx.fill_factor, idx.type_desc,

col.name as columnname, col.is_computed, idxc.is_included_column, ius.user_seeks, ius.user_scans, ius.user_lookups,

ius.user_updates, ius.last_user_seek, ius.last_user_scan, ius.last_user_lookup, ius.last_user_update, ius.last_system_update

INTO #index_info

FROMsys.tables tab

INNERJOINsys.indexes idx on tab.object_id= idx.object_id

INNERJOINsys.index_columns idxc ON idxc.index_id = idx.index_id and idxc.object_id= tab.object_id

INNERJOINsys.columns col ON col.column_id = idxc.column_id and col.object_id= tab.object_id

INNERJOINsys.dm_db_index_usage_stats ius ON idx.object_id= ius.object_idAND idxc.index_id = ius.index_id

SELECTdistinct a.table_schema, a.table_name, a.column_name, a.data_type, a.origfillfactor, a.designed_length, a.max_length,

a.distinct_count, a.all_count, a.cardinality

, b.indexName, b.Fill_Factor, b.Type_desc, b.is_computed, b.is_included_column,

b.user_seeks, b.user_scans, b.user_lookups, b.user_updates, b.last_user_seek, b.last_user_scan, b.last_user_lookup, b.last_user_update,

b.last_system_update

FROM

#table_info a

LEFTJOIN #index_info b ON a.table_name = b.tablename AND a.column_name = b.columnname

SETTRANSACTIONISOLATIONLEVELREADCOMMITTED

DROPTABLE #table_info

DROPTABLE #index_info

-- Results / Take-away

-- Let's look at these results:

-- Key results :

-- Person.Person.Firstname IX_Person_LastName_FirstName_MiddleName

-- Context... Name often searchable.

-- Cardinality of only 5 and 6 percent; will yield poor selectivity

-- PER dm_db_index_usage_stats DMV, this index yields user_scans, not the expected seeks

-- Stats from an existing client show this way for their "Customer" table::

-- Distinctallcard..IDX_NameFFType Seek Scan

-- 50586849618520.1020nci2_LastnameFirstName90NONCLUSTERED 19090 634

-- result record #200; transactiontype from Production.TransactionHistory

-- Seems odd to me that the transactiontype is not utilized in an index with some other columns... like actual cost perhaps?

-- let's look at the procs in the db to see if there are any....

-- select * from sysobjects where id in (select id from syscomments where text like '%transactiontype%')

-- there aren't any; but reporting would probably utilize this for cross sections of transaction types.

-- result record #237; PurchaseOrderDetailID -- Clustered Index. Scans, not seeks.

-- perhaps if the PK wasn't clustered the cardinality of PurchaseorderdetailID would provide seeks in the clustered index instead of scans.

-- back to slide deck

------

use tempdb

-- create a table with 2000 rows. 1000 of them have the values 1 to 1000 each once (no

-- duplicates). Then we have 1000 rows with the value 5000.

--drop table t

createtable t(col1 int)

declare @i int

set @i = 0

while @i 1000

begin

insertinto t(col1)values (@i)

set @i = @i + 1

end

set @i = 0

while @i 1000

begin

insertinto t(col1)values (5000)

set @i = @i + 1

end

select*from t

selectcount(*),col1 from t

groupby col1 havingCOUNT(*) 1

-- Let's create some fullscan statistics on the column in our table

createstatistics t_col1 on t(col1)withfullscan

-- compile with no value to sniff.

dbcc freeproccache

declare @p int

select*from t where col1 = @p

-- (look at the output plan to see the estimate)

-- estimated # of rows = 2... 2?

-- same scenario but set a value. The value 5000 has 1000 instances, but we estimate 2 rows.

-- Why? Well, we compile the batch before we execute it, so the optimizer in 2005 does not see

-- the parameter value and we treat this the same as the previous case because it hasn’t been

-- actually set when the query is compiled

dbcc freeproccache

declare @p int

set @p = 5000

select*from t where col1 = @p

-- Let's use the option recompile as a workaround.

-- The first optimization has the same problem as before - estimates 2 rows

dbcc freeproccache

declare @p int

set @p = 1

select*from t where col1 = @p

option(recompile)

-- Another (better) workaround is to use the new optimize for hint - it avoids the recompile

-- and we estimate 1 row

dbcc freeproccache

declare @p int

set @p = 1

select*from t where col1 = @p

option (optimize for (@p = 1))

-- last workaround - use a stored procedure. This will create a new context in the

-- server and lets the optimizer "see" the parameter and sniff it during compilation.

createprocedure foo(@p int)

as

select*from t where col1 = @p

-- compile and examine the plan for this - estimates 1 row instead of 2

dbcc freeproccache

execute foo1

--back to slide deck

------

USE AdventureWorks2008

GO

CREATEPROCEDURE MyProc( @d datetime)

AS

SELECTCOUNT(*)

FROM Sales.SalesOrderHeader

WHERE OrderDate @d+1

GO

exec myproc'1/1/06'-- est. # of rows 1

USE AdventureWorks2008

GO

CREATEPROCEDURE MyProc2( @d datetime)

AS

BEGIN

-- creating a local variable... value unknown, defaults, in this case to 30% selectivity.

DECLARE @d2 datetime

SET @d2 = @d+1

SELECTCOUNT(*)

FROM Sales.SalesOrderHeader

WHERE OrderDate @d2

END

exec myproc2'1/1/06'-- est. # of rows 9439