Technical Article

Delete records in all related tables

,

Set the @table parameter to the parent table you want to delete from.
Change the @type parameter to either S or D to generate a select or delete query.
The @debug flag is not useful for 'production' use, but it helps you see what's going on if set to 1

As it stands, this script will generate 4 Select queries in the pubs db, 'seeded' with the titles tab

/*

Used to generate Delete scripts. It does NOT run them. 
You need to copy the output of this script to a new window and run it manually.
Run in text mode. 

Use at own risk! 

Set the @table parameter to the parent table you want to delete from. 
Change the @type parameter to either S or D to generate a select or delete query.
The @debug flag is not useful for 'production' use, but it helps you see what's going on if set to 1

As it stands, this script will generate 4 Select queries in the pubs db, 'seeded' with the titles table.

Dave Jackson
http://glossopian.co.uk

31 July 2008

*/
Set Nocount On

Declare @table Varchar(200)
 ,@lvl Int
 ,@where Varchar(2000)
 ,@type Char(1)
 ,@current Varchar(200)
 
--Set a debug flag 1 = True, 0 = False
--if set to true, then select from work tables, show where clause etc.
Declare @debug Int
Set @debug = 0
 
--Set the table we ultimately want to delete from, and the starting level to one
Select @table = 'titles'
 ,@lvl = 1
 
--Choose type of Query to generate
--S = Select, D = Delete
Select @type = Upper('s')
 
--Set the where clause
--leave @where blank ('') if you want to get rid of all records...(be careful people).
--use double single quotes if you need a single one, or use the pipe symbol, '|', 
--without the quotes instead of a 2 single quotes. 
--The next replace statement replaces them with escaped single quote marks
--making the where clause easier to read.
--Note you must qualify the column names using 2 part naming convention.
Select @where = 'where titles.type like |%cook%|'
 
Select @where = Replace(@where,'|','''')
 
If @debug = 1
 --show what we've got
 Select @where
 
--copy the initial table setting for use later
Select @current = @table
 
--drop object if it already exists
If Object_id('tempdb..#hierarchy') Is Not Null
 Drop Table #hierarchy
 
--create work table
Create Table #hierarchy
 (parent_table varchar(200) not null
 ,parent_column varchar(200) not null
 ,child_table varchar(200)
 ,child_column varchar(200)
 ,lvl int not null
 ,CONSTRAINT UIX_parentchild2 
 UNIQUE NONCLUSTERED (parent_table, child_table, parent_column, child_column)
)
Create Clustered Index Cix_parent On #hierarchy (Parent_table)

--drop object if it already exists
If Object_id('tempdb..#done') Is Not Null
 Drop Table #done
 
--create work done table
Create Table #done (
 Tab Varchar(200))

--drop object if it already exists
If Object_id('tempdb..#FkeyDefs') Is Not Null
 Drop Table #fkeydefs
 
--setup working set - we do this once into a working table 
--because it is slow doing this once each iteration of the following loop
--Taken from John Liu's very good script, find it at
--http://www.sqlservercentral.com/scripts/Miscellaneous/61481/
Select 'PK_Table_Name' = Ccu.Table_name
 ,'PK_Column_Name' = Ccu.Column_name
 ,'FK_Table_Name' = Ccu1.Table_name
 ,'FK_Column_Name' = Ccu1.Column_name
Into #fkeydefs
From Information_schema.Constraint_column_usage Ccu
 Inner Join Information_schema.Referential_constraints Rc
 On Ccu.Constraint_name = Rc.Unique_constraint_name
 Inner Join Information_schema.Constraint_column_usage Ccu1
 On Rc.Constraint_name = Ccu1.Constraint_name
Where Ccu.Constraint_name Not In (Select Constraint_name
 From Information_schema.Referential_constraints)
 
Create Clustered Index Cix_pk_table_name2 On #fkeydefs (
 Pk_table_name)

If @debug = 1
 --show what we've got
 Select *
 From #fkeydefs

declare @errs int, @rows int
 
--Infinite loop ;0)
While 1 = 1
 Begin
 Insert #hierarchy
 Select Pk_table_name
 ,Pk_column_name
 ,Fk_table_name
 ,Fk_column_name
 ,@lvl
 From #fkeydefs
 Where Pk_table_name Like @current
 and not Exists(Select H1.Parent_table
 From #hierarchy H1
 Left Join #hierarchy H2
 On H1.Child_table = H2.Parent_table
 And H1.Parent_table = H2.Parent_table
 Where H2.Child_table Is Null 
 And H1.Parent_table = @current)
 select @errs = @@Error, @rows = @@rowcount 

 if @rows = 0
 --This looks to see if there are any more to do
 Begin
 If Not Exists (Select H1.Parent_table
 From #hierarchy H1
 Left Join #hierarchy H2
 On H1.Child_table = H2.Parent_table
 And H1.Parent_table = H2.Parent_table
 Where H2.Child_table Is Null 
 And H1.Child_table Not In (Select Tab
 From #done))
 --There isn't, so quit this loop
 Break
 End
 
 --Keep a record of what we have done, or we'll keep reprocessing when we are at the 
 --last leaf of the tree.
 Insert #done
 Select @current
 
 Set @lvl = @lvl + 1
 
 --Reset the name of the table we are after
 Select @current = H1.Child_table
 From #hierarchy H1
 Left Join #hierarchy H2
 On H1.Child_table = H2.Parent_table
 And H1.Parent_table = H2.Parent_table
 Where H2.Child_table Is Null 
 And H1.Child_table Not In (Select Tab
 From #done)
 
 --Bottom of the While loop, jump back to the top here
 End
 
If @debug = 1
 --show what we've got
 Select *
 From #hierarchy
 Order By Lvl Desc
 
Set Nocount On 

--work out the hierarchy
--taken from the example in BOL (2000)
Declare @line Varchar(255)
 
If Object_id('tempdb..#stack') Is Not Null
 Drop Table #stack

Create Table #stack (Item Char(255), Lvl Int)

If Object_id('tempdb..#stack2') Is Not Null
 Drop Table #stack2

Create Table #stack2 (Item Char(255), Lvl Int)

Insert Into #stack
Values (@table, 1)

Select @lvl = 1

While @lvl > 0
 Begin
 If Exists (Select *
 From #stack
 Where Lvl = @lvl)
 Begin
 Select @table = Item
 From #stack
 Where Lvl = @lvl
 
 --uncomment the next line if you don't want to see the hierarchy unless you are in debug mode...
 --If @debug = 1
 Begin
 Select @line = '--' + Space((@lvl * 4) - 1) + @table
 Print @line
 End
 
 Select @line = @table
 
 If Not Exists (Select 1
 From #stack2
 Where Item = @line)
 Insert Into #stack2
 Select @line
 ,@lvl
 
 Delete From #stack
 Where Lvl = @lvl
 And Item = @table
 
 Insert #stack
 Select Child_table
 ,@lvl + 1
 From #hierarchy
 Where Parent_table = @table
 
 If @@rowcount > 0
 Select @lvl = @lvl + 1
 End
 Else
 Select @lvl = @lvl - 1
 End --Of the WHILE Loop
 
If @debug = 1
 --show what we've got
 Begin
 Select Item
 ,Lvl
 From #stack2
 Order By Lvl Desc
 ,Item Desc
 End
Print '--========================================================='
-- =============================================
-- Declare a READ_ONLY cursor, 
-- funnily enough, the quickest part of this script...
-- =============================================
Print 'Begin Tran
-- rollback
-- commit
-- Note: If there is no FK defined, this script does NOT pick it up.'

Declare BuildSelectDeleteQuery Cursor Read_only For
Select Item
 ,Max(Lvl)
From #stack2
Group By Item
Order By Max(Lvl) Desc
 
Declare @item Varchar(255)
 ,@lvl2 Int
 ,@sql Varchar(8000)
 ,@copysql Varchar(8000)
 ,@joinsql Varchar(8000)

Open BuildSelectDeleteQuery

Fetch Next From BuildSelectDeleteQuery
Into @item,
 @lvl

While (@@fetch_status <> -1)
 Begin
 If (@@fetch_status <> -2)
 Begin
 Select @item = Rtrim(@item)

 Select @lvl2 = @lvl
 
 --Don't muck about with the line breaks, 
 --they are essential for formatting the output
 If @type = 'S'
 Select @sql = Char(13) + 'Select ' + @item + '.* '
 
 If @type = 'D'
 Select @sql = Char(13) + 'Delete ' + @item
 
 Select @sql = @sql + ' from' + Char(13) + ' ' + @item
 Select @joinsql = ''

 While @lvl2 <> 0
 Begin
 Select @copysql = @sql
 Select @sql = @sql + Char(13) + ' inner join ' + Parent_table + Char(13) +
 ' on ' + Parent_table + '.' + Parent_column + ' = ' + Child_table + '.' + Child_column
 From #stack2 S
 Inner Join #hierarchy H
 On S.Item = H.Child_table
 Where H.Child_table = @item
 
 if @@rowcount > 1
 --found more than one, so we need to re-construct the join condition
 --This assumes the compound column keys used in the join are named the same...
 --If they are not, manual intervention will be required
 Begin
 Select top 1 @joinsql = @joinsql + Char(13) + ' inner join ' + Parent_table + ' on ' 
 + Parent_table + '.' + Parent_column + ' = ' + Child_table + '.' + Child_column
 From #stack2 S
 Inner Join #hierarchy H
 On S.Item = H.Child_table
 Where H.Child_table = @item 

 Select @joinsql = @joinsql + Char(13) + ' and '
 + Parent_table + '.' + Parent_column + ' = ' + Child_table + '.' + Child_column
 From #stack2 S
 Inner Join #hierarchy H
 On S.Item = H.Child_table
 Where H.Child_table = @item and H.child_column = H.parent_column
 And patindex('%' + H.child_column + '%', @joinsql) = 0
 
 Select @sql = @copysql + ' ' + @joinSQL
 Select @joinsql = ''
 End

 Select @item = Parent_table
 From #stack2 S
 Inner Join #hierarchy H
 On S.Item = H.Child_table
 Where S.Item = @item
 
 Select @lvl2 = @lvl2 - 1
 End --End of While @lvl2 <> 0

 Print @sql + Char(13) + @where
 End --End of (@@fetch_status <> -2) While
 
 Fetch Next From BuildSelectDeleteQuery
 Into @item, @lvl

 End --End of (@@fetch_status <> -1) While
 
Close BuildSelectDeleteQuery

Deallocate BuildSelectDeleteQuery

Set Nocount Off

Rate

5 (1)

You rated this post out of 5. Change rating

Share

Share

Rate

5 (1)

You rated this post out of 5. Change rating