Compatible from SQL Server 2008
Get information about all existing indexes in given database. This script can be handful when you want to check all indexes with their create scripts and some of useful information about user statistics, basic configuration, etc. all of that in one table. You can process that data further in temp table and so on. Really handful and I’m using it almost in every performance investigation.
TIP: Use that if you want to recreate replication and you have some additional indexing on subscriber.
Script source: https://littlekendra.com/2016/05/05/how-to-script-out-indexes-from-sql-server/
SELECT
DB_NAME() AS database_name,
sc.name + N'.' + t.name AS table_name,
(
SELECT MAX(user_reads)
FROM (
VALUES (last_user_seek), (last_user_scan), (last_user_lookup)
) AS value(user_reads)
) AS last_user_read, last_user_update,
'IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name=''' + si.name + ''' AND object_id = OBJECT_ID(''' + sc.name + '.' + t.name + '''))' AS checking_statement, CASE si.index_id
WHEN 0
THEN N'/* No create statement (Heap) */'
ELSE
CASE is_primary_key
WHEN 1 THEN N'ALTER TABLE ' + QUOTENAME(sc.name) + N'.' + QUOTENAME(t.name) + N' ADD CONSTRAINT ' + QUOTENAME(si.name) + N' PRIMARY KEY ' + CASE
WHEN si.index_id > 1 THEN N'NON'
ELSE N''
END + N'CLUSTERED '
ELSE N'CREATE ' +
CASE
WHEN si.is_unique = 1 THEN N'UNIQUE '
ELSE N''
END +
CASE
WHEN si.index_id > 1 THEN N'NON'
ELSE N''
END +
N'CLUSTERED ' + N'INDEX ' + QUOTENAME(si.name) + N' ON ' + QUOTENAME(sc.name) + N'.' + QUOTENAME(t.name) + N' '
END +
/* key def */ N'(' + key_definition + N')' +
/* includes */
CASE
WHEN include_definition IS NOT NULL THEN N' INCLUDE (' + include_definition + N')'
ELSE N''
END +
/* filters */
CASE
WHEN filter_definition IS NOT NULL THEN N' WHERE ' + filter_definition
ELSE N''
END +
/* with clause - compression goes here */
CASE
WHEN row_compression_partition_list IS NOT NULL OR page_compression_partition_list IS NOT NULL THEN N' WITH (' +
CASE
WHEN row_compression_partition_list IS NOT NULL THEN N'DATA_COMPRESSION = ROW ' +
CASE
WHEN psc.name IS NULL THEN N''
ELSE + N' ON PARTITIONS (' + row_compression_partition_list + N')'
END
ELSE N''
END +
CASE
WHEN row_compression_partition_list IS NOT NULL AND page_compression_partition_list IS NOT NULL THEN N', '
ELSE N''
END +
CASE
WHEN page_compression_partition_list IS NOT NULL THEN N'DATA_COMPRESSION = PAGE ' +
CASE
WHEN psc.name IS NULL THEN N''
ELSE + N' ON PARTITIONS (' + page_compression_partition_list + N')'
END
ELSE N''
END + N')'
ELSE N''
END +
/* ON where? filegroup? partition scheme? */
' ON ' +
CASE
WHEN psc.name IS NULL THEN ISNULL(QUOTENAME(fg.name), N'')
ELSE psc.name + N' (' + partitioning_column.column_name + N')'
END + N';'
END AS index_create_statement,
si.index_id,
si.name AS index_name,
partition_sums.reserved_in_row_GB,
partition_sums.reserved_LOB_GB,
partition_sums.row_count,
stat.user_seeks,
stat.user_scans,
stat.user_lookups,
stat.user_updates,
partition_sums.partition_count,
si.allow_page_locks,
si.allow_row_locks,
si.is_hypothetical,
si.has_filter,
si.fill_factor,
si.is_unique,
ISNULL(pf.name, '/* Not partitioned */') AS partition_function,
ISNULL(psc.name, fg.name) AS partition_scheme_or_filegroup,
t.create_date AS table_created_date,
t.modify_date AS table_modified_date
FROM sys.indexes AS si
INNER JOIN sys.tables AS t ON si.object_id = t.object_id
INNER JOIN sys.schemas AS sc ON t.schema_id = sc.schema_id
LEFT JOIN sys.dm_db_index_usage_stats AS stat ON stat.database_id = DB_ID() AND si.object_id = stat.object_id AND si.index_id = stat.index_id
LEFT JOIN sys.partition_schemes AS psc ON si.data_space_id = psc.data_space_id
LEFT JOIN sys.partition_functions AS pf ON psc.function_id = pf.function_id
LEFT JOIN sys.filegroups AS fg ON si.data_space_id = fg.data_space_id
/* Key list */
OUTER APPLY (
SELECT
STUFF((
SELECT N', ' + QUOTENAME(c.name) + CASE ic.is_descending_key
WHEN 1 THEN N' DESC' ELSE N'' END
FROM sys.index_columns AS ic
INNER JOIN sys.columns AS c ON ic.column_id = c.column_id AND ic.object_id = c.object_id
WHERE ic.object_id = si.object_id
AND ic.index_id = si.index_id
AND ic.key_ordinal > 0
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) AS keys(key_definition)
/* Partitioning Ordinal */
OUTER APPLY (
SELECT
MAX(QUOTENAME(c.name)) AS column_name
FROM sys.index_columns AS ic
INNER JOIN sys.columns AS c ON ic.column_id = c.column_id AND ic.object_id = c.object_id
WHERE ic.object_id = si.object_id
AND ic.index_id = si.index_id
AND ic.partition_ordinal = 1
) AS partitioning_column
/* Include list */
OUTER APPLY (
SELECT
STUFF((
SELECT
N', ' + QUOTENAME(c.name)
FROM sys.index_columns AS ic
INNER JOIN sys.columns AS c ON ic.column_id = c.column_id AND ic.object_id = c.object_id
WHERE ic.object_id = si.object_id
AND ic.index_id = si.index_id AND ic.is_included_column = 1
ORDER BY c.name
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) AS includes(include_definition)
/* Partitions */
OUTER APPLY (
SELECT
COUNT(*) AS partition_count,
CAST(SUM(ps.in_row_reserved_page_count) * 8. / 1024. / 1024. AS NUMERIC(32, 1)) AS reserved_in_row_GB,
CAST(SUM(ps.lob_reserved_page_count) * 8. / 1024. / 1024. AS NUMERIC(32, 1)) AS reserved_LOB_GB,
SUM(ps.row_count) AS row_count
FROM sys.partitions AS p
INNER JOIN sys.dm_db_partition_stats AS ps ON p.partition_id = ps.partition_id
WHERE p.object_id = si.object_id
AND p.index_id = si.index_id
) AS partition_sums
/* row compression list by partition */
OUTER APPLY (
SELECT
STUFF((
SELECT
N', ' + CAST(p.partition_number AS VARCHAR(32))
FROM sys.partitions AS p
WHERE p.object_id = si.object_id
AND p.index_id = si.index_id
AND p.data_compression = 1
ORDER BY p.partition_number
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) AS row_compression_clause(row_compression_partition_list)
/* data compression list by partition */
OUTER APPLY (
SELECT
STUFF((
SELECT N', ' + CAST(p.partition_number AS VARCHAR(32))
FROM sys.partitions AS p
WHERE p.object_id = si.object_id
AND p.index_id = si.index_id
AND p.data_compression = 2
ORDER BY p.partition_number
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) AS page_compression_clause(page_compression_partition_list)
WHERE si.type IN (0, 1, 2) /* heap, clustered, nonclustered */
AND si.index_id NOT IN (0, 1)
ORDER BY table_name, si.index_id
OPTION (RECOMPILE);
GO
Results
Script results screen:
Script results detailed description:
| Column name | Data type | Description |
|---|---|---|
| database_name | sysname | Name of actual database |
| table_name | sysname | Table name with schema name where index is created in |
| last_user_read | datetime | Time of last user seek, scan or lookup |
| last_user_update | datetime | Time of last user update. |
| checking_statement | nvarchar(MAX) | T-SQL statement that can be used when recreating this index. You can check if index already exists. |
| index_create_statement | nvarchar(MAX) | T-SQL statement that can be used to create this index |
| index_id | int | ID of the index. index_id is unique only within the object. 0 = Heap, 1 = Clustered index, > 1 = Nonclustered index. |
| index_name | sysname | Name of the index. name is unique only within the object. NULL = Heap. |
| reserved_in_row_GB | bigint | Total GB size of pages reserved for storing and managing in-row data in this partition, regardless of whether the pages are in use or not. Always 0 for a columnstore index. |
| reserved_LOB_GB | bigint | Total GB size of pages reserved for storing and managing out-of-row text, ntext, image, varchar(max), nvarchar(max), varbinary(max), and xml columns within the partition, regardless of whether the pages are in use or not. IAM pages are included. Total number of LOBs reserved for storing and managing a columnstore index in the partition. |
| row_count | bigint | The approximate number of rows in the partition. |
| user_seeks | bigint | Number of seeks by user queries. |
| user_scans | bigint | Number of scans by user queries that did not use 'seek' predicate. |
| user_lookups | bigint | Number of bookmark lookups by user queries. |
| user_updates | bigint | Number of updates by user queries. This includes Insert, Delete, and Updates representing number of operations done not the actual rows affected. For example, if you delete 1000 rows in one statement, this count increments by 1 |
| partition_count | bigint | Number of partitions of index is partitioned. |
| allow_page_locks | bit | 1 = Index allows page locks. 0 = Index does not allow page locks. Always 0 for clustered columnstore indexes. |
| allow_row_locks | bit | 1 = Index allows row locks. 0 = Index does not allow row locks. Always 0 for clustered columnstore indexes. |
| is_hypothetical | bit | 1 = Index is hypothetical and cannot be used directly as a data access path. Hypothetical indexes hold column-level statistics. 0 = Index is not hypothetical. |
| has_filter | bit | 1 = Index has a filter and only contains rows that satisfy the filter definition. 0 = Index does not have a filter. |
| fill_factor | tinyint | > 0 = FILLFACTOR percentage used when the index was created or rebuilt. 0 = Default value Always 0 for clustered columnstore indexes. |
| is_unique | bit | 1 = Index is unique. 0 = Index is not unique. Always 0 for clustered columnstore indexes. |
| partition_function | sysname | Name of the partition function. Is unique within the database. |
| partition_scheme_or_filegroup | sysname | Name of data space, unique within the database. |
| table_created_date | datetime | Date the table object was created. |
| table_modified_date | datetime | Date the table object was last modified by using an ALTER statement. If the object is a table or a view, modify_date also changes when a clustered index on the table or view is created or altered. |