Data purge on Foreign key connected tables
There was a requirement to delete data from a lot of tables connected with foreign keys without disabling the constrain or using CASCADE feature. Hence here is a simple technique applicable for smaller tables. For large tables, data has to be purged in batches. I am still working on that part at the moment. I used AdventureWorks sample database for this project.
Note: If you are in a hurry to copy-paste and execute the script, scroll down to the bottom of this post and copy the final script. All other scripts above are only sections of the code to help you understand the functioning of the final script and explains how I built the final script.
Below join will fetch all child table details
-- childTableSchema, childTable, childKeyName, childColumnName, parentTable SELECT schema_name(fk.schema_id) childTableSchema, object_name(fk.parent_object_id) childTable, fk.name childKeyName, cl.name childColumnName, parent.name parentTable, (select name from sys.columns where column_id = fkc.constraint_column_id and object_id = fkc.referenced_object_id) as parentColumnName FROM sys.tables parent join sys.foreign_keys fk on fk.referenced_object_id = parent.object_id join sys.foreign_key_columns fkc on fkc.parent_object_id = fk.parent_object_id and fk.object_id = fkc.constraint_object_id join sys.columns cl on cl.object_id = fk.parent_object_id and cl.column_id = fkc.parent_column_id and fkc.referenced_object_id = parent.object_id where parent.name = 'Address'
Above join is then passed through a loop to iterate and populate a dependency tree in a table.
set nocount on
IF OBJECT_ID('tempdb.dbo.#fkeys') IS NOT NULL
begin
drop table #fkeys
end
create table #fkeys (id int identity(1,1),
childTableSchema sysname,
childTable sysname,
childKeyName sysname,
childColumnName sysname,
parentTableSchema sysname,
parentTable sysname,
parentColumnName sysname default NULL NULL,
tLevel int default 0,
done bit default 0)
declare @pTableSchema sysname, @pTableName sysname,
@pTableColumnName sysname,
@pTableKeyName sysname, @tLevel int = 0
set @pTableSchema = 'dbo'
set @pTableName = 'entity'
insert into #fkeys(childTableSchema, childTable, childKeyName, childColumnName, parentTableSchema, parentTable)
select @pTableSchema, @pTableName, ind.name, cl.name, 0, 0 from sys.indexes ind join sys.index_columns icl on ind.object_id = icl.object_id
and ind.index_id = icl.index_id
join sys.columns cl on icl.column_id = cl.column_id and cl.object_id = icl.object_id
where is_primary_key = 1 and ind.object_id = object_id(@pTableSchema+'.'+@pTableName)
--select * from #fkeys
declare @pTableId int
while ((select count(1) from #fkeys where done = 0)>0)
begin
select top 1 @pTableId = id, @pTableSchema= childTableSchema, @pTableName = childTable, @pTableKeyName = childKeyName
, @pTableColumnName = childColumnName, @tLevel = tLevel
from #fkeys where done = 0
insert into #fkeys (childTableSchema, childTable, childKeyName, childColumnName, parentTableSchema, parentTable, parentColumnName, tLevel)
-- childTableSchema, childTable, childKeyName, childColumnName, parentTable
SELECT
schema_name(fk.schema_id) childTableSchema,
object_name(fk.parent_object_id) childTable,
fk.name childKeyName,
cl.name childColumnName,
@pTableSchema parentTableSchema,
@pTableName parentTable,
(select name from sys.columns where column_id = fkc.constraint_column_id and object_id = fkc.referenced_object_id) as parentColumnName,
(@tLevel+1) currentLevel
FROM
sys.tables parent
join
sys.foreign_keys fk
on fk.referenced_object_id = parent.object_id
join sys.foreign_key_columns fkc
on fkc.parent_object_id = fk.parent_object_id
and fk.object_id = fkc.constraint_object_id
join sys.columns cl
on cl.object_id = fk.parent_object_id
and cl.column_id = fkc.parent_column_id
and fkc.referenced_object_id = parent.object_id
where parent.name = @pTableName
update #fkeys set done = 1 where childTableSchema = @pTableSchema and childTable = @pTableName
end
select * from #fkeys
set nocount off
Data in the tree is then used to generate delete statements that could be executed in the opposite order without any issues.
set nocount on
IF OBJECT_ID('tempdb.dbo.#fkeys') IS NOT NULL
begin
drop table #fkeys
end
create table #fkeys (id int identity(1,1),
childTableSchema sysname,
childTable sysname,
childKeyName sysname,
childColumnName sysname,
parentTableSchema sysname,
parentTable sysname,
parentColumnName sysname default NULL NULL,
tLevel int default 0,
done bit default 0)
declare @pTableSchema sysname, @pTableName sysname,
@pTableColumnName sysname,
@pTableKeyName sysname, @tLevel int = 0
set @pTableSchema = 'dbo'
set @pTableName = 'entity'
insert into #fkeys(childTableSchema, childTable, childKeyName, childColumnName, parentTableSchema, parentTable)
select @pTableSchema, @pTableName, ind.name, cl.name, 0, 0 from sys.indexes ind join sys.index_columns icl on ind.object_id = icl.object_id
and ind.index_id = icl.index_id
join sys.columns cl on icl.column_id = cl.column_id and cl.object_id = icl.object_id
where is_primary_key = 1 and ind.object_id = object_id(@pTableSchema+'.'+@pTableName)
--select * from #fkeys
declare @pTableId int
while ((select count(1) from #fkeys where done = 0)>0)
begin
select top 1 @pTableId = id, @pTableSchema= childTableSchema, @pTableName = childTable, @pTableKeyName = childKeyName
, @pTableColumnName = childColumnName, @tLevel = tLevel
from #fkeys where done = 0
insert into #fkeys (childTableSchema, childTable, childKeyName, childColumnName, parentTableSchema, parentTable, parentColumnName, tLevel)
-- childTableSchema, childTable, childKeyName, childColumnName, parentTable
SELECT
schema_name(fk.schema_id) childTableSchema,
object_name(fk.parent_object_id) childTable,
fk.name childKeyName,
cl.name childColumnName,
@pTableSchema parentTableSchema,
@pTableName parentTable,
(select name from sys.columns where column_id = fkc.constraint_column_id and object_id = fkc.referenced_object_id) as parentColumnName,
(@tLevel+1) currentLevel
FROM
sys.tables parent
join
sys.foreign_keys fk
on fk.referenced_object_id = parent.object_id
join sys.foreign_key_columns fkc
on fkc.parent_object_id = fk.parent_object_id
and fk.object_id = fkc.constraint_object_id
join sys.columns cl
on cl.object_id = fk.parent_object_id
and cl.column_id = fkc.parent_column_id
and fkc.referenced_object_id = parent.object_id
where parent.name = @pTableName
update #fkeys set done = 1 where childTableSchema = @pTableSchema and childTable = @pTableName
end
select * from #fkeys
select 'delete c from '+childTableSchema+'.'+childTable+' c join '+ parentTableSchema+'.'+parentTable
+' p on c.'+childColumnName+'=p.'+parentColumnName
from #fkeys order by id desc
set nocount off