There are scenarios in which it's useful to gather information about the available indexes and their definition as a primary step for troubleshooting or index maintenance. Moreover, it's useful to take a baseline of the defined indexes and update it accordingly when indexes change. A minimum of information can be gathered in Excel files or similar repositories, though for the same can be used also metadata tools, at least when they're easy to use and the associated costs aren't neglectable.
Usually, there are two levels at which the information is needed - at index, respectively at column level. Sometimes it's useful to have an independent query for each level of detail, though in data warehouses and similar use cases it's useful to provide a model on top of the metadata, at least to improve query's maintainability.
The first view encapsulates the logic needed to export the data and it's based on the sys.indexes, sys.objects, sys.schemas and sys.database_principals system objects.
-- create schema for metadata CREATE SCHEMA meta; -- clean after DROP VIEW IF EXISTS meta.vIndexes -- create views CREATE OR ALTER VIEW meta.vIndexes AS -- sys metadata - indexes SELECT OBJ.schema_id , IND.object_id , IND.index_id , SCH.name schema_name , OBJ.name table_name , IND.name index_name , IND.type_desc index_type , IND.is_primary_key , IND.is_unique_constraint , IND.fill_factor , IND.has_filter , IND.auto_created , IND.is_unique is_unique_index , DBP.type principal_type , DBP.type_desc principal_type_desc --, INC.* FROM sys.indexes IND WITH (NOLOCK) JOIN sys.objects OBJ WITH (NOLOCK) ON IND.object_id = OBJ.object_id JOIN sys.schemas SCH WITH (NOLOCK) ON OBJ.schema_id = SCH.schema_id JOIN sys.database_principals DBP ON SCH.principal_id = DBP.principal_id WHERE DBP.type IN ('S', 'E')
Upon case, the needed information can be exported via a query like the one below:
-- sys metadata - index columns SELECt IND.db_name , IND.schema_name , IND.table_name , IND.index_name , IND.index_type , IND.principal_type FROM meta.vIndexes IND WHERE IND.schema_name = 'SalesLT' AND IND.table_name IN ('Address', 'Customer') ORDER BY IND.table_name , IND.index_name
Output:
db_name | schema_name | table_name | index_name | index_type | principal_type |
AdventureWorks01... | SalesLT | Address | AK_Address_rowguid | NONCLUSTERED | S |
AdventureWorks01... | SalesLT | Address | IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion | NONCLUSTERED | S |
AdventureWorks01... | SalesLT | Address | IX_Address_StateProvince | NONCLUSTERED | S |
AdventureWorks01... | SalesLT | Address | PK_Address_AddressID | CLUSTERED | S |
AdventureWorks01... | SalesLT | Customer | AK_Customer_rowguid | NONCLUSTERED | S |
AdventureWorks01... | SalesLT | Customer | IX_Customer_EmailAddress | NONCLUSTERED | S |
AdventureWorks01... | SalesLT | Customer | PK_Customer_CustomerID | CLUSTERED | S |
Similarly, on top of the above view can be built a similar object that provides also the column-related information by adding the sys.index_columns and sys.columns system object to the logic:
-- clean after DROP VIEW IF EXISTS meta.vIndexColumns -- create CREATE OR ALTER VIEW meta.vIndexColumns AS -- sys metadata - index columns SELECT IND.db_id , IND.schema_id , INC.object_id , INC.index_id , INC.index_column_id , INC.column_id , IND.db_name , IND.schema_name , IND.table_name , IND.index_name , COL.name column_name , INC.key_ordinal , INC.partition_ordinal , IND.index_type , IND.is_primary_key , IND.is_unique_constraint , IND.fill_factor , IND.has_filter , IND.auto_created , IND.is_unique_index , INC.is_descending_key , INC.is_included_column , IND.principal_type , IND.principal_type_desc FROM sys.index_columns INC JOIN sys.columns COL WITH (NOLOCK) ON INC.object_id = COL.object_id AND INC.column_id = COL.column_id JOIN meta.vIndexes IND WITH (NOLOCK) ON INC.object_id = IND.object_id AND INC.index_id = IND.index_id
And, there's an example of the query based on this view:
-- sys metadata - index columns SELECt INC.schema_name , INC.table_name , INC.index_name , INC.column_name , INC.key_ordinal , INC.index_type , INC.principal_type FROM meta.vIndexColumns INC WHERE INC.schema_name = 'SalesLT' AND INC.table_name IN ('Address', 'Customer') ORDER BY INC.table_name , INC.index_name , INC.key_ordinal
Output:
schema_name | table_name | index_name | column_name | key_ordinal | index_type | principal_type |
SalesLT | Address | AK_Address_rowguid | rowguid | 1 | NONCLUSTERED | S |
SalesLT | Address | IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion | AddressLine1 | 1 | NONCLUSTERED | S |
SalesLT | Address | IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion | AddressLine2 | 2 | NONCLUSTERED | S |
SalesLT | Address | IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion | City | 3 | NONCLUSTERED | S |
SalesLT | Address | IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion | StateProvince | 4 | NONCLUSTERED | S |
SalesLT | Address | IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion | PostalCode | 5 | NONCLUSTERED | S |
SalesLT | Address | IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion | CountryRegion | 6 | NONCLUSTERED | S |
SalesLT | Address | IX_Address_StateProvince | StateProvince | 1 | NONCLUSTERED | S |
SalesLT | Address | PK_Address_AddressID | AddressID | 1 | CLUSTERED | S |
SalesLT | Customer | AK_Customer_rowguid | rowguid | 1 | NONCLUSTERED | S |
SalesLT | Customer | IX_Customer_EmailAddress | EmailAddress | 1 | NONCLUSTERED | S |
SalesLT | Customer | PK_Customer_CustomerID | CustomerID | 1 | CLUSTERED | S |
Notes:
1) As a DBA it's useful to take a baseline of the indexes defined and reevaluate their usefulness over time. These are one of the checks that should be done when one becomes responsible for the administration of a database server, independently of the vendor.
2) The definitions of the views can be extended as needed, though one should try to keep the overall complexity to a minimum.
3) There are voices against the use of NOLOCK. Feel free to change the objects accordingly!
4) It's useful to work in a dedicated schema (e.g. meta) and have a different naming convention that deviates slightly from one defined by Microsoft. This should make sure that no confusion with the system objects exists.
5) Azure SQL takes over many of the responsibilities for index maintenance. Even if indexes are managed automatically by the system, a baseline is still needed, at least to evaluate functionality's performance, respectively the changes that occurred in the environment.
6) Except attributes which were added for specific functionality over time, the queries should work at least starting with SQL Server 2005 forward.
7) See also the notes on clustered vs nonclustered indexes.
Happy coding!
Previous Post <<||>> Next Post
No comments:
Post a Comment