Monday, August 25, 2014

T-SQL Tips: Be Cautious With Your Delete Queries

This blog speaks about some of the critical things you need to be aware of while implementing a delete logic for your project.
The impetus for writing the blog is a recent experience we had due to a small oversight in one of the delete code implemented by a fellow team member. By the time we realized the mistake, considerable amount of the damage was already done and we had to do a good amount of work to put everything back in place.
Now onto the details.
There are few things that need to taken care of when you're developing a DELETE procedure to clean up the data in your project. The main checks that you need to do can be summed up as below

1. Make sure the logic is correct
This is the basic thing that you need to take care of :) . Do a part to part comparison of the DELETE query logic against the actual requirement and make sure the query covers all the conditions as stated by the requirement

2. Test with a sample data
Make sure you have a sandbox with sample data available in which you can test the delete logic and make sure it works exactly as per your requirement.
In cases were you don't have such a setup available i.e. just have a single environment to work with (which is extremely dangerous IMO!)  you can follow the below approach
Wrap the script within a dummy transaction as below



--COMMIT TRAN TestDelete

Check the output from the table after the delete operation and if it matches your expectation uncomment and execute the COMMIT TRAN statement. Otherwise uncomment and execute the ROLLBACK TRAN statement.
This can be applied in the cases where you want to apply manual delete script.
In case of automated jobs you can do a different approach as the above is not possible.
You can make use of OUTPUT clause to capture the contents of the DELETED table to another staging table and check it after the DELETE. If by any chance it doesn't match you expectation you can ROLLBACK the transaction otherwise you may COMMIT it.
The stub will look like below


FROM Table t

IF (SELECT COUNT(*) FROM OtherTable) =

3. Always fully qualify the object names used in DELETE statement
This is the most critical of the steps and the one which gave us the maximum trouble with. Make sure you use appropriate aliases and fully qualify the columns otherwise you may end up flushing out the entire data of the table as per below. This is what happened to us and we had to spend good amount of time to rework and put the lost data back in place.
See the below illustration to understand the issue

declare @t table
id int,
val varchar(50)

insert @t
values (1,'Test'),

declare @t1 table
Col1 int,
col2 int
insert @t1
values (12,123),(2,234),(5,213)

select * from @t
select * from @t1

from @t
where id in (select id from @t1)

select * from @t
select * from @t1

If you see the code above carefully it has a wrong column placed within the subquery. Little did we realize what this would cause to us.
Checking the output you can see that it will delete all the records from the outer table @t as it will act as a trivial condition check.
This behavior is by design and is explained in the KB article below

Rewriting the query as below you can see that it will complain on the wrong column

from @t t
where in (select from @t1 t1)

Which will avoid any possibility of table getting emptied as it happened above
So the bottom line is always fully qualify your objects
Hopefully this would give you some insight into things to be taken care of while developing and implementing delete queries in future and avoid any unintended deletions from happening.