varias tablas relacionados registros referencial puedo modificar los integridad eliminar cascada borrar sql-server sql-server-2005 cascade

sql-server - tablas - integridad referencial sql server



En SQL Server 2005, ¿puedo hacer una eliminación en cascada sin configurar la propiedad en mis tablas? (13)

Tengo una base de datos llena de datos de clientes. Es tan grande que es muy difícil de operar, y prefiero limitarlo al 10% de los clientes, que es mucho para el desarrollo. Tengo muchísimas mesas y no quiero alterarlas con "ON DELETE CASCADE", especialmente porque es un trato de una sola vez.

¿Puedo hacer una operación de eliminación que conecte en cascada a través de todas mis tablas sin configurarlas primero? Si no, ¿cuál es mi mejor opción?


Mi sugerencia es seguir adelante y escribir una secuencia de comandos que agregará la cascada de eliminación a cada relación en la base de datos al exportar una lista de relaciones modificadas. Luego puede invertir el proceso y eliminar el comando borrar cascada en cada tabla de la lista.


Normalmente solo escribo las consultas para eliminar los registros que no quiero y guardo eso como un archivo .sql para referencia futura. El pseudocódigo es:

  1. seleccionar identificadores de registros de la tabla principal que deseo eliminar en una tabla temporal
  2. escriba una consulta de eliminación para cada tabla relacionada que se una a la tabla temporal.
  3. escriba una consulta de eliminación para la tabla principal que se une a mi tabla temporal.

Personalmente, si van a dejar los registros en producción, también los dejaré en desarrollo. De lo contrario, puede escribir un código que funciona bien cuando el conjunto de registros es pequeño pero agota el tiempo cuando se enfrenta al conjunto de registros real.

Pero si está decidido a hacer esto, copiaría el campo de identificación de los registros que desea detectar de la tabla principal primero a una tabla de trabajo. Luego tomaría cada tabla relacionada y escribiría una unión de eliminación en esa tabla de trabajo para eliminar solo esos registros. Termine con la tabla padre. Asegúrese de que está escrito en un script y guardado para que la próxima vez que quiera hacer algo similar a sus datos de prueba, pueda ejecutarlo fácilmente sin tener que averiguar cuáles son las tablas procesadas que necesitan registros eliminados de ellos.


Vaya a SQL Server Management Studio y haga clic con el botón derecho en la base de datos. Seleccione Tareas-> Generar secuencias de comandos. Haga clic en Siguiente dos veces. En la ventana Opciones, elija establecerlo para generar solo sentencias CREATE y poner todo en False, excepto para las claves externas. Haga clic en Siguiente. Seleccione Tablas y haga clic en Siguiente nuevamente. Haga clic en el botón "Seleccionar todo" y haga clic en Siguiente, luego en Finalizar y envíe el script a su elección de una ventana o archivo de consulta (no use el portapapeles, ya que podría ser un script grande). Ahora elimine todo el script que agrega las tablas y le debe dejar una secuencia de comandos para crear sus claves externas.

Haga una copia de esa secuencia de comandos porque es la forma en que restaurará su base de datos a su estado actual. Use una búsqueda y reemplace para agregar ON DELETE CASCADE al final de cada restricción. Esto puede variar dependiendo de cómo estén configurados tus FK y es posible que debas hacer alguna edición manual.

Repita la generación del script, pero esta vez configúrelo para generar declaraciones DROP solamente. Asegúrese de eliminar manualmente las gotas de la tabla que se generan . Ejecute las gotas, luego ejecute las creaciones editadas para hacer que todas sean en cascada al eliminar. Haga sus borrados, ejecute el guión drop nuevamente y luego ejecute el script que guardó al inicio.

Además, ¡ HAGA UNA COPIA DE SEGURIDAD DE SU BD PRIMERO! Incluso si solo se trata de una base de datos de desarrollo, le ahorrará algo de dolor de cabeza si parte de la secuencia de comandos no es del todo correcta.

¡Espero que esto ayude!

Por cierto, definitivamente deberías hacer algunas pruebas con tus datos de prueba completos, como sugirió otro afiche, pero puedo ver por qué no necesitarías eso para el desarrollo inicial. Simplemente no olvide incluir eso como parte de QA en algún momento.


Combinando sus consejos y un script que encontré en línea, hice un procedimiento que producirá SQL que puede ejecutar para realizar una eliminación en cascada independientemente de ON DELETE CASCADE . Probablemente fue una gran pérdida de tiempo, pero pasé un buen rato escribiéndolo. Una ventaja de hacerlo de esta manera es que puede colocar una declaración GO entre cada línea y no tiene que ser una transacción grande. El original fue un procedimiento recursivo; este desenrolla la recursión en una tabla de pila.

create procedure usp_delete_cascade ( @base_table_name varchar(200), @base_criteria nvarchar(1000) ) as begin -- Adapted from http://www.sqlteam.com/article/performing-a-cascade-delete-in-sql-server-7 -- 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_name varchar(200) not null, processed bit not null, delete_sql varchar(1000) ) insert into @to_delete (criteria, table_name, processed) values (@base_criteria, @base_table_name, 0) declare @id int, @criteria nvarchar(1000), @table_name varchar(200) while exists(select 1 from @to_delete where processed = 0) begin select top 1 @id = id, @criteria = criteria, @table_name = table_name from @to_delete where processed = 0 order by id desc insert into @to_delete (criteria, table_name, processed) select referencing_column.name + '' in (select ['' + referenced_column.name + ''] from ['' + @table_name +''] where '' + @criteria + '')'', 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.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_name + ''] where '' + criteria from @to_delete order by id desc end exec usp_delete_cascade ''root_table_name'', ''id = 123''


A menos que desee mantener todas las consultas relacionadas según lo propuesto por Chris, ON DELETE CASCADE es, de lejos, la solución más rápida y directa. Y si no quiere que sea permanente, ¿por qué no tiene un código T-SQL que activará y desactivará esta opción como aquí?

  1. elimine la restricción Tbl_A_MyFK original (sin ON DELETE CASCADE)

    ALTER TABLE Tbl_A DROP CONSTRAINT Tbl_A_MyFK

  2. establece la restricción Tbl_A_MyFK con ON DELETE CASCADE

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

  3. Aquí puedes hacer tu eliminación

    DELETE FROM Tbl_A WHERE ...

  4. suelta tu restricción Tbl_A_MyFK

    ALTER TABLE Tbl_A DROP CONSTRAINT Tbl_A_MyFK

  5. establecer la restricción Tbl_A_MyFK sin ON DELETE CASCADE

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


después de seleccionar, debe compilar y ejecutar la eliminación real

declare @deleteSql nvarchar(1200) declare delete_cursor cursor for select table_name, criteria from @to_delete order by id desc open delete_cursor fetch next from delete_cursor into @table_name, @criteria while @@fetch_status = 0 begin select @deleteSql = ''delete from '' + @table_name + '' where '' + @criteria --print @deleteSql -- exec sp_execute @deleteSql EXEC SP_EXECUTESQL @deleteSql fetch next from delete_cursor into @table_name, @criteria end close delete_cursor deallocate delete_cursor


Tomando la respuesta aceptada un poco más, tuve la necesidad de hacer esto a través de tablas en diferentes esquemas. He actualizado la secuencia de comandos para incluir el esquema en las secuencias de comandos de eliminación de salida.

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''''''


La publicación de Kevin está incompleta, su t-sql sp solo imprime el comando, para ejecutar estos comandos, antes del último fin agrega esto

DECLARE @commandText VARCHAR(8000) DECLARE curDeletes CURSOR FOR select ''delete from ['' + table_name + ''] where '' + criteria from @to_delete order by id desc OPEN curDeletes FETCH NEXT FROM curDeletes INTO @commandText WHILE(@@FETCH_STATUS=0) BEGIN EXEC (@commandText) FETCH NEXT FROM curDeletes INTO @commandText END CLOSE curDeletes DEALLOCATE curDeletes


Aquí hay una versión de la respuesta aceptada optimizada para modelos de datos escasamente poblados. Comprueba la existencia de datos en una cadena FK antes de agregarlo a la lista de eliminación. Lo uso para limpiar datos de prueba.

No lo use en un db transaccional activo: mantendrá los bloqueos demasiado tiempo.

/* -- ============================================================================ -- Purpose: Performs a cascading hard-delete. -- Not for use on an active transactional database- it holds locks for too long. -- (http://.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


Expansión de la respuesta de croisharp para tener en cuenta factores desencadenantes, es decir, solución consciente de esquemas que deshabilita todos los desencadenantes que afectan, elimina filas y activa los desencadenadores.

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 DECLARE @commandText VARCHAR(8000), @triggerOn VARCHAR(8000), @triggerOff VARCHAR(8000) DECLARE curDeletes CURSOR FOR select ''DELETE FROM ['' + table_schema + ''].['' + table_name + ''] WHERE '' + criteria, ''ALTER TABLE ['' + table_schema + ''].['' + table_name + ''] DISABLE TRIGGER ALL'', ''ALTER TABLE ['' + table_schema + ''].['' + table_name + ''] ENABLE TRIGGER ALL'' from @to_delete order by id desc OPEN curDeletes FETCH NEXT FROM curDeletes INTO @commandText, @triggerOff, @triggerOn WHILE(@@FETCH_STATUS=0) BEGIN EXEC (@triggerOff) EXEC (@commandText) EXEC (@triggerOn) FETCH NEXT FROM curDeletes INTO @commandText, @triggerOff, @triggerOn END CLOSE curDeletes DEALLOCATE curDeletes end


Este script tiene dos problemas: 1. Debe indicar la condición 1 = 1 para eliminar toda la base de la tabla. 2. Esto crea las relaciones directas con la tabla base solamente. Si la tabla final tiene otra relación primaria de tabla, la eliminación falla

ELIMINAR DE [dbo]. [Table2] WHERE TableID in (seleccionar [ID] de [dbo]. [Table3] donde 1 = 1)

Si table2 tiene una relación padre table1


Publique aquí un script que funcionará con claves externas que contenga más de una columna.

create procedure usp_delete_cascade ( @TableName varchar(200), @Where nvarchar(1000) ) as begin declare @to_delete table ( id int identity(1, 1) primary key not null, criteria nvarchar(1000) not null, table_name varchar(200) not null, processed bit not null default(0), delete_sql varchar(1000) ) DECLARE @MyCursor CURSOR declare @referencing_column_name varchar(1000) declare @referencing_table_name varchar(1000) declare @Sql nvarchar(4000) insert into @to_delete (criteria, table_name) values ('''', @TableName) declare @id int, @criteria nvarchar(1000), @table_name varchar(200) while exists(select 1 from @to_delete where processed = 0) begin select top 1 @id = id, @criteria = criteria, @table_name = table_name from @to_delete where processed = 0 order by id desc SET @MyCursor = CURSOR FAST_FORWARD FOR select referencing_column.name as column_name, referencing_table.name as 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 OPEN @MyCursor FETCH NEXT FROM @MYCursor INTO @referencing_column_name, @referencing_table_name WHILE @@FETCH_STATUS = 0 BEGIN PRINT @referencing_column_name PRINT @referencing_table_name update @to_delete set criteria = criteria + '' AND ''+@table_name+''.''+@referencing_column_name+''=''+ @referencing_table_name+''.''+@referencing_column_name where table_name = @referencing_table_name if(@@ROWCOUNT = 0) BEGIN --if(@id <> 1) --BEGIN insert into @to_delete (criteria, table_name) VALUES( '' LEFT JOIN ''+@table_name+'' ON ''+@table_name+''.''+@referencing_column_name+''=''+ @referencing_table_name+''.''+@referencing_column_name+ @criteria, @referencing_table_name ) --END --ELSE --BEGIN --insert into @to_delete (criteria, table_name) --VALUES( '' LEFT JOIN ''+@table_name+'' ON ''+@table_name+''.''+@referencing_column_name+''=''+ @referencing_table_name+''.''+@referencing_column_name, --@referencing_table_name --) --END END FETCH NEXT FROM @MYCursor INTO @referencing_column_name, @referencing_table_name END CLOSE @MyCursor DEALLOCATE @MyCursor update @to_delete set processed = 1 where id = @id end --select ''print ''''deleting from '' + table_name + ''...''''; delete from ['' + table_name + ''] where '' + criteria from @to_delete order by id desc --select id, table_name, criteria, @Where from @to_delete order by id desc 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 ['' + @table_name + ''] from ['' + @table_name + ''] '' + @criteria+'' WHERE ''+@Where exec (@Sql) PRINT @Sql -- Next record set @id = @id - 1 end end