DROP IF EXISTS and CREATE OR ALTER for Iterative Deployments

Iterative deployments should be safe to run twice. DROP IF EXISTS shrugs when the object is already gone. CREATE OR ALTER changes a procedure in place. Together they let you rerun a script without breaking what already works.

Gouache painting of a slate blue jacket on a wooden hanger with every button sewn on, and a dish holding one spare button, vermilion thread and a needle

Why the second run matters

Every deployment script works on a clean database. That proves very little. The real test is the second run, on a server that already has your objects. Does it fail with “already exists”? Does it quietly remove something people depend on?

Imagine a release that goes out at 6 PM. Nothing fails. At 9 AM the app users start getting permission errors. The script dropped a procedure and created it again, and the grant went away with the old one. I will show you exactly that.

DROP IF EXISTS tolerates absence

DROP IF EXISTS does nothing, without an error, when the object is not there. So you can run it twice. The example below drops an index, then drops the table twice. Note that DROP INDEX needs the table name too.

DROP TABLE IF EXISTS #DeploymentSyntax;
CREATE TABLE #DeploymentSyntax (Id int PRIMARY KEY, Name varchar(50));
CREATE INDEX IX_DeploymentSyntax ON #DeploymentSyntax (Name);

DROP INDEX IF EXISTS IX_DeploymentSyntax ON #DeploymentSyntax;
DROP TABLE IF EXISTS #DeploymentSyntax;
DROP TABLE IF EXISTS #DeploymentSyntax;

SELECT OBJECT_ID(N'tempdb..#DeploymentSyntax') AS RemainingTable;

The second table drop ran against a table that was already gone, and nothing complained. RemainingTable is NULL, the first grid in the screenshot below. One warning: IF EXISTS only means “do not fail if missing”. It also means “delete it happily if it holds your data”. Use it for objects you can rebuild.

CREATE OR ALTER keeps the permissions

For procedures, views, functions and triggers, CREATE OR ALTER creates the object or replaces its definition. It does not remove the object first. Let me make a procedure, create a user without a login, and grant EXECUTE.

CREATE OR ALTER PROCEDURE dbo.IterativeDemo
AS SELECT 1 AS DeploymentValue;
GO
DROP USER IF EXISTS IterativeReader;
CREATE USER IterativeReader WITHOUT LOGIN;
GRANT EXECUTE ON dbo.IterativeDemo TO IterativeReader;

EXECUTE AS USER = N'IterativeReader';
EXEC dbo.IterativeDemo;
REVERT;

The user runs the procedure and gets DeploymentValue 1. Now change the procedure and run the same check again.

CREATE OR ALTER PROCEDURE dbo.IterativeDemo
AS SELECT 2 AS DeploymentValue;
GO
EXECUTE AS USER = N'IterativeReader';
EXEC dbo.IterativeDemo;
REVERT;

SELECT permission_name, state_desc
FROM sys.database_permissions
WHERE major_id = OBJECT_ID(N'dbo.IterativeDemo')
  AND grantee_principal_id = USER_ID(N'IterativeReader');
SQL Server results showing procedure revisions and the retained execution grant
The value changes from 1 to 2 and the EXECUTE grant is still there.

The same user now gets DeploymentValue 2, and the permission query shows EXECUTE with state GRANT. New code, same grant. The script was safe to run again.

The drop and create trap

Now the 9 AM story. The “old way” drops the procedure and creates it again. Watch what happens to the grant.

DROP PROCEDURE IF EXISTS dbo.IterativeDemo;
GO
CREATE PROCEDURE dbo.IterativeDemo
AS SELECT 3 AS DeploymentValue;
GO
SELECT COUNT(*) AS GrantsLeft
FROM sys.database_permissions
WHERE major_id = OBJECT_ID(N'dbo.IterativeDemo')
  AND grantee_principal_id = USER_ID(N'IterativeReader');

EXECUTE AS USER = N'IterativeReader';
BEGIN TRY
    EXEC dbo.IterativeDemo;
END TRY
BEGIN CATCH
    SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
REVERT;

GrantsLeft is 0. The user now gets an error: the EXECUTE permission was denied. That is error 229, and it is exactly what the app users saw. The drop deleted the permission along with the procedure. You would have to grant it again in the script.

CREATE OR ALTER or drop and create

Check definitions, not just names

One more trap. An existence check only asks whether a name exists. It says nothing about whether the thing is right. Here a column called Note already exists, but it is too short.

DROP TABLE IF EXISTS #Orders;
CREATE TABLE #Orders (OrderId int, Note varchar(10));

IF COL_LENGTH(N'tempdb..#Orders', N'Note') IS NULL
    ALTER TABLE #Orders ADD Note varchar(200);

SELECT name, max_length
FROM tempdb.sys.columns
WHERE object_id = OBJECT_ID(N'tempdb..#Orders') AND name = N'Note';

The check found Note and skipped the change. The column is still 10 characters long, not 200. Tables have no CREATE OR ALTER, so for tables compare the definition, then ALTER. Test your migration from the old schema and from a fresh one.

Last, clean up the demo objects.

DROP PROCEDURE IF EXISTS dbo.IterativeDemo;
DROP USER IF EXISTS IterativeReader;
DROP TABLE IF EXISTS #Orders;

Run your deployment script twice before you trust it.

A repeatable deployment is not a repeated drop, it is a controlled change.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

DevOps, SQL Server, SQL Stored Procedure
Previous Post
A T-SQL Cheat Sheet for Everyday Work
Next Post
Log Shipping to a Readable Standby: Restores vs Report Users

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.