<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://sqlblog.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Search results matching tags 'T-SQL', 'Optimization', and 'Performance'</title><link>http://sqlblog.com/search/SearchResults.aspx?o=DateDescending&amp;tag=T-SQL,Optimization,Performance&amp;orTags=0</link><description>Search results matching tags 'T-SQL', 'Optimization', and 'Performance'</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>What Happened Today? DATE and Date Ranges Over DATETIME</title><link>http://sqlblog.com/blogs/adam_machanic/archive/2009/10/20/what-happened-today-date-and-date-ranges-over-datetime.aspx</link><pubDate>Tue, 20 Oct 2009 18:21:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:18023</guid><dc:creator>Adam Machanic</dc:creator><description>&lt;p&gt;A few days ago Aaron posted yet another fantastic entry in his Bad Habits series, this one discussing &lt;a href="http://sqlblog.com/blogs/aaron_bertrand/archive/2009/10/16/bad-habits-to-kick-mishandling-date-range-queries.aspx"&gt;mishandling of date ranges in queries&lt;/a&gt;. This is a topic near and dear to me, having had to clean up a lot of poorly thought out code in the past few years. Aaron's post includes many examples, all of which boil down to the proper way to do the job, a query like the following (which I've &lt;strike&gt;stolen&lt;/strike&gt;borrowed from the post):&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;SELECT&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;COUNT(*)&lt;br&gt;FROM dbo.SomeLogTable&lt;br&gt;WHERE &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;DateColumn &amp;gt;= '20091011'&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;AND DateColumn &amp;lt; '20091012';&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This query asks a simple question: How many rows do we have for October 11, 2009? And assuming the presence of an index on DateColumn, the work will be done much more efficiently than the most common anti-pattern I see, converting both the input date and the date column to CHAR(8) using CONVERT format 112 (YYYYMMDD) and comparing the strings. Doing that will cause a table scan, which is definitely a bad habit worth kicking.&lt;br&gt;&lt;/p&gt;&lt;p&gt;But what I want to show in this post is that contrary to what you might understand from working with previous versions of SQL Server, in SQL Server 2008 conversion is not &lt;i&gt;always&lt;/i&gt; a bad thing. In conjunction with the addition of the DATE type, the query optimizer received a minor upgrade. Converting both sides of the predicate to DATE will cause the optimizer to do exactly what you probably wanted to begin with: Return all of the events that occurred today (or on whatever input date you specified), by seeking into the index rather than scanning the table.&lt;/p&gt;&lt;p&gt;To see this optimization, start with the following test data:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;CREATE TABLE #dates &lt;br&gt;(&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; the_date DATETIME NOT NULL PRIMARY KEY&lt;br&gt;);&lt;br&gt;GO&lt;br&gt;&lt;br&gt;WITH &lt;br&gt;numbers AS&lt;br&gt;(&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT number&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM master..spt_values&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; type = 'P'&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; AND number &amp;gt; 0&lt;br&gt;)&lt;br&gt;INSERT #dates &lt;br&gt;SELECT DATEADD(hh, -number, GETDATE())&lt;br&gt;FROM numbers&lt;br&gt;UNION ALL&lt;br&gt;SELECT DATEADD(hh, number, GETDATE())&lt;br&gt;FROM numbers;&lt;br&gt;GO &lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;... And now ask a question. What happened today? First we'll ask the wrong way:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;SET SHOWPLAN_TEXT ON;&lt;br&gt;GO&lt;br&gt;&lt;br&gt;SELECT&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*)&lt;br&gt;FROM #dates&lt;br&gt;WHERE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CONVERT(CHAR(8), the_date, 112) = CONVERT(CHAR(8), GETDATE(), 112);&lt;br&gt;GO&lt;br&gt;&lt;br&gt;SET SHOWPLAN_TEXT OFF;&lt;br&gt;GO&lt;/p&gt;&lt;p&gt;---&lt;/p&gt;&lt;p&gt;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1006],0)))&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(DEFINE:([Expr1006]=Count(*)))&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[#dates]), WHERE:(CONVERT(char(8),[tempdb].[dbo].[#dates].[the_date],112)=CONVERT(char(8),getdate(),112))) &lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Oops! An index scan. That won't do. But if you like writing code like this, all is not lost--you just need to do a different conversion:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;SET SHOWPLAN_TEXT ON;&lt;br&gt;GO&lt;br&gt;&lt;br&gt;SELECT&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*)&lt;br&gt;FROM #dates&lt;br&gt;WHERE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CONVERT(DATE, the_date) = CONVERT(DATE, GETDATE());&lt;br&gt;GO&lt;br&gt;&lt;br&gt;SET SHOWPLAN_TEXT OFF;&lt;br&gt;GO&lt;br&gt;&lt;br&gt;---&lt;br&gt;&lt;br&gt;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1009],0)))&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(DEFINE:([Expr1009]=Count(*)))&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1007], [Expr1008], [Expr1006]))&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Compute Scalar(DEFINE:(([Expr1007],[Expr1008],[Expr1006])=GetRangeThroughConvert(CONVERT(date,getdate(),0),CONVERT(date,getdate(),0),(62))))&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Constant Scan&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[#dates]), SEEK:([tempdb].[dbo].[#dates].[the_date] &amp;gt; [Expr1007] AND [tempdb].[dbo].[#dates].[the_date] &amp;lt; [Expr1008]),&amp;nbsp; WHERE:(CONVERT(date,[tempdb].[dbo].[#dates].[the_date],0)=CONVERT(date,getdate(),0)) ORDERED FORWARD)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This plan is quite a bit more complex, but the important things to note are: &lt;/p&gt;&lt;ol&gt;&lt;li&gt;An index seek is used, rather than an index scan, meaning that this query will return the results in a much more efficient manner&lt;/li&gt;&lt;li&gt;The new query is logically equivalent to the first query&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Need more than one day? Use either IN or BETWEEN, both of which produce query plans similar to the above.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;SELECT&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*)&lt;br&gt;FROM #dates&lt;br&gt;WHERE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CONVERT(DATE, the_date) IN (CONVERT(DATE, GETDATE()-1), CONVERT(DATE, GETDATE()));&lt;br&gt;GO&lt;br&gt;&lt;br&gt;SELECT&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*)&lt;br&gt;FROM #dates&lt;br&gt;WHERE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CONVERT(DATE, the_date) BETWEEN CONVERT(DATE, GETDATE()-1) AND CONVERT(DATE, GETDATE());&lt;br&gt;GO &lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Please note that I'm not suggesting that this methodology is any better or worse than what Aaron suggested in his post. I simply want you to know your options and--more importantly--understand that the knee-jerk "we must never use functions in the WHERE clause" approach is not always advantageous. The query optimizer has come a long way in recent versions of SQL Server and I suspect we'll see a lot more in these areas in the coming releases. It will be interesting to see how many of today's truths cease to be an issue as the optimizer becomes smarter and more adept at fixing user error.&lt;/p&gt;&lt;p&gt;Enjoy! &lt;br&gt;&lt;/p&gt;</description></item><item><title>Controlling Stored Procedure Caching with ... Dyanmic SQL?!?</title><link>http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/controlling-stored-procedure-caching-with-dyanmic-sql.aspx</link><pubDate>Thu, 13 Jul 2006 01:19:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:80</guid><dc:creator>Adam Machanic</dc:creator><description>&lt;p&gt;Tell me if this situation sends a chill down your spine: You've written
a stored procedure, tested it against a variety of inputs, and finally
rolled it out in production. All is well... Or so you think. You start
getting complaints from some users that it's taking forever to return.
But other users are having no problem. What the..?
&lt;/p&gt;&lt;p&gt;Veteran DBAs will know right away what's going on (even without
reading the title of this post!) -- but for those of you who haven't
had the &lt;i&gt;pleasure&lt;/i&gt;
of debugging these kinds of things, the answer is that cached execution
plans are not always as wonderful for performance as we might like.
&lt;/p&gt;&lt;p&gt;For any given query, there are numerous possible execution
plans that the query optimizer can come up with. Some of them are
optimal, some are less than optimal. But in the end, it's the job of
the query optimizer to decide which one to use (hopefully, the optimal
one). If a stored procedure is executed and its does not have a query
plan in cache, whatever execution plan the optimizer decides to use
will be cached for next time. This is usually a good thing -- it can be
quite a bit of work for the optimizer to make that decision.
&lt;/p&gt;&lt;p&gt;But in some cases, this is where the trouble begins. One of the
main factors the optimizer uses is index statistics vs. what parameters
are being used for the query. This can greatly affect what the
'correct' execution plan is -- the optimizer must decide such things as
which index should be used, whether a seek or a scan should be
performed, what types of joins are most efficient, etc. But as
parameters change, so can the most appropriate choices.
&lt;/p&gt;&lt;p&gt;
To illustrate this better, some sample data will be useful.  Break out your &lt;a href="http://sqljunkies.com/WebLog/amachanic/articles/NumbersTable.aspx" target="#"&gt;numbers table&lt;/a&gt; and run the following script, which will create a table with three columns, around 20 million rows, and a couple of indexes...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT &lt;br&gt;	Number, &lt;br&gt;	DATEADD(ss, Number, 0) AS TheDate&lt;br&gt;INTO DateTbl&lt;br&gt;FROM Numbers&lt;br&gt;&lt;br&gt;DECLARE &lt;br&gt;	@Num INT,&lt;br&gt;	@Incr INT&lt;br&gt;&lt;br&gt;SELECT &lt;br&gt;	@Num = MAX(Number) + 1,&lt;br&gt;	@Incr = MAX(Number) + 1&lt;br&gt;FROM DateTbl&lt;br&gt;&lt;br&gt;WHILE @Num &amp;lt; 20000000&lt;br&gt;BEGIN&lt;br&gt;	INSERT DateTbl (Number, TheDate)&lt;br&gt;	SELECT Number + @Num, DATEADD(ss, Number + @Num, 0) AS TheDate&lt;br&gt;	FROM Numbers&lt;br&gt;&lt;br&gt;	SET @Num = @Num + @Incr&lt;br&gt;END&lt;br&gt;&lt;br&gt;&lt;br&gt;CREATE UNIQUE CLUSTERED INDEX IX_Date ON DateTbl(TheDate)&lt;br&gt;&lt;br&gt;CREATE UNIQUE NONCLUSTERED INDEX IX_Number ON DateTbl(Number)&lt;br&gt;&lt;br&gt;ALTER TABLE DateTbl &lt;br&gt;ADD AnotherCol VARCHAR(40) NULL&lt;br&gt;&lt;br&gt;UPDATE DateTbl&lt;br&gt;SET AnotherCol = CONVERT(VARCHAR, Number) + CONVERT(VARCHAR, TheDate)&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;Okay! Now that your hard drive's workout is done, let's take a look
at what we have... DateTbl has three columns: A sequential number, a
datetime column, and a character column. You should have one row for
every second between January 1, 1900 and sometime around August 21,
1900, depending on how big your numbers table is. The date column and
the number column are indexed (we'll be using those as predicates in
the WHERE clause of the example queries), but the character column is
not. That's on purpose, to force a bookmark lookup. What can I say --
this is a totally contrived example!
&lt;/p&gt;&lt;p&gt;
Put Query Analyzer into Show Execution Plan mode and check out the following:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT &lt;br&gt;	Number,&lt;br&gt;	TheDate,&lt;br&gt;	AnotherCol&lt;br&gt;FROM DateTbl&lt;br&gt;WHERE TheDate BETWEEN '19000201 09:35:00' AND '19000201 09:36:00'&lt;br&gt;	OR Number = 10&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;Before I proceed, I would just like to say that anyone who comments
or e-mails me saying that this query can be re-written with a UNION to
get consistently better execution plans will be slapped upside the head
with a trout. YES, this is a bad query, but as I said, this is a very
simple example. In real life, these situations are usually much more
difficult to re-write. So if you don't like my example, go write your
own article!
&lt;/p&gt;&lt;p&gt;
... Now that that's taken care of ...
&lt;/p&gt;&lt;p&gt;The execution plan you should see will have a seek on each
index. Makes sense -- we're looking at a very small chunk of data in
each place. But what if we change the query to use a much larger date
range?
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT &lt;br&gt;	Number,&lt;br&gt;	TheDate,&lt;br&gt;	AnotherCol&lt;br&gt;FROM DateTbl&lt;br&gt;WHERE TheDate BETWEEN '19000101' AND '19000201'&lt;br&gt;	OR Number = 10&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;A seek on the date index no longer makes sense. A range scan is a
better option. And why bother seeking on the Number column? The row
with the number 10 is already found within the selected range. SQL
Server agrees with me on this, and performs only a scan of the
clustered date index.
&lt;/p&gt;&lt;p&gt;
But now let's see what happens when we throw this into a stored procedure.  Create the following:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE PROCEDURE GetStuff&lt;br&gt;	@StartDate DateTime,&lt;br&gt;	@EndDate DateTime,&lt;br&gt;	@Number INT&lt;br&gt;AS&lt;br&gt;	SELECT &lt;br&gt;		Number,&lt;br&gt;		TheDate,&lt;br&gt;		AnotherCol&lt;br&gt;	FROM DateTbl&lt;br&gt;	WHERE TheDate BETWEEN @StartDate AND @EndDate&lt;br&gt;		OR Number = @Number&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... And run the following in Query Analyzer with Show Execution Plan turned on ...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;EXEC GetStuff '19000201 09:35:00', '19000201 09:36:00', 10&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Same execution plan as before!  That's great, right?  Well...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;EXEC GetStuff '19000101', '19000201', 10&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... ... ...
&lt;/p&gt;&lt;p&gt;
This is taking a while ...
&lt;/p&gt;&lt;p&gt;
Enjoy the break?  Good, now get back to work!  
&lt;/p&gt;&lt;p&gt;Check out the execution plan. I guess the cached one wasn't
optimal for the second query. So how do we satisfy BOTH sets of
arguments?
&lt;/p&gt;&lt;p&gt;
One way is to force the stored procedure to recompile each time:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;ALTER PROCEDURE GetStuff&lt;br&gt;	@StartDate DateTime,&lt;br&gt;	@EndDate DateTime,&lt;br&gt;	@Number INT&lt;br&gt;WITH RECOMPILE&lt;br&gt;AS&lt;br&gt;	SELECT &lt;br&gt;		Number,&lt;br&gt;		TheDate,&lt;br&gt;		AnotherCol&lt;br&gt;	FROM DateTbl&lt;br&gt;	WHERE TheDate BETWEEN @StartDate AND @EndDate&lt;br&gt;		OR Number = @Number&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
You'll notice that I added &lt;b&gt;WITH RECOMPILE&lt;/b&gt;. And while that's
probably not a big deal for this example stored procedure, it isn't a
good idea for the types of really complex stored procedures where these
problems crop up in the real world. Recompilation can be quite
intensive, and I really don't want it happening every time an active
stored procedure is called.
&lt;/p&gt;&lt;p&gt;But you already knew that wasn't the solution, because in
elementary school you were taught how to read, and the title of this
article isn't "Controlling Stored Procedure Caching with ... WITH
RECOMPILE".
&lt;/p&gt;&lt;p&gt;
No, instead the title is, "Controlling Stored Procedure Caching with ... Dyanmic SQL?!?"
&lt;/p&gt;&lt;p&gt;
Yes, dynamic SQL.  If you don't know about dynamic SQL, go read &lt;a href="http://www.sommarskog.se/dynamic_sql.html" target="#"&gt;this article&lt;/a&gt; right now and come back when you're finished.
&lt;/p&gt;&lt;p&gt;
You may have heard about a system stored procedure called &lt;b&gt;sp_executesql&lt;/b&gt;.
It lets you evaluate dynamic SQL, but it happens to also cache its
execution plan. In addition, due to the fact that it accepts
parameters, it makes SQL injection nearly impossible if correctly used.
So it's good stuff. We could evaluate our test query using
sp_executesql like this:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @SQL NVARCHAR(300)&lt;br&gt;&lt;br&gt;SET @SQL = '' +&lt;br&gt;	'SELECT ' +&lt;br&gt;		'Number, ' +&lt;br&gt;		'TheDate, ' +&lt;br&gt;		'AnotherCol ' +&lt;br&gt;	'FROM DateTbl ' +&lt;br&gt;	'WHERE TheDate BETWEEN @StartDate AND @EndDate ' +&lt;br&gt;		'OR Number = @Number'&lt;br&gt;&lt;br&gt;EXEC sp_executesql &lt;br&gt;	@SQL, &lt;br&gt;	N'@StartDate DATETIME, @EndDate DATETIME, @Number INT', &lt;br&gt;	@StartDate = '19000201 09:35:00',&lt;br&gt;	@EndDate = '19000201 09:36:00', &lt;br&gt;	@Number = 10&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... And that's just wonderful, but it gives us &lt;i&gt;absolutely nothing&lt;/i&gt;, because if you re-run it with the other parameters you'll find that you have the same problem as the stored procedure version:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @SQL NVARCHAR(300)&lt;br&gt;&lt;br&gt;SET @SQL = '' +&lt;br&gt;	'SELECT ' +&lt;br&gt;		'Number, ' +&lt;br&gt;		'TheDate, ' +&lt;br&gt;		'AnotherCol ' +&lt;br&gt;	'FROM DateTbl ' +&lt;br&gt;	'WHERE TheDate BETWEEN @StartDate AND @EndDate ' +&lt;br&gt;		'OR Number = @Number'&lt;br&gt;&lt;br&gt;EXEC sp_executesql &lt;br&gt;	@SQL, &lt;br&gt;	N'@StartDate DATETIME, @EndDate DATETIME, @Number INT', &lt;br&gt;	@StartDate = '19000101',&lt;br&gt;	@EndDate = '19000201', &lt;br&gt;	@Number = 10&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Time for another coffee break...
&lt;/p&gt;&lt;p&gt;But let us not lose hope yet, because we're still in the article
that's talking about how to control caching and recompilation and you
know that I wouldn't have written this article if I didn't know the
answer.
&lt;/p&gt;&lt;p&gt;
So what's &lt;i&gt;really&lt;/i&gt; being cached here?  Let's take a look:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT sql&lt;br&gt;FROM master..syscacheobjects&lt;br&gt;WHERE sql LIKE '%datetbl%'&lt;br&gt;	AND cacheobjtype = 'Executable Plan'&lt;br&gt;&lt;br&gt;-----------------------------------------------------&lt;br&gt;&lt;br&gt;(@StartDate DATETIME, @EndDate DATETIME, @Number INT)&lt;br&gt;SELECT &lt;br&gt;	Number, &lt;br&gt;	TheDate, &lt;br&gt;	AnotherCol &lt;br&gt;FROM DateTbl &lt;br&gt;WHERE TheDate BETWEEN @StartDate &lt;br&gt;	AND @EndDate OR Number = @Number&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;The cached plan is cached for not just the query, but also a
parameter list -- and not just any parameter list, but the very
parameter list that was passed in to sp_executesql. So how could we
force SQL Server to cache a different plan for the same query?
&lt;/p&gt;&lt;p&gt;
... Change the parameter list!
&lt;/p&gt;&lt;p&gt;The parameter list, of course, is correlated to the actual
parameters passed in. But what you may not realize is that if you
satisfy a parameter within the list, sp_executesql will not expect a
correlated parameter to be passed in. For instance, the following is
perfectly valid:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @SQL NVARCHAR(300)&lt;br&gt;&lt;br&gt;SET @SQL = '' +&lt;br&gt;	'SELECT @TheParam AS TheParam'&lt;br&gt;&lt;br&gt;EXEC sp_executesql &lt;br&gt;	@SQL, &lt;br&gt;	N'@TheParam VARCHAR(100) = ''This is the param'''&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Not only that, but it's been cached:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT sql&lt;br&gt;FROM master..syscacheobjects&lt;br&gt;WHERE sql LIKE '%param%'&lt;br&gt;	AND cacheobjtype = 'Executable Plan'&lt;br&gt;&lt;br&gt;-----------------------------------------------------&lt;br&gt;&lt;br&gt;(@TheParam VARCHAR(100) = 'This is the param')&lt;br&gt;SELECT @TheParam AS TheParam&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
So what happens if we change our parameter's value?
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @SQL NVARCHAR(300)&lt;br&gt;&lt;br&gt;SET @SQL = '' +&lt;br&gt;	'SELECT @TheParam AS TheParam'&lt;br&gt;&lt;br&gt;EXEC sp_executesql &lt;br&gt;	@SQL, &lt;br&gt;	N'@TheParam VARCHAR(100) = ''This is the other_param'''&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Same query, but...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT sql&lt;br&gt;FROM master..syscacheobjects&lt;br&gt;WHERE sql LIKE '%other_param%'&lt;br&gt;	AND cacheobjtype = 'Executable Plan'&lt;br&gt;&lt;br&gt;-----------------------------------------------------&lt;br&gt;&lt;br&gt;(@TheParam VARCHAR(100) = 'This is the other_param')&lt;br&gt;SELECT @TheParam AS TheParam&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;Yes, a second cached execution plan! Exciting, isn't it? Kind of
like winning the lottery, only even better, because you don't have to
worry about how to spend all of that extra cash!
&lt;/p&gt;&lt;p&gt;So how do we put this all together? A quick recap: We know that
the query requires at least two execution plans; one for big date
ranges, and one for smaller date ranges. There might be more, but we
haven't tested that, so I'll leave it as an exercise for the reader. We
also know that sp_executesql will cache a second, third, or &lt;i&gt;Nth&lt;/i&gt;
execution plan whenever the parameter list is changed. So all we need
to do is change the parameter list depending on the inputs...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;ALTER PROC GetStuff&lt;br&gt;	@StartDate DATETIME,&lt;br&gt;	@EndDate DATETIME,&lt;br&gt;	@Number INT&lt;br&gt;AS&lt;br&gt;	DECLARE @SQL NVARCHAR(300)&lt;br&gt;	DECLARE @Params NVARCHAR(100)&lt;br&gt;&lt;br&gt;	SET @SQL = '' +&lt;br&gt;		'SELECT ' +&lt;br&gt;			'Number, ' +&lt;br&gt;			'TheDate, ' +&lt;br&gt;			'AnotherCol ' +&lt;br&gt;		'FROM DateTbl ' +&lt;br&gt;		'WHERE TheDate BETWEEN @StartDate AND @EndDate ' +&lt;br&gt;			'OR Number = @Number'&lt;br&gt;&lt;br&gt;	IF DATEDIFF(hh, @StartDate, @EndDate) &amp;lt;= 2&lt;br&gt;		SET @Params = '@StartDate DATETIME, @EndDate DATETIME, @Number INT, @dX1 INT = 1'&lt;br&gt;	ELSE&lt;br&gt;		SET @Params = '@StartDate DATETIME, @EndDate DATETIME, @Number INT, @dX1 INT = 2'&lt;br&gt;&lt;br&gt;	EXEC sp_executesql &lt;br&gt;		@SQL, &lt;br&gt;		@Params, &lt;br&gt;		@StartDate, &lt;br&gt;		@EndDate, &lt;br&gt;		@Number&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;Pretending that we've actually tested for the correct thresholds
(which you should do if you use this technique), you'll notice that
we're forcing a different execution plan if the time between start date
and end date is less than or equal to two hours (that will be an index
seek) or more than two hours (that will be an index scan).
&lt;/p&gt;&lt;p&gt;Since forcing evaluation of a new execution plan in this case
is simply a matter of changing the value of @dX1, you can add as many
conditions as necessary to control which cached plan is used for any
given set of arguments. Two hours is almost certainly not the best
choice here, but really, does it matter?
&lt;/p&gt;&lt;p&gt;So in conclusion, blah, blah, blah... No one reads this far,
you stopped after you saw the final stored procedure, didn't you? Have
a nice day, and enjoy your new, more dynamic stored procedures.&lt;/p&gt;</description></item></channel></rss>