Technical Article

Show Cascade Delete Tree

,

Often developers come to me wanting cascade deletes implemented through foreign key definitions. With this stored procedure I can quickly show them the child tables involved and which ones need changes in their foreign key definitions from 'no action' to 'cascade'.

Create the ShowDeleteTree procedure in any database and call it with a table name for example:

exec ShowDeleteTree 'PurchaseOrderHeaders'

Use text output so you can see the tree of foreign key tables involved. You may optionally pass a number from 1 to 9 of the depth you want to see. This limits output to something manageable since larger databases often result in pages of output.

Most databases contain a mixture of child deletes through triggers and through foreign keys. This procedure will help with the declarative referential integrity half. You'll still need to look through trigger code for the other half.

 

The output below is from running the following command in msdb:

exec ShowDeleteTree DTA_reports_database

 

IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[ShowDeleteTree]') AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE [dbo].[ShowDeleteTree]
GO

CREATE PROCEDURE dbo.ShowDeleteTree
@TableName varchar(128),
@PrintDepth int = 9
AS  

-- print tree of dependencies for any table
declare 
@p varchar(128),
@c1 varchar(128),
@c2 varchar(128),
@c3 varchar(128),
@c4 varchar(128),
@c5 varchar(128),
@c6 varchar(128),
@c7 varchar(128),
@delaction varchar(20),
@char9 varchar(10)


set @p = @TableName
set @char9 = '-->'

-- view as text

-- hierarchy -------------------------------------------------------------
set nocount on
declare @loop int
declare @dep table( child varchar(100), parent varchar(100), pass int, delaction varchar(20))

-- dependency list
insert into @dep
select distinct object_name(parent_object_id), object_name(referenced_object_id), 0, delete_referential_action_desc
from sys.foreign_keys 
where parent_object_id <> referenced_object_id
order by 1,2



-- bottom: children that are never parents
update
@dep
set
pass = 1
where
child not in 
(
select 
parent
from
@dep
)


-- middle to top: parents where children have been processed
set @loop = 2
while @loop < 10
begin
update
@dep
set
pass = @loop
where
pass = 0
and
parent in 
(
-- select parents where all children have been processed
select
parent
from
@dep d1
where
pass = 0
and
not exists
(select * from @dep d2 where d1.child=d2.parent and d2.pass=0)
)

set @loop = @loop + 1
end

select parent as 'ImmediateParent', delaction as 'DeleteAction' from @dep where child = @p
print ''

print '-- children of'
print @p

if exists (select * 
from sys.tables t join sys.triggers tr on t.object_id=tr.parent_id 
where t.name = @p
and tr.is_instead_of_trigger=1
and objectproperty(tr.object_id,'ExecIsDeleteTrigger')=1)
print 'Has "Instead Of Delete" Trigger so parents cannot cascade delete it.

'


--1
select @c1=min(child) from @dep where parent = @p
while @c1 is not null
begin
select @delaction = delaction from @dep where child=@c1 and parent=@p
if @printdepth >=1 print @char9+@c1+' '+@delaction
--2
select @c2=min(child) from @dep where parent = @c1
while @c2 is not null
begin
select @delaction = delaction from @dep where child=@c2 and parent=@c1
if @printdepth >=2 print @char9+@char9+@c2+' '+@delaction
--3
select @c3=min(child) from @dep where parent = @c2
while @c3 is not null
begin
select @delaction = delaction from @dep where child=@c3 and parent=@c2
if @printdepth >=3 print @char9+@char9+@char9+@c3+' '+@delaction
--4
select @c4=min(child) from @dep where parent = @c3
while @c4 is not null
begin
select @delaction = delaction from @dep where child=@c4 and parent=@c3
if @printdepth >=4 print @char9+@char9+@char9+@char9+@c4+' '+@delaction
--5
select @c5=min(child) from @dep where parent = @c4
while @c5 is not null
begin
select @delaction = delaction from @dep where child=@c5 and parent=@c4
if @printdepth >=5 print @char9+@char9+@char9+@char9+@char9+@c5+' '+@delaction
--6
select @c6=min(child) from @dep where parent = @c5
while @c6 is not null
begin
select @delaction = delaction from @dep where child=@c6 and parent=@c5
if @printdepth >=6 print @char9+@char9+@char9+@char9+@char9+@char9+@c6+' '+@delaction
--7
select @c7=min(child) from @dep where parent = @c6
while @c7 is not null
begin
select @delaction = delaction from @dep where child=@c7 and parent=@c6
if @printdepth >=7 print @char9+@char9+@char9+@char9+@char9+@char9+@char9+@c7+' '+@delaction
select @c7=min(child) from @dep where parent = @c6 and child > @c7
end
--7
select @c6=min(child) from @dep where parent = @c5 and child > @c6
end
--6
select @c5=min(child) from @dep where parent = @c4 and child > @c5
end
--5
select @c4=min(child) from @dep where parent = @c3 and child > @c4
end
--4
select @c3=min(child) from @dep where parent = @c2 and child > @c3
end
--3
select @c2=min(child) from @dep where parent = @c1 and child > @c2
end
--2
select @c1=min(child) from @dep where parent = @p and child > @c1
end
--1

return 0
GO

Rate

4 (2)

You rated this post out of 5. Change rating

Share

Share

Rate

4 (2)

You rated this post out of 5. Change rating