SQL Server: Script out all indexes in a database

Kendra Little has a gist to script out all indexes HOW TO SCRIPT OUT INDEXES FROM SQL SERVER but it didn’t include XML or columnstore indexes, so I’ve forked her gist and added a few things to it. I changed the FOR XML/STUFF trick into STRING_AGG() (which is SQL Server 2017 onwards) for no other reason than I’m not working with any instance versions less than that.

The updated gist is here.

/*****************************************************************************
MIT License, http://www.opensource.org/licenses/mit-license.php
Contact: help@sqlworkbooks.com
Copyright (c) 2018 SQL Workbooks LLC
Copyright (c) 2024 Mitch Wheat
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*****************************************************************************/
-- Removed FOR-XML 'trick' used to concatenate comma separated values with STRING_AGG()
-- STRING_AGG() is SQL Server 2017 onwards...
-- Added:
-- is_autocreated
-- xml indexes
-- columnstore indexes
-- Currently not showing:
--
-- 1. xml compression by partition (SQL Server2022 onwards)
-- 2. spatial indexes
-- 3. selective xml indexes
-- 4. NONCLUSTERED HASH indexes
SELECT
DB_NAME() AS [database],
sc.name + N'.' + t.name AS [table],
si.name AS [index],
si.type_desc AS indextype,
CASE si.index_id
WHEN 0 THEN N'/* Heap */'
ELSE
CASE WHEN si.is_primary_key = 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 xi.using_xml_index_id IS NULL AND (si.type = 3) THEN N'PRIMARY ' ELSE N'' END +
CASE
WHEN si.type = 1 THEN N'CLUSTERED'
WHEN si.type = 2 THEN N'NONCLUSTERED'
WHEN si.type = 3 THEN N'XML'
WHEN si.type = 5 THEN N'CLUSTERED COLUMNSTORE'
WHEN si.type = 6 THEN N'NONCLUSTERED COLUMNSTORE'
--WHEN si.type = 4 THEN N'SPATIAL'
--WHEN si.type = 7 THEN N'NONCLUSTERED HASH'
ELSE N''
END +
N' INDEX ' + QUOTENAME(si.name) + N' ON ' + QUOTENAME(sc.name) + N'.' + QUOTENAME(t.name) + N''
END +
CASE
WHEN key_column_list IS NOT NULL AND (si.type != 5) THEN '(' + key_column_list + N')'
WHEN xmlkey_column_list IS NOT NULL AND (si.type= 3) THEN '(' + xmlkey_column_list + N')'
WHEN include_column_list IS NOT NULL AND (si.type = 6) THEN '(' + include_column_list + N')'
ELSE N''
END +
CASE WHEN primary_name IS NOT NULL THEN N' USING XML INDEX ' + primary_name + N' FOR ' + xi.secondary_type_desc COLLATE Latin1_General_CI_AS ELSE N'' END +
CASE WHEN include_column_list IS NOT NULL AND (si.type != 5 AND si.type != 6) THEN N' INCLUDE (' + include_column_list + N')' ELSE N'' END +
CASE WHEN si.filter_definition IS NOT NULL THEN N' WHERE ' + si.filter_definition ELSE N'' END +
/* with clause */
CASE WHEN row_compression_partition_list IS NOT NULL OR page_compression_partition_list IS NOT NULL OR columnstore_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 +
CASE
WHEN row_compression_partition_list IS NOT NULL AND page_compression_partition_list IS NOT NULL AND columnstore_compression_partition_list IS NOT NULL THEN N', '
ELSE N''
END +
CASE
WHEN columnstore_compression_partition_list IS NOT NULL THEN N'DATA_COMPRESSION = COLUMNSTORE' +
CASE WHEN psc.name IS NULL THEN N'' ELSE + N' ON PARTITIONS (' + columnstore_compression_partition_list + N')' END
ELSE N''
END +
CASE
WHEN si.compression_delay IS NOT NULL THEN N', COMPRESSION_DELAY = ' + CAST(si.compression_delay AS NVARCHAR(16))
ELSE N''
END
+ N')'
ELSE N''
END +
CASE
WHEN si.type != 3 THEN
/* 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
ELSE N''
END
+ N';'
END AS create_statement,
partition_sums.row_count,
si.is_primary_key,
si.fill_factor,
si.is_unique,
si.auto_created AS is_auto_created,
si.is_hypothetical,
stat.user_seeks,
stat.user_scans,
stat.user_lookups,
user_updates,
(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,
partition_sums.reserved_in_row_GB,
partition_sums.reserved_LOB_GB,
si.allow_page_locks,
si.allow_row_locks,
partition_sums.partition_count,
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_modify_date
FROM
sys.indexes si
JOIN sys.tables t ON si.object_id = t.object_id
JOIN sys.schemas sc ON t.schema_id = sc.schema_id
LEFT JOIN sys.dm_db_index_usage_stats 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 psc ON si.data_space_id = psc.data_space_id
LEFT JOIN sys.partition_functions pf ON psc.function_id = pf.function_id
LEFT JOIN sys.filegroups fg ON si.data_space_id = fg.data_space_id
LEFT JOIN sys.xml_indexes xi ON xi.object_id = si.object_id and xi.index_id = si.index_id
LEFT JOIN
(
SELECT
i2.object_id,
i2.index_id,
i2.name as primary_name
FROM sys.xml_indexes i2
) sxi ON sxi.object_id = xi.object_id AND xi.using_xml_index_id = sxi.index_id
/* Key list */
OUTER APPLY (
SELECT STRING_AGG(QUOTENAME(c.name) + CASE ic.is_descending_key WHEN 1 then N' DESC' ELSE N'' END, N', ') WITHIN GROUP (ORDER BY ic.key_ordinal)
FROM sys.index_columns ic
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)
) AS keys ( key_column_list )
/* XML Key list */
OUTER APPLY (
SELECT STRING_AGG(QUOTENAME(c.name) + CASE ic.is_descending_key WHEN 1 then N' DESC' ELSE N'' END, N', ') WITHIN GROUP (ORDER BY ic.key_ordinal)
FROM sys.index_columns ic
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 and is_included_column = 0)
) AS xmlkeys ( xmlkey_column_list )
/* Partitioning Column */
OUTER APPLY (
SELECT MAX(QUOTENAME(c.name)) AS column_name
FROM sys.index_columns ic
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 STRING_AGG(QUOTENAME(c.name), N', ') WITHIN GROUP (ORDER BY c.name)
FROM sys.index_columns ic
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
) AS includes ( include_column_list )
/* 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 p
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 STRING_AGG(CAST(p.partition_number AS NVARCHAR(16)), N', ') WITHIN GROUP (ORDER BY p.partition_number)
FROM sys.partitions p
WHERE
p.object_id = si.object_id
and p.index_id = si.index_id
and p.data_compression = 1
) AS row_compression ( row_compression_partition_list )
/* page compression list by partition */
OUTER APPLY (
SELECT STRING_AGG(CAST(p.partition_number AS NVARCHAR(16)), N', ') WITHIN GROUP (ORDER BY p.partition_number)
FROM sys.partitions p
WHERE
p.object_id = si.object_id
and p.index_id = si.index_id
and p.data_compression = 2
) AS page_compression ( page_compression_partition_list )
/* columnstore compression list by partition */
OUTER APPLY (
SELECT STRING_AGG(CAST(p.partition_number AS NVARCHAR(16)), N', ') WITHIN GROUP (ORDER BY p.partition_number)
FROM sys.partitions p
WHERE
p.object_id = si.object_id
and p.index_id = si.index_id
and p.data_compression = 3
) AS columnstore_compression ( columnstore_compression_partition_list )
--/* XML compression list by partition: SQL Server 2022 onwards */
--OUTER APPLY (
-- SELECT STRING_AGG(CAST(p.partition_number AS NVARCHAR(16)), N', ') WITHIN GROUP (ORDER BY p.partition_number)
-- FROM sys.partitions p
-- WHERE
-- p.object_id = si.object_id
-- and p.index_id = si.index_id
-- and p.xml_compression = 1 -- 2022 onwards
-- ) AS xml_compression ( xml_compression_partition_list )
WHERE
si.type IN (0, 1, 2, 3, 5, 6)
ORDER BY
sc.name, t.name, si.index_id
OPTION(RECOMPILE);
GO

SQL Server: Compressing a Table and/or Indexes

I always forget whether the first syntax compresses the NC indexes as well, so posting here so I don’t forget again!

This compresses just the clustered index (i.e. the table data):

1
2
3
4
-- Just clustered index
ALTER TABLE dbo.Table
REBUILD PARTITION = ALL
WITH (DATA_COMPRESSION = PAGE);

This compresses all indexes including the clustered index:

1
2
3
4
-- All indexes including clustered index
ALTER INDEX ALL ON dbo.Table
REBUILD PARTITION = ALL
WITH (DATA_COMPRESSION = PAGE);