Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Data Management

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

Search

XML Feeds

Google Ads

« Review Of Beginning Spatial With SQL Server 2008Changing exec to sp_executesql doesn't provide any benefit if you are not using parameters correctly »
comments
Rate Post:
submit to reddit Digg!FacebookDotnetkicks

Yesterday in the Changing exec to sp_executesql doesn't provide any benefit if you are not using parameters correctly post I showed you that sp_executesql is better than exec because you get plan reuse and the procedure cache doesn't get bloated.
Today I will show you that sp_executesql is better than exec or ad hoc queries when you deal with conversions in execution plans

First create this table and populate it with some data

  1. CREATE TABLE TestPerf(  id INT not null,
  2.             SomeCol1 NVARCHAR(20),
  3.             SomeValue SMALLINT)
  4.  
  5.  
  6. INSERT TestPerf VALUES(1,'Test',1)
  7. INSERT TestPerf VALUES(2,'Aest',257)
  8. INSERT TestPerf VALUES(3,'Best',258)
  9. INSERT TestPerf VALUES(4,'Cest',259)
  10. INSERT TestPerf VALUES(5,'Dest',251)
  11. INSERT TestPerf VALUES(6,'Eest',252)
  12. INSERT TestPerf VALUES(7,'Fest',253)
  13. INSERT TestPerf VALUES(8,'Gest',254)
  14. INSERT TestPerf VALUES(9,'Hest',255)

Take a look this query

  1. SELECT * FROM TestPerf
  2. WHERE SomeCol1 = 'Test'
  3. and SomeValue = 1

Run the query

Here is the execution plan


|--Table Scan(OBJECT:([testpage].[dbo].[TestPerf]),
WHERE:([testpage].[dbo].[TestPerf].[SomeValue]=CONVERT_IMPLICIT(smallint,[@2],0)
AND [testpage].[dbo].[TestPerf].[SomeCol1]=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)))

As you can see the value 1 needs to be converted to smallint and 'Test' Needs to be converted to nvarchar. You can actually see what the value is converted to by using the SQL_VARIANT_PROPERTY function.
When you run the following query

  1. SELECT SQL_VARIANT_PROPERTY(1, 'BaseType') AS [Base Type],
  2.        SQL_VARIANT_PROPERTY(1, 'Precision') AS [PRECISION],
  3.        SQL_VARIANT_PROPERTY(1, 'Scale') AS [Scale]
  4.  
  5. UNION all
  6. SELECT SQL_VARIANT_PROPERTY('Test', 'BaseType') AS [Base Type],
  7.        SQL_VARIANT_PROPERTY('Test', 'Precision') AS [PRECISION],
  8.        SQL_VARIANT_PROPERTY('Test', 'Scale') AS [Scale]

you get this as output

Base Type	PRECISION	Scale
int	        10	        0
varchar	        0	        0

so the value 1 becomes an int and 'test' is varchar

Exec with dynamic SQL is not any better than ad hoc of course since the same query gets generated as with the ad hoc query
Here is such a query

  1. DECLARE @SomeValue SMALLINT
  2. DECLARE @SomeCol1 NVARCHAR(20)
  3.  
  4. SELECT @SomeValue = 1,@SomeCol1 = 'Test'
  5.  
  6. DECLARE @SQL NVARCHAR(1000)
  7. SELECT @SQL = 'select * from dbo.TestPerf
  8. where SomeCol1 = ''' + @SomeCol1 +'''
  9. and SomeValue = ' + CONVERT(NVARCHAR(10),@SomeValue)
  10.  
  11. EXEC (@SQL)


Here is the execution plan

 |--Table Scan(OBJECT:([testpage].[dbo].[TestPerf]), 
 WHERE:([testpage].[dbo].[TestPerf].[SomeValue]=CONVERT_IMPLICIT(smallint,[@2],0) 
 AND [testpage].[dbo].[TestPerf].[SomeCol1]=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)))

Ideally you want a query with parameters; below is the quey

  1. DECLARE @SomeValue SMALLINT
  2. DECLARE @SomeCol1 NVARCHAR(20)
  3.  
  4. SELECT @SomeValue = 1,@SomeCol1 = 'Test'
  5.  
  6. SELECT * FROM dbo.TestPerf
  7. WHERE SomeCol1 =  @SomeCol1
  8. and SomeValue = @SomeValue

Here is the execution plan

  
 |--Table Scan(OBJECT:([testpage].[dbo].[TestPerf]),
 WHERE:([testpage].[dbo].[TestPerf].[SomeValue]=[@SomeValue] 
 AND [testpage].[dbo].[TestPerf].[SomeCol1]=[@SomeCol1]))

As you can see no conversions here

Now let's take a look at how we can take the dynamic query from before and use sp_executesql to get rid of conversions

Run this query

  1. DECLARE @SomeValue SMALLINT
  2. DECLARE @SomeCol1 NVARCHAR(20)
  3.  
  4. SELECT @SomeValue = 1,@SomeCol1 = 'Test'
  5.  
  6.  
  7. DECLARE @SQL NVARCHAR(1000)
  8. SELECT @SQL = 'select * from dbo.TestPerf
  9. where SomeCol1 = @InnerSomeCol1
  10. and SomeValue =  @InnerSomeValue'
  11.  
  12. DECLARE @ParmDefinition NVARCHAR(500)
  13. SET @ParmDefinition = N'@InnerSomeValue smallint ,@InnerSomeCol1 nvarchar(20)'
  14.  
  15.  
  16.  
  17. EXEC SP_EXECUTESQL  @SQL,@ParmDefinition,
  18.             @InnerSomeValue = @SomeValue,
  19.             @InnerSomeCol1  = @SomeCol1

Here is the execution plan

Execution Plan

As you can see there were no conversions.

How to avoid conversions

The thing that is important with a query like that is that the parameters inside the dynamic sql match the data types of the columns.

So for this part of the query

   select @SQL = 'select * from dbo.TestPerf
   where SomeCol1 = @InnerSomeCol1 
   and SomeValue =  @InnerSomeValue' 

The SomeCol1 column and the @InnerSomeCol1 param/variable have to be of the same datatype,
the SomeValue column and the @InnerSomeValue param/variable also have to be of the same datatype in order to prevent conversions

Let's look at something else
The query from before with parameters

  1. DECLARE @SomeValue SMALLINT
  2. DECLARE @SomeCol1 NVARCHAR(20)
  3.  
  4. SELECT @SomeValue = 1,@SomeCol1 = 'Test'
  5.  
  6. SELECT * FROM dbo.TestPerf
  7. WHERE SomeCol1 =  @SomeCol1
  8. and SomeValue = @SomeValue

Here is the execution plan

  
 |--Table Scan(OBJECT:([testpage].[dbo].[TestPerf]), 
 WHERE:([testpage].[dbo].[TestPerf].[SomeValue]=[@SomeValue] 
 AND [testpage].[dbo].[TestPerf].[SomeCol1]=[@SomeCol1]))

what happens if you change the params to this

  1. DECLARE @SomeValue INT
  2. DECLARE @SomeCol1 VARCHAR(20)
  3.  
  4. SELECT @SomeValue = 1,@SomeCol1 = 'Test'
  5.  
  6. SELECT * FROM dbo.TestPerf
  7. WHERE SomeCol1 =  @SomeCol1
  8. and SomeValue = @SomeValue

Here is the execution plan

  
 |--Table Scan(OBJECT:([testpage].[dbo].[TestPerf]), 
 WHERE:([testpage].[dbo].[TestPerf].[SomeValue]=[@SomeValue] 
 AND [testpage].[dbo].[TestPerf].[SomeCol1]=CONVERT_IMPLICIT(nvarchar(20),[@SomeCol1],0)))

We still get a conversion on the nvarchar SomeCol1 column

What happens if we change the outer parameter data types in the following dynamic query?

  1. DECLARE @SomeValue SMALLINT
  2. DECLARE @SomeCol1 NVARCHAR(20)
  3.  
  4. SELECT @SomeValue = 1,@SomeCol1 = 'Test'
  5.  
  6.  
  7. DECLARE @SQL NVARCHAR(1000)
  8. SELECT @SQL = 'select * from dbo.TestPerf
  9. where SomeCol1 = @InnerSomeCol1
  10. and SomeValue =  @InnerSomeValue'
  11.  
  12. DECLARE @ParmDefinition NVARCHAR(500)
  13. SET @ParmDefinition = N'@InnerSomeValue smallint ,@InnerSomeCol1 nvarchar(20)'
  14.  
  15.  
  16.  
  17. EXEC SP_EXECUTESQL  @SQL,@ParmDefinition,
  18.             @InnerSomeValue = @SomeValue,
  19.             @InnerSomeCol1  = @SomeCol1

Here is the execution plan

Execution Plan


Now we will change the parameters from smallint and nvarchar to int and varchar in the query below. This won't make any difference in the execution plan because the parameters used in the plan are still of the correct datatype since the inner parameters are used not the outer ones!!

  1. /*changed from
  2. declare @SomeValue smallint
  3. declare @SomeCol1 nvarchar(20)*/
  4.  
  5. DECLARE @SomeValue INT
  6. DECLARE @SomeCol1 VARCHAR(20)
  7.  
  8. SELECT @SomeValue = 1,@SomeCol1 = 'Test'
  9.  
  10.  
  11. DECLARE @SQL NVARCHAR(1000)
  12. SELECT @SQL = 'select * from dbo.TestPerf
  13. where SomeCol1 = @InnerSomeCol1
  14. and SomeValue =  @InnerSomeValue'
  15.  
  16. DECLARE @ParmDefinition NVARCHAR(500)
  17. SET @ParmDefinition = N'@InnerSomeValue smallint ,@InnerSomeCol1 nvarchar(20)'
  18.  
  19.  
  20.  
  21. EXEC SP_EXECUTESQL  @SQL,@ParmDefinition,
  22.             @InnerSomeValue = @SomeValue,
  23.             @InnerSomeCol1  = @SomeCol1

Here is the execution plan

Execution Plan

That wraps up this blog post



*** If you have a SQL related question try our Microsoft SQL Server Programming forum or our Microsoft SQL Server Admin forum

About the Author

User bio imageDenis has been working with SQL Server since version 6.5. Although he worked as an ASP/JSP/ColdFusion developer before the dot com bust, he has been working exclusively as a database developer/architect since 2002. In addition to English, Denis is also fluent in Croatian and Dutch, but he can curse in many other languages and dialects (just ask the SQL optimizer) He lives in Princeton, NJ with his wife and three kids.
Social SitingsTwitterFacebookLinkedInHomePageLTD RSS Feed
2445 views
submit to reddit Digg!FacebookDotnetkicks

Comments and Feedback

3 comments

Comment from: Ted Krueger (onpnt) [Member] Email
*****
Awesome write up Denis! Something you don't see discussed very often in that detail
06/10/09 @ 11:06
Comment from: SQLDenis [Member] Email
*****
Thanks man....this is one of the first things I had to deal with when starting my current job 4 years ago.....rewriting stuff that was written like this
06/10/09 @ 12:17
Comment from: Naomi [Member] Email
*****
Thanks, great blog as usual
07/28/09 @ 10:13

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)