Thursday 16 August 2018

MS SQL Server Database Dependence Tree using DMV sys.sql_expression_dependencies

-- By Steven Rao
-- On Aug 16, 2018
-- Given a single database object (such as stored procedure, function, view, table), list the dependance tree view of all depedent objects
-- Recusively looking inside DMV sys.sql_expression_dependencies

-- Based on
-- https://www.sqlservercentral.com/Forums/Topic1874321-3077-1.aspx


declare @schemaName varchar(128), @objectName varchar(128), @objectID int

select @schemaName='dbo', @objectName='sp_SRC_MAIN'
--select @schemaName='dbo', @objectName='mp_MPS53B3_AWD_MAIN'

select @objectID=OBJECT_ID(@objectName)

-- debug and verify ObjectID
--select @schemaName as [@schemaName], @objectName as [@objectName], @objectID as [@objectID]

if object_id('tempdb..#base_objects') is not null
  drop table #base_objects

create table #base_objects
(schemaname varchar(128)
,objectname varchar(128)
,objectID int
)



insert into #base_objects
select @schemaName, @objectName, @objectID


;
--------------------------------------------------------------------------------------
-- extend the expression dependance with a more reliable referenced ID
-- (ignore cross database case)
--------------------------------------------------------------------------------------
with DP as
(
-- referencing_id: parent
-- referenced_id: child
-- need to rely on
-- rely on referencing_id and referenced_entity_name
--select * from sys.sql_expression_dependencies where referencing_id is null or referenced_entity_name is null
--select * from sys.sql_expression_dependencies where referenced_id is null
select
*
-- this could give us more depedence data becasue referenced_id could be null
,isnull(referenced_id, OBJECT_ID(referenced_entity_name)) as ReferencedID
from sys.sql_expression_dependencies
)
-- this is CTE to recursivly serach for object dependence
,
DepTree
  (
  top_level_name, referenced_id, referenced_schema, referenced_name, referencing_id, referencing_schema, referencing_name, NestLevel, callstack, typedesc
  )
as
(
-- initialization of the depedence tree
select
schema_name(o.schema_id) + '.' + o.name as top_level_name
, o.object_id as referenced_id
, schema_name(o.schema_id) as referenced_schema
, o.name as referenced_name
, o.object_id as referencing_id
, schema_name(o.schema_id) as referencing_schema
, o.name as referencing_name
, 0 as NestLevel
, cast ('|' + schema_name(o.schema_id) + '.' + o.name + '|' as nvarchar(max)) as callstack
, o.type_desc as typedesc
from sys.objects o
inner join #base_objects ro
on o.object_id=ro.objectID
--ro.schemaname = schema_name(o.schema_id)
--and ro.objectname = o.name
-- using ID for better matching, this can fix issue with 'sp_SRC_MAIN'
--SCHEMA_ID(ro.schemaname)=o.schema_id
--and OBJECT_ID(ro.objectname) = o.object_id

-- recursive expansion of the depedence tree
union all

SELECT
r.top_level_name
, ref.referenced_id
, ref.referenced_schema
, ref.referenced_name
, ref.referencing_id
, ref.referencing_schema
-- Steven Rao
-- is there a way to show the intermediate parent instead of the top level ??
, ref.referencing_name
--, ref.objectname
, ref.NestLevel
, callstack + ref.objectname + '|' as callstack
, cast(ref.typedesc as nvarchar(60)) as typedesc
FROM  DP d1
  INNER JOIN DepTree r
  ON d1.referencing_id =  r.referenced_id

outer apply (
select
ob2.object_id as referenced_id
, schema_name(ob2.schema_id) as referenced_schema
, ob1.name as referenced_name
, schema_name(ob2.schema_id) + '.' + ob2.name as objectname
, ob1.object_id as referencing_id
, schema_name(ob1.schema_id) as referencing_schema
, ob1.name as referencing_name
, NestLevel + 2 as NestLevel
, cast(ob2.type_desc as nvarchar(60)) as typedesc
from sys.synonyms sy1
inner join sys.objects ob1
on ob1.object_id = sy1.object_id
inner join sys.objects ob2
on '[' + schema_name(ob2.schema_id) + '].[' + ob2.name + ']' = sy1.base_object_name
--OBJECT_ID(ob2.name)=OBJECT_ID(sy1.base_object_name)
where --sy1.object_id = d1.referenced_id
sy1.object_id = d1.ReferencedID


   union all

   select
--d1.referenced_id
d1.ReferencedID as referenced_id
, schema_name(ob1.schema_id) as referenced_schema
, ob1.name as referenced_name
, schema_name(ob1.schema_id) + '.' + ob1.name as objectname
, r.referencing_id
, r.referencing_schema
, r.referencing_name
, NestLevel + 1 as NestLevel
, cast(ob1.type_desc as nvarchar(60)) as typedesc
from sys.objects ob1
where ob1.object_id = d1.ReferencedID

union all

select
--d1.referenced_id
d1.ReferencedID as referenced_id
, schema_name(ty1.schema_id) as referenced_schema
, ty1.name as referenced_name
, schema_name(ty1.schema_id) + '.' + ty1.name as objectname
, r.referencing_id
, r.referencing_schema
, r.referencing_name
, NestLevel + 1 as NestLevel
, cast(d1.referenced_class_desc as nvarchar(60)) as typedesc
from sys.table_types ty1
where ty1.user_type_id = ReferencedID
) ref
  where --------------------------------------------
-- need to aviod infinity loop!!
--------------------------------------------
callstack not like '%|' +  ref.objectname + '|%'
-- can we use not in clasue here??
)

select
NestLevel
  ,referenced_schema as ReferencedSchema
,referenced_name as ReferencedObjectName
-- more user friendly display
-- remove first and last |, then replace | with "<="
,replace(left(right(callstack, len(callstack)-1),len(callstack)-2), '|', ' <= ') as ReferencingStack
,typedesc as ReferencingType
/*
-- already shows up in the referencing stack
--,top_level_name as RootObjectName
,referencing_schema
,referencing_name
,referencing_id
,referenced_id
*/
from DepTree dt
where 1=1
--and NestLevel=0
-- ignore top leve which contains exactly one row for self reference
and NestLevel > 0
-------------------------------------------------------------------------------
-- Case 1
-- Stored Procedure Dependence only
and typedesc in ('SQL_STORED_PROCEDURE')
-------------------------------------------------------------------------------
-- Case 2
-- stored procedure and table dependence
--and typedesc in ('SQL_STORED_PROCEDURE', 'USER_TABLE')
-------------------------------------------------------------------------------
-- Case 3
-- all dependence
--and typedesc like 'SQL%' or typedesc in ('USER_TABLE')
-------------------------------------------------------------------------------
order by 1,2,5,3,4
option (maxrecursion 5000);