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