Recently a need resurfaced to explore the foreign key tree (hierarchy/genealogy) as it related to a specific table within a database. As I pulled out the script from the repository, I realized there was some unfinished work to be done. But there was also some polish that needed to be added. This is an update to the most recent posting of that script. You can see several revisions in the series at this link or the group here.
Some of the changes involve formatting and and labeling. I added a new column called “Direction” to help understand the relationship of the key to the table in question. I also changed up the FKGenealogy (formerly called SortCol) to reflect the source table more accurately in the case when the key comes from an ancestor rather than a descendant. The Level of the FK was also modified to help understand a little better how far away the ancestor was in relationship to the origin table.
A final adjustment also comes from the Genealogy attribute. Ancestors were all starting at the wrong point in the lineage. I adjusted that so the lineage can be seen from the point in the tree that the ancestor is related rather than as a root direct from the origin table.
All in all, this script should make more sense to the user than the previous versions.
DECLARE @StartParentTable VARCHAR(256); SELECT @StartParentTable = 'tblCampaignMain'; With Posterity (ParentTable,ParentColumn, ChildTable,ChildColumn, FKLevel, FKGenealogy) AS ( SELECT OBJECT_NAME(sfk.referenced_object_id) AS ParentTable ,COL_NAME(sfkc.referenced_object_id,sfkc.referenced_column_id) AS ParentColumn ,OBJECT_NAME(sfk.Parent_object_id) AS ChildTable ,COL_NAME(sfkc.parent_object_id,sfkc.parent_column_id) AS ChildColumn ,0 AS FKLevel ,CAST('.'+CAST(OBJECT_NAME(sfk.referenced_object_id) AS VARCHAR(MAX))+'.' AS VARCHAR(MAX)) FROM sys.foreign_key_columns sfkc INNER Join sys.foreign_keys sfk ON sfkc.constraint_object_id = sfk.OBJECT_ID WHERE OBJECT_NAME(sfk.referenced_object_id) = @StartParentTable UNION All SELECT OBJECT_NAME(sfk.referenced_object_id) AS ParentTable ,COL_NAME(sfkc.referenced_object_id,sfkc.referenced_column_id) AS ParentColumn ,OBJECT_NAME(sfk.Parent_object_id) AS ChildTable ,COL_NAME(sfkc.parent_object_id,sfkc.parent_column_id) AS ChildColumn ,FKLevel + 1 ,STUFF(('.' + F.FKGenealogy + CAST(CAST(OBJECT_NAME(sfk.referenced_object_id) AS VARCHAR(MAX)) + '.' AS VARCHAR(MAX)) ),1,1,'') FROM sys.foreign_keys sfk INNER Join Posterity F ON OBJECT_NAME(sfk.referenced_object_id) = F.ChildTable And F.FKGenealogy NOT LIKE '%'+CAST(OBJECT_NAME(sfk.referenced_object_id) AS VARCHAR(MAX))+'%' INNER Join sys.foreign_key_columns sfkc ON sfkc.constraint_object_id = sfk.OBJECT_ID WHERE OBJECT_NAME(sfk.referenced_object_id) <> @StartParentTable And sfk.referenced_object_id <> sfk.parent_object_id ), Ancestry (ParentTable,ParentColumn, ChildTable,ChildColumn, FKLevel, FKGenealogy) AS ( SELECT DISTINCT OBJECT_NAME(sfk.referenced_object_id) AS ParentTable ,COL_NAME(sfkc.referenced_object_id,sfkc.referenced_column_id) AS ParentColumn ,OBJECT_NAME(sfk.Parent_object_id) AS ChildTable ,COL_NAME(sfkc.parent_object_id,sfkc.parent_column_id) AS ChildColumn ,(FKLevel * -1)-1 AS FKLevel ,F.FKGenealogy + CAST(CAST(OBJECT_NAME(sfk.Parent_object_id) AS VARCHAR(MAX))+'.' AS VARCHAR(MAX)) FROM Posterity F INNER Join sys.foreign_keys sfk ON F.ChildTable = OBJECT_NAME(sfk.parent_object_id) And F.ParentTable <> OBJECT_NAME(sfk.referenced_object_id) INNER Join sys.foreign_key_columns sfkc ON sfkc.constraint_object_id = sfk.OBJECT_ID UNION All SELECT OBJECT_NAME(sfk.referenced_object_id) AS ParentTable ,COL_NAME(sfkc.referenced_object_id,sfkc.referenced_column_id) AS ParentColumn ,OBJECT_NAME(sfk.Parent_object_id) AS ChildTable ,COL_NAME(sfkc.parent_object_id,sfkc.parent_column_id) AS ChildColumn ,F.FKLevel -1 ,STUFF(('.' + FKGenealogy + CAST(CAST(OBJECT_NAME(sfk.referenced_object_id) AS VARCHAR(MAX)) + '.' AS VARCHAR(MAX)) ),1,1,'') FROM Ancestry F INNER Join sys.foreign_keys sfk ON F.ParentTable = OBJECT_NAME(sfk.parent_object_id) INNER Join sys.foreign_key_columns sfkc ON sfkc.constraint_object_id = sfk.OBJECT_ID WHERE F.ParentTable not in (SELECT ParentTable FROM Posterity) And sfk.referenced_object_id <> sfk.parent_object_id And F.ChildTable not IN (SELECT ChildTable FROM Posterity) ) SELECT ParentTable,ParentColumn, ChildTable,ChildColumn, FKLevel, FKGenealogy ,Case When FKLevel > 0 Then 'Descendant' When FKLevel = 0 Then 'Root' End as Direction FROM Posterity UNION All SELECT ParentTable,ParentColumn, ChildTable,ChildColumn, FKLevel, FKGenealogy ,'Ancestor' as Direction FROM Ancestry ORDER BY FKGenealogy ASC, Direction Desc OPTION (maxrecursion 500);