Database deployment scripting with Dynamic SQL

Bob Blackburn
2014-08-01

Dynamic SQL is most often associated with online applications. This is because you are processing unknowns and coding for every possible scenario is impossible. Dynamic SQL can help you process unknown inputs. However, it also opens you up to SQL Injection. A very serious problem; but, out of the scope of this blog post (See additional reading). This article will focus on a few cases where Dynamic SQL will solve some batch scripting problems.

Database deployments (vs code deployments) can have their own set of problems. We often have to maintain multiple environments at different versions and upgrade scripts from many versions to many versions. The use of DACPACs in SQL Server can automate a great portion of the deployment process; but, there are some tasks (Data conversion, schema changes with data loss) that must be kept out of the DACPAC. Maintaining numerous pre and post deployment scripts can become a full time endeavor and the likelihood of errors goes up. It would be best to automate the process and maintain only a handful of scripts.

We will look at two specific issues that arise when trying to automate versioning in a script. They are Batch Commands and Delayed Resolution.

Dynamic SQL in Batch Commands


When scripting database upgrades, the use of version numbers controls the logic. This causes a few extra steps in certain cases (like no use of GO statement inside an IF statement). Also, creating procedures based on upgrade path because the CREATE PROCEDURE statement must be the only statement in a batch. Luckily Dynamic SQL gets around this. As an added bonus, we can also use a different database inside Dynamic SQL without being stopped by the CREATE statement being the only statement in the batch rule.

Batch Command Example


For the SQL command, we will create an EndLine character to allow for easier reading. Using strings with embedded quotes also takes a little time to get use to if you are new to Dynamic SQL.

-- Uses two databases DEMO1 and DEMO2

USE DEMO1
GO

Declare @TargetVersion char(5) = '03.01'
       ,@PreviousVersion char(5) = '03.00';

IF @TargetVersion > @PreviousVersion
Begin
      DECLARE @EndLine NCHAR(2) = NCHAR(13) + NCHAR(10);
      DECLARE @TSQL NVARCHAR(4000);

      SET @TSQL =
            N'Create Procedure PrintHello' + @EndLine +
            N'AS' + @EndLine +
            N'Begin' + @EndLine +
            N'Print(''''Hello World!'''')' + @EndLine +
            N'End' 

            SET @TSQL = 'USE Demo2; EXEC sp_executesql N''' + @TSQL + '''';

            Begin Try
                  EXEC  (@TSQL)
            End Try

            Begin Catch
                  Print('Procedure PrintHello already exists')
            end Catch

END -- IF @TargetVersion > @PreviousVersion

USE DEMO2
GO
EXEC PRINTHELLO
GO

The output is simply ‘Hello World!’. But, we were able to get around a few batch rules with Dynamic SQL.

Delayed resolution example


This may be the more frequent need for Dynamic SQL in batch scripting. Changes to the Schema often create the need to back fill columns. We have to add them as null, back fill, and then make NOT NULL to allow the DACPAC to deploy and create indexes on columns that are not null. The catch is the column does not exist yet so your script fails validation. Another feature of SQL is an UPDATE statement won’t recognize a new column unless a GO is executed after the ALTER TABLE. This forces us to create multiple IF statements to control the script. For this example, we will declare and initialize the variables. After each batch (GO) you will have to reset the variables from table.

Declare @TargetVersion char(5) = '03.01'
       ,@PreviousVersion char(5) = '03.00';



CREATE TABLE [Customer](
      [CustomerID] [int] IDENTITY(1,1) NOT NULL,
      [FirstName] [nvarchar](30) NOT NULL,
      [LastName] [nvarchar](30) NOT NULL,
 CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ([CustomerID] ASC) ON [PRIMARY]
) ON [PRIMARY]



INSERT INTO [dbo].[Customer]
           ([FirstName]
           ,[LastName])
     VALUES
           ('Code'
           ,'Monkey')

If @TargetVersion > @PreviousVersion
Begin
      ALTER TABLE Customer ADD CustomerName nvarchar(60)  null;
end
GO

Declare @TargetVersion char(5) = '03.01'
       ,@PreviousVersion char(5) = '03.00';

If @TargetVersion > @PreviousVersion
Begin
      DECLARE @EndLine NCHAR(2) = Nchar(13) + Nchar(10);
      DECLARE @sql NVARCHAR(4000); 

      SET @sql =
            N'UPDATE Customer' + @EndLine +
            N'set CustomerName = FirstName + '' '' + LastName;'

      EXEC (@SQL)

 
End

ALTER TABLE Customer ALTER COLUMN CustomerName nvarchar(60) not null;
GO

select *
  from Customer


Conclusion

With these examples of Dynamic SQL in batch scripting, you can reduce the amount of scripts you need to maintain and control the upgrade process as needed.

Additional reading


SQL Injection
Using sp_executesql
SQLInjection Attacks and Some Tips on How to Prevent Them
DACPAC