The Ultimate Index-Less Foreign-Key Finder

,

*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
Util_FKsWithoutIndexes
By Jesse Roberge - YeshuaAgapao@Yahoo.com

Searches for foreign key constraints that don't have fully matching indexes.
The best partial matching indexes are outputted with MatchCounts and column comparisons
Generates a CREATE INDEX template for each foreign-key with no matching index or a partial-matching index.
Customize as needed (add includes, if you want it clustered, or if it should be a part of the primary key, or if you want to merge with another index)
FKs missing full matching indexes can severely hurt the performance of DELETES on the referenced table due to table-scans for checking referential integrity,
as well as SELECTS on the referencing tables where the foreign-key column(s) are in the WHERE or JOIN predicates (This will affect you whether a constraint exists or not).
This only checks the first N columns of the index where N is the number of columns in the foreign key constraint.
Index column order is not verified beyond this (A two-col FK that has 1 matching column in the 2nd col of a 3-col index will be outputted as a partial match)
If your database has no foreign key constraints, then this tool will be worthless to you.
Many databases have partial foreign key constraint coverage. This only works on related tables where constraints are declared.

Required Input Parameters:
None

Optional Input Parameters:
@Delimiter VarChar(1)=','

Usage:
EXECUTE Util.Util_FKsWithoutIndexes

Copyright:
Licensed under the L-GPL - a weak copyleft license - you are permitted to use this as a component of a proprietary database and call this from proprietary software.
Copyleft lets you do anything you want except plagiarize, conceal the source, or prohibit copying & re-distribution of this script/proc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

see <http://www.fsf.org/licensing/licenses/lgpl.html> for the license text.

*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=

SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
SET ANSI_PADDING ON
GO

IF OBJECT_ID('Util.Util_FKsWithoutIndexes', 'P') IS NOT NULL DROP PROCEDURE Util.Util_FKsWithoutIndexes
GO

/*
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
Util_FKsWithoutIndexes
By Jesse Roberge - YeshuaAgapao@Yahoo.com

Searches for foreign key constraints that don't have fully matching indexes.
The best partial matching indexes are outputted with MatchCounts and column comparisons
Generates a CREATE INDEX template for each foreign-key with no matching index or a partial-matching index.
	Customize as needed (add includes, if you want it clustered, or if it should be a part of the primary key, or if you want to merge with another index)
FKs missing full matching indexes can severely hurt the performance of DELETES on the referenced table due to table-scans for checking referential integrity,
	as well as SELECTS on the referencing tables where the foreign-key column(s) are in the WHERE or JOIN predicates (This will affect you whether a constraint exists or not).
This only checks the first N columns of the index where N is the number of columns in the foreign key constraint.
	Index column order is not verified beyond this (A two-col FK that has 1 matching column in the 2nd col of a 3-col index will be outputted as a partial match)
If your database has no foreign key constraints, then this tool will be worthless to you.
	Many databases have partial foreign key constraint coverage. This only works on related tables where constraints are declared.

Required Input Parameters:
	None

Optional Input Parameters:
	@Delimiter VarChar(1)=’,’

Usage:
	EXECUTE Util.Util_FKsWithoutIndexes

Copyright:
	Licensed under the L-GPL - a weak copyleft license - you are permitted to use this as a component of a proprietary database and call this from proprietary software.
	Copyleft lets you do anything you want except plagiarize, conceal the source, or prohibit copying & re-distribution of this script/proc.

	This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    see <http://www.fsf.org/licensing/licenses/lgpl.html> for the license text.

*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
*/

CREATE PROCEDURE Util.Util_FKsWithoutIndexes
	@Delimiter VarChar(1)=','
AS

SELECT
	parentschemas.schema_id AS ReferencingSchemaID, parentschemas.name AS ReferencingSchemaName,
	parentobjects.object_id AS ReferencingTableID, parentobjects.name AS ReferencingTableName,
	referencedschemas.schema_id AS ReferencedSchemaID, referencedschemas.name AS ReferencedSchemaName,
	referencedobjects.object_id AS ReferencedTableID, referencedobjects.name AS ReferencedTableName,
	objects.object_id AS FKConstraintID, objects.name AS FKConstraintName, FKTotal.FKColumnCount,
	FKMatch.index_id AS PartialMatchIndexID, indexes.name AS PartialMatchIndexName, FKMatch.IndexColumnMatchCount,
	foreign_key_columns.foreign_key_columns, index_columns.index_columns_key, index_columns.index_columns_include,
	'CREATE NONCLUSTERED INDEX IX__' + parentschemas.name + '_' + parentobjects.name + '__' + REPLACE(foreign_key_columns.foreign_key_columns, ', ', '_') + ' ON ' + parentschemas.name + '.' + parentobjects.name + ' (' + foreign_key_columns.foreign_key_columns + ')' AS FKIndexCreate
FROM
	(
		SELECT foreign_key_columns.constraint_object_id, MAX(referenced_object_id) AS referenced_object_id, COUNT(*) AS FKColumnCount
		FROM sys.foreign_key_columns
		GROUP BY foreign_key_columns.constraint_object_id
	) AS FKTotal
	JOIN sys.objects ON FKTotal.constraint_object_id=objects.object_id
	JOIN sys.objects AS parentobjects ON objects.parent_object_id=ParentObjects.object_id
	JOIN sys.schemas AS parentschemas ON parentobjects.schema_id=parentschemas.schema_id
	JOIN sys.objects AS referencedobjects ON FKTotal.referenced_object_id=referencedObjects.object_id
	JOIN sys.schemas AS referencedschemas ON referencedobjects.schema_id=referencedschemas.schema_id
	OUTER APPLY (
		SELECT constraint_object_id, index_id, index_object_id, IndexColumnMatchCount
		FROM
			(
				SELECT
					foreign_key_columns.constraint_object_id, index_columns.index_id, MAX(index_columns.object_id) AS index_object_id,
					COUNT(*) AS IndexColumnMatchCount,
					ROW_NUMBER() OVER (PARTITION BY foreign_key_columns.constraint_object_id ORDER BY COUNT(*) DESC) AS IndexColumnMatchCountRowNumber
				FROM
					sys.foreign_key_columns
					JOIN sys.index_columns ON
						index_columns.object_id=foreign_key_columns.parent_object_id
						AND index_columns.column_id=foreign_key_columns.parent_column_id
				WHERE
					index_columns.is_included_column=0
					AND index_columns.key_ordinal<=FKTotal.FKColumnCount
					AND FKTotal.constraint_object_id=foreign_key_columns.constraint_object_id
				GROUP BY foreign_key_columns.constraint_object_id, index_columns.index_id
			) AS FKMatch
		WHERE IndexColumnMatchCountRowNumber=1
	) AS FKMatch
	LEFT OUTER JOIN sys.indexes ON FKMatch.index_object_id=indexes.object_id AND FKMatch.index_id=indexes.index_id
	CROSS APPLY (
		SELECT
			LEFT(index_columns_key, LEN(index_columns_key)-1) AS foreign_key_columns
		FROM
			(
				SELECT
					(
						SELECT columns.name + @Delimiter + ' '
						FROM
							sys.foreign_key_columns
							JOIN sys.columns ON
								foreign_key_columns.parent_column_id=columns.column_id
								AND foreign_key_columns.parent_object_id=columns.object_id
						WHERE
							FKTotal.constraint_object_id=foreign_key_columns.constraint_object_id
						ORDER BY constraint_column_id
						FOR XML PATH('')
					) AS index_columns_key
			) AS Index_Columns
	) AS foreign_key_columns
	CROSS APPLY (
		SELECT
			LEFT(index_columns_key, LEN(index_columns_key)-1) AS index_columns_key,
			LEFT(index_columns_include, LEN(index_columns_include)-1) AS index_columns_include
		FROM
			(
				SELECT
					(
						SELECT columns.name + @Delimiter + ' '
						FROM
							sys.index_columns
							JOIN sys.columns ON
								index_columns.column_id=columns.column_id
								AND index_columns.object_id=columns.object_id
						WHERE
							index_columns.is_included_column=0
							AND FKMatch.index_object_id=index_columns.object_id
							AND FKMatch.index_id=index_columns.index_id
						ORDER BY key_ordinal
						FOR XML PATH('')
					) AS index_columns_key,
					(
						SELECT sys.columns.name + @Delimiter + ' '
						FROM
							sys.index_columns
							JOIN sys.columns ON
								sys.index_columns.column_id=sys.columns.column_id
								AND sys.index_columns.object_id=sys.columns.object_id
						WHERE
							sys.index_columns.is_included_column=1
							AND sys.indexes.object_id=sys.index_columns.object_id
							AND sys.indexes.index_id=sys.index_columns.index_id
						ORDER BY index_column_id
						FOR XML PATH('')
					) AS index_columns_include
			) AS Index_Columns
	) AS Index_Columns
WHERE FKMatch.IndexColumnMatchCount IS NULL OR FKTotal.FKColumnCount<>FKMatch.IndexColumnMatchCount
ORDER BY
	IndexColumnMatchCount DESC, FKColumnCount DESC,
	parentobjects.name, objects.name
GO

--*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=

Rate

5 (2)

Share

Share

Rate

5 (2)