Reliably dropping a SQL Server database if it exists

I often need to write scripts that drop and recreate databases. The hard part of that has always been reliably dropping a database if it already exists. And no, you wouldn’t think that would be hard, but it is.
Built in Command
T-SQL has a built-in command for this.
DROP DATABASE IF EXISTS Sales;
You’d hope that would work, but it doesn’t. I wish it did. The problem is that it will fail if anyone is connected to the DB. And to check if anyone is attached, you first need to check if the DB exists, so it makes the whole “IF EXISTS” part that was added to this command, completely pointless.
Worse, if you have separate code to kick everyone off first, you always have a chance of a race condition, between when you kick everyone off, and when you execute the command.
Nearly OK
Years back, the Microsoft docs library said to drop a database like this:
USE master;
GO
IF EXISTS (SELECT 1 FROM sys.databases WHERE [name] = N'Sales')
BEGIN
ALTER DATABASE Sales SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE Sales;
END;
GO
This was promising, but unfortunately, it has an issue as well. Because you were in the master database when you issued the ALTER, you don’t know that you are the single user. So, periodically, that would fail too.
Best Workaround
Over the last few days, we’ve had a discussion on an MVP list about how to work around this. Many thanks to Paul White, Erland Sommarskog, and Simon Sabin for contributing to it.
The best outcome I have right now is to use this:
USE tempdb;
GO
DECLARE @SQL nvarchar(max);
IF EXISTS (SELECT 1 FROM sys.databases WHERE [name] = 'Sales')
BEGIN
SET @SQL =
N'USE Sales;
ALTER DATABASE Sales SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
USE master;
DROP DATABASE Sales;';
EXEC (@SQL);
USE tempdb;
END;
GO
To get the DROP to work properly, you need to execute the ALTER DATABASE from within the target database. That way, you end up being the single user, and even though you then execute a change to master, you hold the required session lock on the DB, and then the drop works as expected.
Because you can’t have a USE Sales in the script if the Sales DB doesn’t exist, this unfortunately has to be done in dynamic SQL code, where it is only executed if the DB does exit.
The last change to tempdb is just protection, if I have a script that then wants to create the DB and change to using it. If that goes wrong, I want to end up creating things in tempdb, not somewhere else like master.
What I wanted
What I’ve been asking for, and for a very long time, is this:
DROP DATABASE IF EXISTS Sales WITH ROLLBACK IMMEDIATE;
The ROLLBACK IMMEDIATE needs to be on the DROP DATABASE command, not on a separate ALTER command. Hopefully one day we’ll get this.
2021-06-24