Introduction
In my previous article Dynamic
SQL vs. Static SQL Part 1 – Security we looked at permissions for dynamic
and static SQL and security issues related to using dynamic SQL. In this article I plan to show cases where
you will be tempted to use dynamic SQL even though static SQL solutions exist
and are usually as good if not better than the dynamic solution.
Many people choose to use dynamic SQL because it often
requires less thought to develop and deploy, requires less code and thus less
time to type it, and is thought to require less maintenance than its static SQL
equivalent. In this article we’ll
explore these reasons and see if they are valid.
As with my previous article the focus of this one is to show
you ways to avoid using dynamic SQL.
I’ll do this by presenting dynamic SQL code and then showing you how to
do the same thing using static SQL.
Dynamic SQL should only be used when you can’t use static SQL.
Throughout this article I’ll use the Pubs database for my
examples.
CASE 1 – Search Criteria
This first case is the one I have seen the most and is the
best example I know to demonstrate where the dynamic
SQL solution requires much less thought and less code to implement. For this case, we’ll use the authors table in
the pubs database. We want to do a
search for a particular author or authors in the table based on the values we
put into a variable. Here is the code
we’ll use in Query Analyzer:
DECLARE @names varchar(7000)
SET @names = 'white'
SELECT *
FROM authors
WHERE au_lname IN (@names)
Upon execution of the above code we get one row
returned. Now we want to search on more
than one name. Modify the SET statement
in the above code to look like this:
SET @names = '''white'',''green'''
We have used the extra single quotes so that the names will
look just like they would if we had hard coded these values and not used a
variable between the parenthesis. However, executing the code returns no
rows. This is because SQL server thinks
this is one long string and tries to find a last name ‘white’,’green’. We will continue this case by stating that
creating two or more separate variables is not an option since we don’t want to
limit the number of names we can use by the number of variables we create; the
only limit acceptable is however long we make our one varchar variable. The easiest solution from here is to convert
the entire SELECT statement to dynamic SQL.
Execute this code:
DECLARE @names varchar(7000),
@Query varchar(8000)
SET @names = '''white'',''green'''
SET @Query = '
SELECT *
FROM authors
WHERE au_lname IN (' + @names +
')'
PRINT @Query
EXEC (@Query)
This returns just what we want and will as long as the names
variable is formatted properly. However,
this solution will force you to deal with the issues I presented in my previous
article with respect to being forced to give SELECT permissions on the authors
table. The following solution will do
the same thing while avoiding dynamic SQL:
DECLARE @names varchar(7000), @len int, @CurPos int, @PrevPos int
CREATE TABLE #names (names varchar(35))
SET NOCOUNT ON
SET @names = 'white,green'
SET @len = LEN(@names)
+ 1
SET @CurPos = 1
SET @PrevPos = @CurPos
WHILE @CurPos < @len + 1
BEGIN
IF SUBSTRING(@names + ',', @CurPos,
1) = ','
BEGIN
INSERT
INTO #names (names)
SELECT
SUBSTRING(@names,@PrevPos,@CurPos - @PrevPos)
SET
@PrevPos = @CurPos + 1
END
SET @CurPos = @CurPos + 1
END
SET NOCOUNT OFF
SELECT *
FROM authors
WHERE au_lname IN (SELECT * FROM
#names)
DROP TABLE #names
For those of you who like to check the speed between
different solutions that do the same thing you’ll notice these always run with
only a few milliseconds difference between them. I’ll not go any further into speed as that is
outside the scope of this article.
Another benefit you gain when using this static SQL solution is that you
can easily modify it to allow more names than one varchar variable can hold by
duplicating the loop section as many times as needed for the number of names
you plan to use. As for the dynamic SQL,
a varchar variable is limited to 8000 characters and you won’t be able to use
all 8000 for names since you must have room in @Query for the code that will
use the names to fetch the rows you want in your result set.
Looking at the above two solutions we can see that the static
SQL has more code than the dynamic SQL.
The static SQL also took me longer to develop and I had to plan and test
more to get it right. When I first ran
it, I didn’t get the final name in the string of names until I added one to the
@len variable in the WHILE loop.
CASE 2 – Column Sorting
Let us suppose you want to deliver a result set sorted by a
column specified by a variable and you want to include some defaults with some
of the sorts but not all of them. Before
I learned how to use the CASE function in the ORDER BY clause I thought the
only way to achieve this was with dynamic SQL something like this:
DECLARE @Query varchar(500), @OrderBy varchar(10), @Sequence varchar(4)
SET @OrderBy = 'au_lname'
SET @Sequence = 'DESC'
SET @Query = '
SELECT *
FROM authors
ORDER BY ' + @OrderBy + ' ' + @Sequence
IF @OrderBy = 'au_lname'
SET @Query = @Query + ', au_fname ' + @Sequence
PRINT @Query
EXEC (@Query)
This gets the job done nicely and is the quickest and
simplest solution to implement. The
static SQL solution is longer and looks like this:
DECLARE @OrderBy varchar(10), @Sequence varchar(4)
SET @OrderBy = 'au_lname'
SET @Sequence = 'DESC'
SELECT *
FROM authors
ORDER BY CASE @OrderBy + @Sequence
WHEN 'au_lnameASC' THEN au_lname
WHEN 'au_fnameASC' THEN au_fname
END ASC,
CASE @OrderBy + @Sequence WHEN 'au_lnameDESC' THEN au_lname WHEN
'au_fnameDESC' THEN au_fname
END DESC,
CASE @OrderBy + @Sequence WHEN 'au_lnameASC' THEN au_fname END
ASC,
CASE @OrderBy + @Sequence WHEN 'au_lnameDESC' THEN au_fname END
DESC
This solution simply requires a little more planning and
more code to implement.
If you change @Sequence in both from DESC to ASC and/or @OrderBy from ‘au_lname’ to ‘au_fname’ you will see that both examples give you the same
result sets. However, when both are
placed into separate stored procedures and users are given execute permissions,
the dynamic SQL solution will fail unless the users also have SELECT
permissions on the authors table.
CASE 3 - Joins
For our last case let us suppose we plan to display author
and/or title information. The query will
display author information only if we send a value into @Author. It will display title info only if we send a
value into @Title. Title and author info
will be displayed only if we send values into both variables. When we assign values for both variables we
will need 3 tables to get our results.
However, if we only send a value into one variable or the other we’ll
only need one table. Here is how the
dynamic SQL solution could look:
DECLARE @Query varchar(500),
@Author varchar(20), @Title varchar(20)
SET @Author = ''
SET @Title = 'e'
SET @Query = '
SELECT *
FROM '
IF @Author <> '' SET @Query = @Query + 'authors a'
IF @Author <> '' AND @Title <> ''
SET @Query
= @Query + CHAR(13) + 'INNER JOIN titleauthor
ta ON ta.au_id = a.au_id' + CHAR(13) + 'INNER JOIN '
IF @Title <> '' SET @Query = @Query + 'titles t'
IF @Author <> '' AND @Title <> ''
SET @Query
= @Query + ' ON t.title_id = ta.title_id'
IF @Author <> '' OR @Title <> '' SET @Query = @Query
+ CHAR(13) + 'WHERE '
IF @Author <> '' SET @Query = @Query + 'au_lname LIKE ''%' + @Author + '%'''
IF @Author <> '' AND @Title <> '' SET @Query =
@Query + CHAR(13) + 'AND '
IF @Title <> '' SET @Query = @Query + 'title LIKE ''%'
+ @Title + '%'''
PRINT @Query
EXEC (@Query)
To do the same thing and avoid dynamic SQL we can use the
following code:
DECLARE @Author varchar(20), @Title
varchar(20)
SET @Author = 'a'
SET @Title = 'u'
IF @Author <> '' AND @Title = ''
BEGIN
SELECT *
FROM
authors
WHERE au_lname LIKE '%' + @Author + '%'
END
IF @Title <> '' AND @Author = ''
BEGIN
SELECT *
FROM titles
WHERE title
LIKE '%' + @Title + '%'
END
IF @Title <> '' AND @Author <> ''
BEGIN
SELECT *
FROM
authors a
INNER JOIN titleauthor ta ON ta.au_id = a.au_id
INNER JOIN
titles t ON t.title_id = ta.title_id
WHERE au_lname LIKE '%' + @Author + '%'
AND title
LIKE '%' + @Title + '%'
END
Unless I didn’t test enough the above two solutions deliver
the same result set when the same values are sent in. Please note, that without the fix described
in my previous article a single quote sent into the dynamic SQL will break the
query and the dynamic SQL will not be protected against unauthorized code being
executed.
Static SQL Is Easier To Maintain
Unless you plan to give all users read and write permissions
to an entire database, static SQL will be easier to maintain. This is because with dynamic SQL you will
have to constantly add permissions for each table or object used in the dynamic
code. With static SQL all you have to do
is give execute permissions to a stored procedure and
it will work. This reduced the thought
and planning you must spend to ensure no one has permissions they shouldn’t
have and that users can only do what you want them to and nothing more.
For many modifications, a find and replace will work just as
well for the dynamic code as for the static code. So maintenance in this area is often the
same.
Another benefit and time saver with static code is that SQL
Server can verify that your syntax is correct.
With dynamic SQL, SQL Server has no idea what code it will execute until
you execute it. There could be bugs and
errors waiting to surface as users begin using your dynamic SQL, unless you
spend extra time testing every possible combination for dynamic SQL similar to
the one used in CASE 3. Testing
increases geometrically with each variable you add and by the time you have 20
or 30 variables that help determine how the dynamic SQL is built, you must do a
lot of testing to ensure you don’t build the string incorrectly and cause a
syntax error. With static SQL you can
build as many queries as needed and SQL Server will check the syntax for you.
As your dynamic SQL code grows and the query it builds
becomes more complex the code becomes harder to follow and read. Another problem appears when the code you put
into a variable exceeds 8000 characters and gets chopped off abruptly. At this point, you’re only alternative is to
convert the dynamic SQL to static SQL.
The time needed for conversion grows with the complexity of the queries
being built.
I have faced and still face these problems. A few months ago, I converted one of our most
complex stored procedures from dynamic SQL to static SQL. It took about 3 weeks to convert and then a few
more weeks to test to ensure no functionality was lost.
Conclusion
When developing code you should always consider using static
SQL. Dynamic SQL only becomes a possible
solution if you can find no way to use static SQL. When you consider coding time, permissions
management, testing, readability, and the possibility of being forced to
convert code from dynamic to static you find that maintenance requirements for
static SQL are less than that for dynamic SQL.