Chris Straw
SHARE:

Find missing Foreign Keys in SQL Server

I was looking working on a script to find possibly missing foreign keys, when I stumbled upon Michael J Swart’s blog and it gave me exactly what I was looking for.

You might need to tweak this a little to match your naming conventions.

SELECT t.name, c.name
FROM sys.columns c
INNER JOIN sys.tables t
	ON t.object_id = c.object_id
INNER JOIN sys.indexes i
	ON i.object_id = t.object_id
LEFT JOIN sys.foreign_key_columns fkc_Parent
	ON fkc_Parent.parent_column_id = c.column_id
	AND fkc_Parent.parent_object_id = c.object_id
LEFT JOIN sys.foreign_key_columns fkc_Referenced
	ON fkc_Referenced.Referenced_column_id = c.column_id
	AND fkc_Referenced.Referenced_object_id = c.object_id
LEFT JOIN sys.index_columns ic
	ON ic.index_id = i.index_id
	AND ic.object_id = t.object_id
	AND ic.column_id = c.column_id
WHERE fkc_Referenced.constraint_object_id IS NULL
	AND fkc_Parent.constraint_column_id IS NULL
	AND ic.index_column_id IS NULL
	AND c.name LIKE '%id'
	AND i.is_primary_key = 1
ORDER BY t.name, c.name

Here is another flavor I found on this blog

SELECT  C.TABLE_SCHEMA,C.TABLE_NAME,C.COLUMN_NAME
FROM    INFORMATION_SCHEMA.COLUMNS C
        INNER Join INFORMATION_SCHEMA.TABLES T
          ON C.TABLE_NAME = T.TABLE_NAME
          And T.TABLE_TYPE = 'Base Table'
          AND T.TABLE_SCHEMA = C.TABLE_SCHEMA
        LEFT Join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE U
          ON C.TABLE_NAME = U.TABLE_NAME
          And C.COLUMN_NAME = U.COLUMN_NAME
          And U.TABLE_SCHEMA = C.TABLE_SCHEMA
WHERE   U.COLUMN_NAME IS Null
        And C.COLUMN_NAME Like '%id'
ORDER BY C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME

Here is mine with a small tweak. ?I added a Join to the?INFORMATION_SCHEMA.KEY_COLUMN_USAGE Table to ensure that the column is a Primary Key of some sort.

SELECT C.TABLE_SCHEMA,C.TABLE_NAME,C.COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS C
JOIN INFORMATION_SCHEMA.TABLES T ON C.TABLE_NAME = T.TABLE_NAME AND T.TABLE_TYPE = 'Base Table' AND T.TABLE_SCHEMA = C.TABLE_SCHEMA
LEFT JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE U ON C.TABLE_NAME = U.TABLE_NAME AND C.COLUMN_NAME = U.COLUMN_NAME AND U.TABLE_SCHEMA = C.TABLE_SCHEMA
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE K ON C.COLUMN_NAME = K.COLUMN_NAME --Make sure that the column is a Primary Key
WHERE   U.COLUMN_NAME IS Null
        And C.COLUMN_NAME Like '%id'
GROUP BY C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME
ORDER BY C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME

Here is my custom version that works for my naming conventions

DECLARE @TableMissingFK TABLE (
	TABLE_SCHEMA nvarchar(128),
	TABLE_NAME sysname,
	TABLE_NAME_PK_COLUMN NVARCHAR(128),
	FK_TABLE_SCHEMA nvarchar(128),
	FK_TABLE_NAME sysname NULL,
	FK_COLUMN_NAME sysname NULL,
	FK_NAME VARCHAR(30))

INSERT @TableMissingFK (TABLE_SCHEMA, TABLE_NAME, FK_COLUMN_NAME)
	SELECT C.TABLE_SCHEMA,C.TABLE_NAME,C.COLUMN_NAME
	FROM INFORMATION_SCHEMA.COLUMNS C
	JOIN INFORMATION_SCHEMA.TABLES T ON C.TABLE_NAME = T.TABLE_NAME AND T.TABLE_TYPE = 'Base Table' AND T.TABLE_SCHEMA = C.TABLE_SCHEMA
	LEFT JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE U ON C.TABLE_NAME = U.TABLE_NAME AND C.COLUMN_NAME = U.COLUMN_NAME AND U.TABLE_SCHEMA = C.TABLE_SCHEMA
	JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE K ON C.COLUMN_NAME = K.COLUMN_NAME --Make sure that the column is a Primary Key
	WHERE   U.COLUMN_NAME IS Null
			And C.COLUMN_NAME Like '%id'
	GROUP BY C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME
	ORDER BY C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME

UPDATE @TableMissingFK SET FK_NAME = SUBSTRING(FK_COLUMN_NAME, 1, CHARINDEX('_', FK_COLUMN_NAME))

UPDATE @TableMissingFK SET
	TABLE_NAME_PK_COLUMN = pkey.COLUMN_NAME,
	FK_NAME = FK_NAME + SUBSTRING(pkey.COLUMN_NAME, 1, CHARINDEX('_', pkey.COLUMN_NAME))
FROM @TableMissingFK ttbl
--JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE keyu ON ttbl.TABLE_SCHEMA = keyu.TABLE_SCHEMA AND ttbl.TABLE_NAME = keyu.TABLE_NAME
JOIN (SELECT tabc.TABLE_SCHEMA, tabc.TABLE_NAME, tabc.CONSTRAINT_NAME, cnst.COLUMN_NAME
	  FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tabc
	  JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cnst ON tabc.CONSTRAINT_NAME = cnst.CONSTRAINT_NAME
	  WHERE tabc.CONSTRAINT_TYPE = 'PRIMARY KEY') pkey ON ttbl.TABLE_SCHEMA = pkey.TABLE_SCHEMA AND ttbl.TABLE_NAME = pkey.TABLE_NAME

UPDATE @TableMissingFK SET FK_NAME = FK_NAME + 'FK'

UPDATE @TableMissingFK SET
	FK_TABLE_SCHEMA = tabc.TABLE_SCHEMA,
	FK_TABLE_NAME = tabc.TABLE_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tabc
JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cnst ON tabc.CONSTRAINT_NAME = cnst.CONSTRAINT_NAME
JOIN @TableMissingFK ttbl ON cnst.COLUMN_NAME = ttbl.FK_COLUMN_NAME
WHERE tabc.CONSTRAINT_TYPE = 'PRIMARY KEY'

SELECT  TABLE_SCHEMA,
        TABLE_NAME,
        TABLE_NAME_PK_COLUMN,
        FK_TABLE_SCHEMA,
        FK_TABLE_NAME,
        FK_COLUMN_NAME,
        FK_NAME,
        'ALTER TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] ADD CONSTRAINT [' + FK_NAME + '] FOREIGN KEY ([' + FK_COLUMN_NAME + ']) REFERENCES [' + FK_TABLE_SCHEMA + '].[' + FK_TABLE_NAME + '] ([' + FK_COLUMN_NAME + '])'
FROM @TableMissingFK