IF OBJECT_ID('dbo.sp_helpExpandView', 'P') IS NULL EXECUTE ('CREATE PROCEDURE dbo.sp_helpExpandView AS SELECT 1'); GO ALTER PROCEDURE dbo.sp_helpExpandView ( @ViewName NVARCHAR(512), @OutputFormat VARCHAR(15) = 'All', @ShowObjectCount BIT = 0, @Debug BIT = 0 ) AS /*=============================================================================== -- sp_helpExpandView -- -- Written By: Andy Yun -- Created On: 2015-02-27 -- -- Summary: -- This tool script was written to aid in de-constructing nested views and -- identifying all base objects underneath. -- -- Supports: -- Scalar & Table-Valued Functions -- Schemas -- Synonyms to other DBs -- -- -- Updates: -- Date Developer Remarks -- 2015-03-02 AYun V1: Initial Release -- 2015-03-03 AYun V2: Rewrote to properly handle external database -- references in child objects. -- 2015-03-03 AYun V2.1: Tweaked to remove duplicate results. -- Added sorting to output -- 2015-03-09 AYun V2.11: Adjusted horizontal output ORDER BY clause. -- 2015-03-30 AYun V2.2: New Output - List all objects referenced & count -- 2015-05-20 AYun V2.3: Corrected CURSOR & Database Name with space issue. -- Thanks Mickey Stuewe! --------------------------------------------------------------------------------- -- License: -- This code is free to use for personal, educational, and internal corporate -- purposes provided that this header is preserved. -- (c) 2015 Andy Yun ===============================================================================*/ BEGIN BEGIN TRY IF OBJECT_ID(@ViewName) IS NULL BEGIN RAISERROR('@ViewName undefined or does not exist.', 11, 1); END DECLARE @BaseObject_DBName NVARCHAR(256), @ChildObjectsFound SMALLINT = 1, @HierarchyLevelToSearch TINYINT = 1, @MinObjectHierarchyID INT = 0, @TmpObjectHierarchyID INT = NULL, @MaxObjectHierarchyID INT = 1, @DBName NVARCHAR(256), @sqlCMD NVARCHAR(4000); -- sys.dm_sql_referenced_entities() dislikes brackets. Must strip out. SET @ViewName = REPLACE(REPLACE(@ViewName, ']', ''), '[', ''); -- if no schema specified, append dbo IF CHARINDEX('.', @ViewName, 1) = 0 SET @ViewName = 'dbo.' + @ViewName; IF @Debug = 1 SELECT '@ViewName', @ViewName ----- -- Temp Tables IF OBJECT_ID('tempdb.dbo.#tmpObjectHierarchy', 'U') IS NOT NULL DROP TABLE #tmpObjectHierarchy; IF OBJECT_ID('tempdb.dbo.#tmpAllObjects', 'U') IS NOT NULL DROP TABLE #tmpAllObjects; IF OBJECT_ID('tempdb.dbo.#tmpExternalObjectsToProcess', 'U') IS NOT NULL DROP TABLE #tmpExternalObjectsToProcess; -- Create final output table containing full object hierarchy CREATE TABLE #tmpObjectHierarchy ( ObjectHierarchyID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED, HierarchyLvl TINYINT, BaseObject_DBName NVARCHAR(512), BaseObject_FullName NVARCHAR(512), Child_DBName NVARCHAR(512), Child_SchemaName NVARCHAR(256), Child_ObjectName NVARCHAR(256), Child_FullName NVARCHAR(512), Child_Type CHAR(2), Child_ObjectID INT, Synonym_BaseObjectName NVARCHAR(1035), Synonym_BaseObjectType NVARCHAR(2), ParentObjectHierarchyID INT ); -- Populate #tmpObjectHierarchy with base level data INSERT INTO #tmpObjectHierarchy ( BaseObject_DBName, BaseObject_FullName, Child_FullName, Child_SchemaName, Child_ObjectName, Child_Type, Child_ObjectID, HierarchyLvl, Child_DBName, ParentObjectHierarchyID ) SELECT DISTINCT DB_NAME(), @ViewName, COALESCE(schemas.name, dm_sql_referenced_entities.referenced_schema_name) + '.' + dm_sql_referenced_entities.referenced_entity_name, COALESCE(schemas.name, dm_sql_referenced_entities.referenced_schema_name), dm_sql_referenced_entities.referenced_entity_name, COALESCE(schemas.type, NULL), dm_sql_referenced_entities.referenced_id, 1 AS HierarchyLvl, COALESCE(dm_sql_referenced_entities.referenced_database_name, DB_NAME()), 0 AS ParentObjectHierarchyID FROM sys.dm_sql_referenced_entities(@ViewName, 'OBJECT') LEFT OUTER JOIN ( SELECT objects.object_id, schemas.name, objects.type FROM sys.objects INNER JOIN sys.schemas ON objects.schema_id = schemas.schema_id ) schemas ON dm_sql_referenced_entities.referenced_id = schemas.object_id WHERE referenced_minor_id = 0; SELECT @MaxObjectHierarchyID = MAX(ObjectHierarchyID) FROM #tmpObjectHierarchy; IF @Debug = 1 SELECT '#tmpObjectHierarchy', * FROM #tmpObjectHierarchy; --SELECT '1-#tmpObjectHierarchy', * FROM #tmpObjectHierarchy; ----- -- Loop for each new Hierarchy Level #tmpObjectHierarchy WHILE @MinObjectHierarchyID < @MaxObjectHierarchyID BEGIN IF @Debug = 1 SELECT @MinObjectHierarchyID AS '@MinObjectHierarchyID', @MaxObjectHierarchyID AS '@MaxObjectHierarchyID'; ----- -- For each DB found in #tmpObjectHierarchy, loop through each recordset of each Database DECLARE cursorEachDatabase CURSOR FAST_FORWARD FOR SELECT DISTINCT Child_DBName FROM #tmpObjectHierarchy; OPEN cursorEachDatabase; FETCH NEXT FROM cursorEachDatabase INTO @DBName WHILE @@FETCH_STATUS = 0 BEGIN IF @Debug = 1 SELECT @DBName AS '@DBName'; SET @sqlCMD = N' INSERT INTO #tmpObjectHierarchy ( BaseObject_DBName, BaseObject_FullName, Child_FullName, Child_SchemaName, Child_ObjectName, Child_ObjectID, Child_Type, HierarchyLvl, Child_DBName, ParentObjectHierarchyID ) SELECT DISTINCT #tmpObjectHierarchy.Child_DBName, #tmpObjectHierarchy.Child_FullName, COALESCE(dm_sql_referenced_entities.referenced_schema_name, schemas.name) + ''.'' + dm_sql_referenced_entities.referenced_entity_name, COALESCE(dm_sql_referenced_entities.referenced_schema_name, schemas.name), dm_sql_referenced_entities.referenced_entity_name, dm_sql_referenced_entities.referenced_id, COALESCE(schemas.type, NULL), #tmpObjectHierarchy.HierarchyLvl + 1 AS HierarchyLvl, COALESCE(dm_sql_referenced_entities.referenced_database_name, #tmpObjectHierarchy.Child_DBName), #tmpObjectHierarchy.ObjectHierarchyID FROM #tmpObjectHierarchy CROSS APPLY [@DatabaseName].sys.dm_sql_referenced_entities(#tmpObjectHierarchy.Child_FullName, ''OBJECT'') LEFT OUTER JOIN ( SELECT objects.object_id, schemas.name, objects.type FROM [@DatabaseName].sys.objects INNER JOIN [@DatabaseName].sys.schemas ON objects.schema_id = schemas.schema_id ) schemas ON dm_sql_referenced_entities.referenced_id = schemas.object_id WHERE dm_sql_referenced_entities.referenced_minor_id = 0 AND #tmpObjectHierarchy.Child_DBName = ''@DatabaseName'' AND #tmpObjectHierarchy.ObjectHierarchyID >= @MinObjectHierarchyID;' SET @sqlCMD = REPLACE(@sqlCMD, '@MinObjectHierarchyID', @MinObjectHierarchyID) SET @sqlCMD = REPLACE(@sqlCMD, '@HierarchyLevelToSearch', @HierarchyLevelToSearch) SET @sqlCMD = REPLACE(@sqlCMD, '@DatabaseName', @DBName) IF @Debug = 1 PRINT @sqlCMD; EXECUTE sp_executesql @sqlCMD -- Must identify if any of these objects are synonyms. SET @sqlCMD = N' -- Update synonym objects with fully qualified base_object_name values UPDATE #tmpObjectHierarchy SET Child_Type = synonyms.type, Child_DBName = PARSENAME(synonyms.base_object_name, 3), Synonym_BaseObjectName = synonyms.base_object_name FROM [@DatabaseName].sys.synonyms INNER JOIN [@DatabaseName].sys.schemas ON synonyms.schema_id = schemas.schema_id WHERE #tmpObjectHierarchy.Child_DBName = ''@DatabaseName'' AND #tmpObjectHierarchy.ObjectHierarchyID >= @MinObjectHierarchyID AND synonyms.object_id = #tmpObjectHierarchy.Child_ObjectID;'; SET @sqlCMD = REPLACE(@sqlCMD, '@MinObjectHierarchyID', @MinObjectHierarchyID) SET @sqlCMD = REPLACE(@sqlCMD, '@DatabaseName', @DBName) IF @Debug = 1 PRINT @sqlCMD; EXECUTE sp_executesql @sqlCMD FETCH NEXT FROM cursorEachDatabase INTO @DBName END CLOSE cursorEachDatabase; DEALLOCATE cursorEachDatabase; SELECT @TmpObjectHierarchyID = @MinObjectHierarchyID, @MinObjectHierarchyID = @MaxObjectHierarchyID + 1, @MaxObjectHierarchyID = MAX(ObjectHierarchyID) FROM #tmpObjectHierarchy; -- Serialize INSERT INTO #tmpObjectHierarchy ( BaseObject_DBName, BaseObject_FullName, Child_FullName, Child_SchemaName, Child_ObjectName, Child_ObjectID, HierarchyLvl, Child_DBName, ParentObjectHierarchyID ) SELECT DISTINCT #tmpObjectHierarchy.BaseObject_DBName AS BaseObject_DBName, #tmpObjectHierarchy.Child_FullName AS BaseObject_FullName, PARSENAME(Synonym_BaseObjectName, 2) + '.' + PARSENAME(Synonym_BaseObjectName, 1) AS Child_FullName, PARSENAME(Synonym_BaseObjectName, 2) AS Child_SchemaName, PARSENAME(Synonym_BaseObjectName, 1) AS Child_ObjectName, OBJECT_ID(#tmpObjectHierarchy.Synonym_BaseObjectName) AS Child_ObjectID, #tmpObjectHierarchy.HierarchyLvl + 1 AS HierarchyLvl, #tmpObjectHierarchy.Child_DBName AS Child_DBName, #tmpObjectHierarchy.ObjectHierarchyID AS ParentObjectHierarchyID FROM #tmpObjectHierarchy WHERE #tmpObjectHierarchy.Child_Type = 'SN' AND #tmpObjectHierarchy.ObjectHierarchyID > @TmpObjectHierarchyID; IF @Debug = 1 SELECT '2-#tmpObjectHierarchy', * FROM #tmpObjectHierarchy; END IF @Debug = 1 SELECT '3-#tmpObjectHierarchy', * FROM #tmpObjectHierarchy; -- All children of synonyms will be missing a Type. Need to go back through all DBs and materialize these. IF EXISTS(SELECT 1 FROM #tmpObjectHierarchy WHERE Child_Type IS NULL) BEGIN ----- -- For each DB found in #tmpObjectHierarchy, loop through each recordset of each Database DECLARE cursorEachDatabase_2 CURSOR FAST_FORWARD FOR SELECT DISTINCT Child_DBName FROM #tmpObjectHierarchy WHERE Child_Type IS NULL; OPEN cursorEachDatabase_2; FETCH NEXT FROM cursorEachDatabase_2 INTO @DBName WHILE @@FETCH_STATUS = 0 BEGIN IF @Debug = 1 SELECT @DBName AS '@DBName'; SET @sqlCMD = N' UPDATE #tmpObjectHierarchy SET Child_Type = objects.type FROM [@DatabaseName].sys.objects WHERE #tmpObjectHierarchy.Child_DBName = ''@DatabaseName'' AND #tmpObjectHierarchy.Child_Type IS NULL AND #tmpObjectHierarchy.Child_ObjectID = objects.object_id;'; SET @sqlCMD = REPLACE(@sqlCMD, '@DatabaseName', @DBName) IF @Debug = 1 PRINT @sqlCMD; EXECUTE sp_executesql @sqlCMD FETCH NEXT FROM cursorEachDatabase_2 INTO @DBName END CLOSE cursorEachDatabase_2; DEALLOCATE cursorEachDatabase_2; END ----- -- Unflattened Output IF ( @OutputFormat = 'Vertical' OR @OutputFormat = 'All' ) BEGIN SELECT DISTINCT HierarchyLvl, BaseObject_FullName, Child_DBName, Child_FullName, Child_Type, ObjectHierarchyID, ParentObjectHierarchyID FROM #tmpObjectHierarchy ORDER BY HierarchyLvl, BaseObject_FullName, Child_DBName, Child_FullName END ----- -- Dynamically create flattened output IF ( @OutputFormat = 'Horizontal' OR @OutputFormat = 'All' ) BEGIN DECLARE @MaxHierarchyID INT, @HierarchyID INT = 2; SELECT @MaxHierarchyID = MAX(HierarchyLvl) FROM #tmpObjectHierarchy; DECLARE @sqlSelect NVARCHAR(4000) = N' SELECT DISTINCT Lvl1.BaseObject_FullName , Lvl1.HierarchyLvl AS Lvl_1 , Lvl1.Child_FullName AS Obj_1 , Lvl1.Child_Type AS Typ_1', @sqlFrom NVARCHAR(4000) = N' FROM #tmpObjectHierarchy Lvl1'; WHILE @HierarchyID <= @MaxHierarchyID BEGIN SET @sqlSelect = @sqlSelect + N' , COALESCE(Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.HierarchyLvl, ''' + CAST(@HierarchyID AS NVARCHAR(4000)) + ''') AS Lvl_' + CAST(@HierarchyID AS NVARCHAR(4000)) + ' , COALESCE(Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.Child_FullName, SPACE(0)) AS Obj_' + CAST(@HierarchyID AS NVARCHAR(4000)) + ' , COALESCE(Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.Child_Type, SPACE(0)) AS Typ_' + CAST(@HierarchyID AS NVARCHAR(4000)); SET @sqlFrom = @sqlFrom + N' LEFT OUTER JOIN #tmpObjectHierarchy Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + ' ON Lvl' + CAST((@HierarchyID - 1) AS NVARCHAR(4000)) + '.ObjectHierarchyID = Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.ParentObjectHierarchyID AND Lvl' + CAST(@HierarchyID AS NVARCHAR(4000)) + '.HierarchyLvl = ' + CAST(@HierarchyID AS NVARCHAR(4000)); SET @HierarchyID = @HierarchyID + 1; END SET @sqlCMD = @sqlSelect + @sqlFrom + N' WHERE Lvl1.HierarchyLvl = 1 ORDER BY Lvl1.BaseObject_FullName, Lvl1.HierarchyLvl, Lvl1.Child_FullName;'; EXEC sp_executesql @sqlCMD; END ----- -- If enabled, show list of all objects referenced & # of times referenced. IF @ShowObjectCount = 1 BEGIN SELECT CASE WHEN #tmpObjectHierarchy.Child_Type = 'U' THEN 'User Table' WHEN #tmpObjectHierarchy.Child_Type = 'S' THEN 'System Table' WHEN #tmpObjectHierarchy.Child_Type = 'V' THEN 'View' WHEN #tmpObjectHierarchy.Child_Type = 'FN' THEN 'Scalar Function' WHEN #tmpObjectHierarchy.Child_Type = 'IF' THEN 'Inline Function' WHEN #tmpObjectHierarchy.Child_Type = 'TF' THEN 'Table Valued Function' WHEN #tmpObjectHierarchy.Child_Type = 'P' THEN 'Stored Procedure' WHEN #tmpObjectHierarchy.Child_Type = 'SN' THEN 'Synonym' ELSE #tmpObjectHierarchy.Child_Type END AS ObjectType, #tmpObjectHierarchy.Child_FullName AS Object_FullName, COUNT(1) AS ReferencedCount FROM #tmpObjectHierarchy GROUP BY #tmpObjectHierarchy.Child_Type, #tmpObjectHierarchy.Child_FullName ORDER BY ObjectType, #tmpObjectHierarchy.Child_FullName END END TRY BEGIN CATCH IF (SELECT CURSOR_STATUS('global','cursorEachDatabase')) >= -1 BEGIN DEALLOCATE cursorEachDatabase END IF (SELECT CURSOR_STATUS('global','cursorEachDatabase_2')) >= -1 BEGIN DEALLOCATE cursorEachDatabase_2 END DECLARE @ErrorNumber INT, @ErrorSeverity INT, @ErrorState INT, @ErrorProcedure NVARCHAR(128), @ErrorLine INT, @ErrorMessage NVARCHAR(4000); SELECT @ErrorNumber = ERROR_NUMBER(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(), @ErrorProcedure = ERROR_PROCEDURE(), @ErrorLine = ERROR_LINE(), @ErrorMessage = ERROR_MESSAGE(); SELECT @ErrorNumber AS ErrorNumber, @ErrorSeverity AS ErrorSeverity, @ErrorState AS ErrorState, @ErrorProcedure AS ErrorProcedure, @ErrorLine AS ErrorLine, @ErrorMessage AS ErrorMessage; END CATCH END GO