In SQL Server 2005, can I do a cascade delete without setting the property on my tables?

前端 未结 13 1458
心在旅途
心在旅途 2020-12-12 17:02

I have a database full of customer data. It\'s so big that it\'s really cumbersome to operate on, and I\'d rather just slim it down to 10% of the customers, which is plenty

相关标签:
13条回答
  • 2020-12-12 17:27

    Unless you want to maintain all related queries as proposed by Chris, the ON DELETE CASCADE is by far the quickest and the most direct solution. And if you don't want it to be permanent, why don't you have some T-SQL code that will switch this option on and off like here

    1. remove the original Tbl_A_MyFK constraint (without the ON DELETE CASCADE)

      ALTER TABLE Tbl_A DROP CONSTRAINT Tbl_A_MyFK

    2. set the constraint Tbl_A_MyFK with the ON DELETE CASCADE

      ALTER TABLE Tbl_A ADD CONSTRAINT Tbl_A_MyFK FOREIGN KEY (MyFK) REFERENCES Tbl_B(Column) ON DELETE CASCADE

    3. Here you can do your delete

      DELETE FROM Tbl_A WHERE ...

    4. drop your constraint Tbl_A_MyFK

      ALTER TABLE Tbl_A DROP CONSTRAINT Tbl_A_MyFK

    5. set the constraint Tbl_A_MyFK without the ON DELETE CASCADE

      ALTER TABLE Tbl_A ADD CONSTRAINT Tbl_A_MyFK FOREIGN KEY (MyFK) REFERENCES (Tbl_B)

    0 讨论(0)
  • 2020-12-12 17:28

    Personally if you are going to leave the records in production, I would also leave them in development. Otherwise you may write code that works fine when the recordset is small but times out when faced with the real recordset.

    But if you are determined to do this, I would copy the id field of the records you want to dete from the main table first to a work table. Then I would take each related table and write a delete joining to that worktable to only delete those records. Finish up with the parent table. Make sure this ia written in a script and saved so the next time you want to do a similar thing to your test data, you can easily run it without having to figure out what are the reated tables that need records deleted from them.

    0 讨论(0)
  • 2020-12-12 17:34

    This script has two issues: 1. You must indicate the condition 1=1 in order to delete all table base. 2. This creates the direct relations with the base table only. If the final table has another table parent relation, the the delete fail

    DELETE FROM [dbo].[table2] WHERE TableID in (select [ID] from [dbo].[table3] where 1=1)

    If table2 has a parent relation table1

    0 讨论(0)
  • 2020-12-12 17:37

    Here's a version of the accepted answer optimised for sparsely populated data models. It checks for the existence of data in a FK chain before adding it to the deletion list. I use it to clean up test data.

    Don't use it in an active transactional db- it will hold locks way too long.

    /*
    -- ============================================================================
    -- Purpose: Performs a cascading hard-delete.
    --          Not for use on an active transactional database- it holds locks for too long.
    --          (http://stackoverflow.com/questions/116968/in-sql-server-2005-can-i-do-a-cascade-delete-without-setting-the-property-on-my)
    -- eg:
    exec dbo.hp_Common_Delete 'tblConsumer', 'Surname = ''TestDxOverdueOneReviewWm''', 1
    -- ============================================================================
    */
    create proc [dbo].[hp_Common_Delete]
    (
        @TableName sysname, 
        @Where nvarchar(4000),  -- Shouldn't include 'where' keyword, e.g. Surname = 'smith', NOT where Surname = 'smith'
        @IsDebug bit = 0
    )
    as
    set nocount on
    
    begin try
        -- Prepare tables to store deletion criteria.  
        -- #tmp_to_delete stores criteria that is tested for results before being added to #to_delete
        create table #to_delete
        (
            id int identity(1, 1) primary key not null,
            criteria nvarchar(4000) not null,
            table_name sysname not null,
            processed bit not null default(0)
        )
        create table #tmp_to_delete 
        (
            id int primary key identity(1,1), 
            criteria nvarchar(4000) not null, 
            table_name sysname not null
        )
    
        -- Open a transaction (it'll be a long one- don't use this on production!)
        -- We need a transaction around criteria generation because we only 
        -- retain criteria that has rows in the db, and we don't want that to change under us.
        begin tran
            -- If the top-level table meets the deletion criteria, add it
            declare @Sql nvarchar(4000)
            set @Sql = 'if exists(select top(1) * from ' + @TableName + ' where ' + @Where + ') 
                insert #to_delete (criteria, table_name) values (''' + replace(@Where, '''', '''''') + ''', ''' + @TableName + ''')'
            exec (@Sql)
    
            -- Loop over deletion table, walking foreign keys to generate delete targets
            declare @id int, @tmp_id int, @criteria nvarchar(4000), @new_criteria nvarchar(4000), @table_name sysname, @new_table_name sysname
            while exists(select 1 from #to_delete where processed = 0) 
            begin
                -- Grab table/criteria to work on
                select  top(1) @id = id, 
                        @criteria = criteria, 
                        @table_name = table_name 
                from    #to_delete 
                where   processed = 0 
                order by id desc
    
                -- Insert all immediate child tables into a temp table for processing
                insert  #tmp_to_delete
                select  referencing_column.name + ' in (select [' + referenced_column.name + '] from [' + @table_name +'] where ' + @criteria + ')',
                        referencing_table.name
                from  sys.foreign_key_columns fk
                        inner join sys.columns referencing_column on fk.parent_object_id = referencing_column.object_id 
                                and fk.parent_column_id = referencing_column.column_id 
                        inner join  sys.columns referenced_column on fk.referenced_object_id = referenced_column.object_id 
                                and fk.referenced_column_id = referenced_column.column_id 
                        inner join  sys.objects referencing_table on fk.parent_object_id = referencing_table.object_id 
                        inner join  sys.objects referenced_table on fk.referenced_object_id = referenced_table.object_id 
                        inner join  sys.objects constraint_object on fk.constraint_object_id = constraint_object.object_id
                where referenced_table.name = @table_name
                        and referencing_table.name != referenced_table.name
    
                -- Loop on child table criteria, and insert them into delete table if they have records in the db
                select @tmp_id = max(id) from #tmp_to_delete
                while (@tmp_id >= 1)
                begin
                    select @new_criteria = criteria, @new_table_name = table_name from #tmp_to_delete where id = @tmp_id
                    set @Sql = 'if exists(select top(1) * from ' + @new_table_name + ' where ' + @new_criteria + ') 
                        insert #to_delete (criteria, table_name) values (''' + replace(@new_criteria, '''', '''''') + ''', ''' + @new_table_name + ''')'
                    exec (@Sql)
    
                    set @tmp_id = @tmp_id - 1
                end
                truncate table #tmp_to_delete
    
                -- Move to next record
                update  #to_delete 
                set     processed = 1
                where   id = @id
            end
    
            -- We have a list of all tables requiring deletion.  Actually delete now.
            select @id = max(id) from #to_delete 
            while (@id >= 1)
            begin
                select @criteria = criteria, @table_name = table_name from #to_delete where id = @id
                set @Sql = 'delete from [' + @table_name + '] where ' + @criteria
                if (@IsDebug = 1) print @Sql
                exec (@Sql)
    
                -- Next record
                set @id = @id - 1
            end
        commit
    end try
    
    begin catch
        -- Any error results in a rollback of the entire job
        if (@@trancount > 0) rollback
    
        declare @message nvarchar(2047), @errorProcedure nvarchar(126), @errorMessage nvarchar(2048), @errorNumber int, @errorSeverity int, @errorState int, @errorLine int
        select  @errorProcedure = isnull(error_procedure(), N'hp_Common_Delete'), 
                @errorMessage = isnull(error_message(), N'hp_Common_Delete unable to determine error message'), 
                @errorNumber = error_number(), @errorSeverity = error_severity(), @errorState = error_state(), @errorLine = error_line()
    
        -- Prepare error information as it would be output in SQL Mgt Studio
        declare @event nvarchar(2047)
        select  @event =    'Msg ' + isnull(cast(@errorNumber as varchar), 'null') + 
                            ', Level ' + isnull(cast(@errorSeverity as varchar), 'null') + 
                            ', State ' + isnull(cast(@errorState as varchar), 'null') + 
                            ', Procedure ' + isnull(@errorProcedure, 'null') + 
                            ', Line ' + isnull(cast(@errorLine as varchar), 'null') + 
                            ': ' + isnull(@errorMessage, '@ErrorMessage null')
        print   @event
    
        -- Re-raise error to ensure admin/job runners understand there was a failure
        raiserror(@errorMessage, @errorSeverity, @errorState)
    end catch
    
    0 讨论(0)
  • 2020-12-12 17:38

    Go into SQL Server Management Studio and right-click the database. Select Tasks->Generate Scripts. Click Next twice. On the Options window choose set it to generate CREATE statements only, and put everything to False except for the Foreign Keys. Click Next. Select Tables and Click Next again. Click the "Select All" button and click Next then Finish and send the script to your choice of a query window or file (don't use the clipboard, since it might be a big script). Now remove all of the script that adds the tables and you should be left with a script to create your foreign keys.

    Make a copy of that script because it is how you'll restore your database to its current state. Use a search and replace to add the ON DELETE CASCADE to the end of each constraint. This might vary depending on how your FKs are currently set up and you might need to do some manual editing.

    Repeat the script generation, but this time set it to generate DROP statements only. Be sure to manually remove the table drops that are generated. Run the drops, then run your edited creates to make them all cascade on delete. Do your deletes, run the drop script again and then run the script that you saved off at the start.

    Also - MAKE A BACKUP OF YOUR DB FIRST! Even if it's just a dev database, it will save you some headache if part of the script isn't quite right.

    Hope this helps!

    BTW - you should definitely do some testing with your full test data as another poster suggested, but I can see why you might not need that for initial development. Just don't forget to include that as part of QA at some point.

    0 讨论(0)
  • 2020-12-12 17:39

    Taking the accepted answer a bit further, I had the need to do this across tables in different schemas. I have updated the script to include schema in the outputted delete scripts.

    CREATE PROCEDURE usp_delete_cascade (
            @base_table_schema varchar(100), @base_table_name varchar(200), @base_criteria nvarchar(1000)
    )
    as begin
    
            -- Expects the name of a table, and a conditional for selecting rows
            -- within that table that you want deleted.
            -- Produces SQL that, when run, deletes all table rows referencing the ones
            -- you initially selected, cascading into any number of tables,
            -- without the need for "ON DELETE CASCADE".
            -- Does not appear to work with self-referencing tables, but it will
            -- delete everything beneath them.
            -- To make it easy on the server, put a "GO" statement between each line.
    
            declare @to_delete table (
                    id int identity(1, 1) primary key not null,
                    criteria nvarchar(1000) not null,
                    table_schema varchar(100),
                    table_name varchar(200) not null,
                    processed bit not null,
                    delete_sql varchar(1000)
            )
    
            insert into @to_delete (criteria, table_schema, table_name, processed) values (@base_criteria, @base_table_schema, @base_table_name, 0)
    
            declare @id int, @criteria nvarchar(1000), @table_name varchar(200), @table_schema varchar(100)
            while exists(select 1 from @to_delete where processed = 0) begin
                    select top 1 @id = id, @criteria = criteria, @table_name = table_name, @table_schema = table_schema from @to_delete where processed = 0 order by id desc
    
                    insert into @to_delete (criteria, table_schema, table_name, processed)
                            select referencing_column.name + ' in (select [' + referenced_column.name + '] from [' + @table_schema + '].[' + @table_name +'] where ' + @criteria + ')',
                                    schematable.name,
                                    referencing_table.name,
                                    0
                            from  sys.foreign_key_columns fk
                                    inner join sys.columns referencing_column on fk.parent_object_id = referencing_column.object_id 
                                            and fk.parent_column_id = referencing_column.column_id 
                                    inner join  sys.columns referenced_column on fk.referenced_object_id = referenced_column.object_id 
                                            and fk.referenced_column_id = referenced_column.column_id 
                                    inner join  sys.objects referencing_table on fk.parent_object_id = referencing_table.object_id 
                                    inner join sys.schemas schematable on referencing_table.schema_id = schematable.schema_id
                                    inner join  sys.objects referenced_table on fk.referenced_object_id = referenced_table.object_id 
                                    inner join  sys.objects constraint_object on fk.constraint_object_id = constraint_object.object_id
                            where referenced_table.name = @table_name
                                    and referencing_table.name != referenced_table.name
    
                    update @to_delete set
                            processed = 1
                    where id = @id
            end
    
            select 'print ''deleting from ' + table_name + '...''; delete from [' + table_schema + '].[' + table_name + '] where ' + criteria from @to_delete order by id desc
    end
    
    exec usp_delete_cascade 'schema', 'RootTable', 'Id = 123'
    exec usp_delete_cascade 'schema', 'RootTable', 'GuidId = ''A7202F84-FA57-4355-B499-1F8718E29058'''
    
    0 讨论(0)
提交回复
热议问题