Problem
Gotcha
Before I get to the gotcha, I just want to point out that I am a Software Architect with a ton of exposure to tSql, but I am not a DBA so some of these core SQL concentric problems still allude me at times. I always appreciate these moments of absolute clarity due to epiphany, it’s a good feeling.
Delete Example
Everything is always better with an example so here it is. Imagine you have a huge table that needs to be pruned. You are given a list of criteria, but really it boils down to batch deleting rows from a target table.
DECLARE @batchSize INT = 5000; DECLARE @rowCount INT = 1; WHILE @rowCount > 0 BEGIN DELETE TOP(@batchSize) t FROM dbo.Table1 t SET @rowCount = @@ROWCOUNT; END
The amount being deleted is truly arbitrary, so I just chose 5K for the sake of argument. Let’s say this is a special table that has a lot of things happening to it implicitly and indirectly, much like many things in the magic world of SQL engines. Some of these things are constraints and some of these things are triggers, therefore in order to successfully delete those rows above, these constraints and triggers must be temporarily disabled. This of course leads to a new host of problems such as – should you be running this in single user mode and things of that nature. The answer is yes, now how you convince your co-workers, boss and especially your DBA of this is up to you I cannot help you with this. Especially if you have to tear down replication and rebuild it. Have fun with that.
Toggling Constraints
SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- [dbo].[sp_ToggleConstraints] 1 -- [dbo].[sp_ToggleConstraints] 0 CREATE PROCEDURE [dbo].[sp_ToggleConstraints] ( @isEnabled BIT ) AS BEGIN SET NOCOUNT ON; DECLARE @keywords VARCHAR(50); DECLARE @sqlStatements TABLE ( EntryId INT IDENTITY(0,1) ,SqlStatement VARCHAR(255) ) IF @isEnabled = 1 SET @keywords = ' WITH NOCHECK CHECK CONSTRAINT ' ELSE SET @keywords = ' NOCHECK CONSTRAINT ' /* Examples: Disable: ALTER TABLE dbo.[TargetTableName] NOCHECK CONSTRAINT [FK name] Enable : ALTER TABLE dbo.[TargetTableName] WITH CHECK CHECK CONSTRAINT [FK name] */ -- Select your dynamically created SQL statements for constraints INSERT INTO @sqlStatements SELECT sqlConstraint = 'ALTER TABLE dbo.' + x.TableName + @keywords + x.ConstraintName FROM ( SELECT TableName = t.name ,ConstraintName = fk.name FROM [dbo].[TableTarget] att -- It's a good idea to know what you are targetting INNER JOIN sys.tables t ON t.name = att.TableName INNER JOIN sys.schemas s ON t.schema_id = s.schema_id AND s.name = 'dbo' INNER JOIN sys.foreign_keys fk ON fk.parent_object_id = t.object_id AND fk.delete_referential_action = 1 -- Targetting constraints with cascading deletes only WHERE att.ToggleConstraints = 1 -- Only select tables that should have their constraints toggled -- Sometimes tables are so poorly designed that all constraints have to be toggled UNION SELECT TableName = 'ScrewedUpTable1' ,ConstraintName = 'ALL' UNION SELECT TableName = 'ScrewedUpTable2' ,ConstraintName = 'ALL' ) as x ORDER BY x.TableName --SELECT * FROM @sqlStatements DECLARE @i INT; SET @i = 0; DECLARE @count INT; SELECT @count = COUNT(1) FROM @sqlStatements DECLARE @str VARCHAR(255) WHILE @i < @count BEGIN SELECT @str = SqlStatement FROM @sqlStatements WHERE EntryId = @i; PRINT @str BEGIN TRY EXEC(@str); END TRY BEGIN CATCH -- declare the variables DECLARE @errorNumber INT ,@errorSeverity INT ,@errorState INT ,@errorProcedure nvarchar(128) ,@errorLine INT ,@errorMessage nvarchar(4000) -- Store the values SET @errorNumber = ERROR_NUMBER() SET @errorSeverity = ERROR_SEVERITY() SET @errorState = ERROR_STATE() SET @errorProcedure = ERROR_PROCEDURE() SET @errorLine = ERROR_LINE() SET @errorMessage = ERROR_MESSAGE() -- Check the values SELECT Number = @errorNumber ,Severity = @errorSeverity ,[State] = @errorState ,[Procedure] = @errorProcedure ,Line = @errorLine ,[Message] = @errorMessage -- Log the error EXEC yourLoggingSprocGoesHere_youShouldReallyHaveOnIfYouDont @errorNumber = @errorNumber ,@errorSeverity = @errorSeverity ,@errorState = @errorState ,@errorProcedure = @errorProcedure ,@errorLine = @errorLine ,@errorMessage = @errorMessage END CATCH SET @i = @i + 1; END END GO
Toggling Triggers
SET ANSI_NULLS ON
GO SET QUOTED_IDENTIFIER ON GO -- [dbo].[sp_ToggleTriggers] 1 -- [dbo].[sp_ToggleTriggers] 0 CREATE PROCEDURE [dbo].[sp_ToggleTriggers] ( @isEnabled BIT ) AS BEGIN SET NOCOUNT ON; DECLARE @keyword VARCHAR(7); IF @isEnabled = 1 SET @keyword = 'ENABLE' ELSE SET @keyword = 'DISABLE' DECLARE @sqlStatements TABLE ( EntryId INT IDENTITY(0,1) ,SqlStatement VARCHAR(255) ) /* Examples: ENABLE TRIGGER dbo.[triggerName] ON dbo.[targetTableName] DISABLE TRIGGER dbo.[triggerName] ON dbo.[targetTableName] */ INSERT INTO @sqlStatements SELECT sqlTrigger = @keyword + ' TRIGGER dbo.' + g.name + ' ON dbo.' + t.name FROM [dbo].[TableTarget] att -- It's a good idea to know what you are targetting INNER JOIN sys.tables t ON t.name = att.TableName INNER JOIN sys.schemas s ON t.schema_id = s.schema_id AND s.name = 'dbo' INNER JOIN sys.triggers g ON g.parent_id = t.object_id WHERE g.name LIKE 'trfs%' -- You may have to filter your triggers if you use replication. Mine happen to start with "trfs" ORDER BY g.name --SELECT * FROM @sqlStatements DECLARE @i INT; SET @i = 0; DECLARE @count INT; SELECT @count = COUNT(1) FROM @sqlStatements DECLARE @str VARCHAR(255) WHILE @i < @count BEGIN SELECT @str = SqlStatement FROM @sqlStatements WHERE EntryId = @i; PRINT @str BEGIN TRY EXEC(@str); END TRY BEGIN CATCH -- declare the variables declare @errorNumber INT ,@errorSeverity INT ,@errorState INT ,@errorProcedure nvarchar(128) ,@errorLine INT ,@errorMessage nvarchar(4000) -- Store the values SET @errorNumber = ERROR_NUMBER() SET @errorSeverity = ERROR_SEVERITY() SET @errorState = ERROR_STATE() SET @errorProcedure = ERROR_PROCEDURE() SET @errorLine = ERROR_LINE() SET @errorMessage = ERROR_MESSAGE() -- Check the values SELECT Number = @errorNumber ,Severity = @errorSeverity ,[State] = @errorState ,[Procedure] = @errorProcedure ,Line = @errorLine ,[Message] = @errorMessage -- Log the error EXEC yourLoggingSprocGoesHere_youShouldReallyHaveOnIfYouDont @errorNumber = @errorNumber ,@errorSeverity = @errorSeverity ,@errorState = @errorState ,@errorProcedure = @errorProcedure ,@errorLine = @errorLine ,@errorMessage = @errorMessage END CATCH SET @i = @i + 1; END END GO