<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-34589901</id><updated>2011-10-10T09:10:17.096-07:00</updated><title type='text'>SQL Server Tips</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>31</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-34589901.post-5979229228595543739</id><published>2007-09-06T08:15:00.000-07:00</published><updated>2007-09-06T08:17:28.525-07:00</updated><title type='text'>The blog has moved to www.sqlblogcasts.com</title><content type='html'>I will continue posting over there at www.sqlblogcasts.com.&lt;br /&gt;Simon Sabon has moved the blog over to www.sqlblogcasts.com.&lt;br /&gt;Thank you Simon!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-5979229228595543739?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/5979229228595543739/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=5979229228595543739' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5979229228595543739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5979229228595543739'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/09/blog-has-moved-to-wwwsqlblogcastscom.html' title='The blog has moved to www.sqlblogcasts.com'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-9111479667437665877</id><published>2007-08-24T09:57:00.000-07:00</published><updated>2007-08-24T09:58:36.130-07:00</updated><title type='text'>Selecting a random number for each row</title><content type='html'>&lt;p&gt;create view wrapped_rand_view&lt;br /&gt;as&lt;br /&gt;select rand( ) as random_value&lt;br /&gt;go&lt;br /&gt;create function wrapped_rand()&lt;br /&gt;returns float&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;declare @f float&lt;br /&gt;set @f = (select random_value from wrapped_rand_view)&lt;br /&gt;return @f&lt;br /&gt;end&lt;br /&gt;&lt;/p&gt;select --your columns--, dbo.wrapped_rand() wrapped_rand&lt;br /&gt;from YourTable&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-9111479667437665877?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/9111479667437665877/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=9111479667437665877' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/9111479667437665877'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/9111479667437665877'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/08/selecting-random-number-for-each-row.html' title='Selecting a random number for each row'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-5679855739718541357</id><published>2007-08-22T10:59:00.000-07:00</published><updated>2007-08-22T11:06:56.560-07:00</updated><title type='text'>How to select a second page faster</title><content type='html'>I used to think that using TOP clause to select a second page is faster than using ROW_NUMBER(). Apparently this is no longer true (although the plans are somewhat different):&lt;br /&gt;&lt;br /&gt;-- this populates IDs with 3M rows, and the CAST strips IDENTITY property&lt;br /&gt;SELECT CAST(my_ID*1 AS BIGINT) as ID INTO IDs FROM MyTable&lt;br /&gt;GO&lt;br /&gt;CREATE UNIQUE CLUSTERED INDEX PK_IDs ON IDs(ID)&lt;br /&gt;GO&lt;br /&gt;SET STATISTICS TIME ON&lt;br /&gt;SET STATISTICS IO ON&lt;br /&gt;GO&lt;br /&gt;SELECT TOP 20 ID FROM(&lt;br /&gt;SELECT TOP 40 ID FROM IDs ORDER BY ID) t&lt;br /&gt;ORDER BY ID DESC&lt;br /&gt;GO&lt;br /&gt;/*&lt;br /&gt;Table 'IDs'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;&lt;br /&gt;SQL Server Execution Times:&lt;br /&gt;  CPU time = 0 ms,  elapsed time = 1 ms.&lt;br /&gt;&lt;br /&gt;A simple execution plan. Correct cardinality estimate:&lt;br /&gt;Estimated Number of Rows: 40&lt;br /&gt;*/&lt;br /&gt;SELECT ID FROM(&lt;br /&gt;SELECT ID, ROW_NUMBER() OVER(ORDER BY ID) rn FROM IDs) t&lt;br /&gt;WHERE rn BETWEEN 21 AND 40&lt;br /&gt;GO&lt;br /&gt;/*&lt;br /&gt;Same real execution costs:&lt;br /&gt;Table 'IDs'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;&lt;br /&gt;SQL Server Execution Times:&lt;br /&gt;  CPU time = 0 ms,  elapsed time = 1 ms.&lt;br /&gt;&lt;br /&gt;A different, more complex plan.&lt;br /&gt;I was surprized to see cost of one step exceed 100%: Segment Cost: 1771%&lt;br /&gt;Also this estimate was somewhat askew: Estimated Number of Rows: 100&lt;br /&gt;*/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-5679855739718541357?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/5679855739718541357/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=5679855739718541357' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5679855739718541357'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5679855739718541357'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/08/how-to-select-second-page-faster.html' title='How to select a second page faster'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-5781397621217943815</id><published>2007-08-21T10:25:00.000-07:00</published><updated>2007-08-21T10:26:58.474-07:00</updated><title type='text'>next article on simple-talk.com</title><content type='html'>"&lt;a id="ctl00_MainContent_Homepage1_repHomepage_ctl02_lnkTitle" class="title" href="http://www.simple-talk.com/sql/t-sql-programming/close-those-loopholes---testing-stored-procedures--/"&gt;Close those Loopholes - Testing Stored Procedures&lt;/a&gt;"&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-5781397621217943815?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/5781397621217943815/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=5781397621217943815' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5781397621217943815'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5781397621217943815'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/08/next-article-on-simple-talkcom.html' title='next article on simple-talk.com'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-4134077686808832905</id><published>2007-08-19T12:29:00.000-07:00</published><updated>2007-08-19T12:37:25.748-07:00</updated><title type='text'>Testing stored procedures which use dynamic SQL</title><content type='html'>&lt;p class="MsoNormal"&gt;Consider the following stored procedure which uses dynamic SQL:&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;CREATE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:blue;"&gt;PROCEDURE&lt;/span&gt; Readers&lt;span style="color:gray;"&gt;.&lt;/span&gt;SelectEmployeesByName&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@FirstName &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;20&lt;span style="color:gray;"&gt;),&lt;/span&gt; &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@LastName &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;20&lt;span style="color:gray;"&gt;),&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@Status &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;6&lt;span style="color:gray;"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;AS&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;IF&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @Status &lt;span style="color:gray;"&gt;IS&lt;/span&gt; &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:gray;"&gt;AND&lt;/span&gt; @Status &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;IN(&lt;/span&gt;&lt;span style="color:red;"&gt;'Active'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt; &lt;span style="color:red;"&gt;'Quit'&lt;/span&gt;&lt;span style="color:gray;"&gt;)&lt;/span&gt; &lt;span style="color:blue;"&gt;BEGIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;RAISERROR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'Readers.SelectEmployeesByName: Invalid Status %s'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt; 16&lt;span style="color:gray;"&gt;,&lt;/span&gt; 1&lt;span style="color:gray;"&gt;,&lt;/span&gt; @Status&lt;span style="color:gray;"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;RETURN&lt;/span&gt; &lt;span style="color:gray;"&gt;-&lt;/span&gt;1&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;END&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;IF&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @FirstName &lt;span style="color:gray;"&gt;IS&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:gray;"&gt;AND&lt;/span&gt; @LastName &lt;span style="color:gray;"&gt;IS&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:gray;"&gt;AND&lt;/span&gt; @Status &lt;span style="color:gray;"&gt;IS&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:blue;"&gt;BEGIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;RAISERROR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'Readers.SelectEmployeesByName: MustProvide At Least One NOT NULL Parameter'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt; 16&lt;span style="color:gray;"&gt;,&lt;/span&gt; 1&lt;span style="color:gray;"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;RETURN&lt;/span&gt; &lt;span style="color:gray;"&gt;-&lt;/span&gt;1&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;END&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;DECLARE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @SQL &lt;span style="color:blue;"&gt;NVARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;3000&lt;span style="color:gray;"&gt;),&lt;/span&gt; @SQLSeparator &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;5&lt;span style="color:gray;"&gt;),&lt;/span&gt; @params &lt;span style="color:blue;"&gt;NVARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;1000&lt;span style="color:gray;"&gt;),&lt;/span&gt; @comma &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;1&lt;span style="color:gray;"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;SELECT&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @SQLSeparator &lt;span style="color:gray;"&gt;=&lt;/span&gt; &lt;span style="color:red;"&gt;'WHERE'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt; &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@params &lt;span style="color:gray;"&gt;=&lt;/span&gt; &lt;span style="color:red;"&gt;'@FirstName VARCHAR(20), @LastName VARCHAR(20), @Status VARCHAR(6)'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:green;"   &gt;--&lt;span style=""&gt;  &lt;/span&gt;@comma = ''&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@SQL &lt;span style="color:gray;"&gt;=&lt;/span&gt; &lt;span style="color:red;"&gt;'SELECT FirstName, LastName, Status, HireDate FROM data.Employees '&lt;/span&gt; &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;IF&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @FirstName &lt;span style="color:gray;"&gt;IS&lt;/span&gt; &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:blue;"&gt;BEGIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;SET&lt;/span&gt; @SQL &lt;span style="color:gray;"&gt;=&lt;/span&gt; @SQL &lt;span style="color:gray;"&gt;+&lt;/span&gt; @SQLSeparator &lt;span style="color:gray;"&gt;+&lt;/span&gt; &lt;span style="color:red;"&gt;' FirstName = @FirstName '&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:green;"&gt;--SET @params = @params + @comma + '@FirstName VARCHAR(20)' + @comma&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;SET&lt;/span&gt; @SQLSeparator &lt;span style="color:gray;"&gt;=&lt;/span&gt; &lt;span style="color:red;"&gt;'AND'&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:green;"&gt;--SET @comma = ','&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;END&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;IF&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @LastName &lt;span style="color:gray;"&gt;IS&lt;/span&gt; &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:blue;"&gt;BEGIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;SET&lt;/span&gt; @SQL &lt;span style="color:gray;"&gt;=&lt;/span&gt; @SQL &lt;span style="color:gray;"&gt;+&lt;/span&gt; @SQLSeparator &lt;span style="color:gray;"&gt;+&lt;/span&gt; &lt;span style="color:red;"&gt;' LastName = @LastName '&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:green;"&gt;--SET @params = @params + @comma + '@LastName VARCHAR(20)' + @comma&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;SET&lt;/span&gt; @SQLSeparator &lt;span style="color:gray;"&gt;=&lt;/span&gt; &lt;span style="color:red;"&gt;'AND'&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:green;"&gt;--SET @comma = ','&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;END&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;IF&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @Status &lt;span style="color:gray;"&gt;IS&lt;/span&gt; &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:blue;"&gt;BEGIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;SET&lt;/span&gt; @SQL &lt;span style="color:gray;"&gt;=&lt;/span&gt; @SQL &lt;span style="color:gray;"&gt;+&lt;/span&gt; @SQLSeparator &lt;span style="color:gray;"&gt;+&lt;/span&gt; &lt;span style="color:red;"&gt;' Status = @Status'&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;END&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:green;"   &gt;-- PRINT for debugging purposes only&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;PRINT&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @SQL&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;EXEC&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:maroon;"&gt;sp_executeSQL&lt;/span&gt; @SQL&lt;span style="color:gray;"&gt;,&lt;/span&gt; @params&lt;span style="color:gray;"&gt;,&lt;/span&gt; @FirstName&lt;span style="color:gray;"&gt;,&lt;/span&gt; @LastName&lt;span style="color:gray;"&gt;,&lt;/span&gt; @Status&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;GO&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;It is very easy &lt;a style=""&gt;to make sure &lt;/a&gt;&lt;span class="MsoCommentReference"&gt;&lt;span style="font-size:8;"&gt;&lt;a class="msocomanchor" id="_anchor_1" onmouseover="msoCommentShow('_anchor_1','_com_1')" onmouseout="msoCommentHide('_com_1')" href="http://www.blogger.com/post-edit.g?blogID=34589901&amp;postID=4134077686808832905#_msocom_1" language="JavaScript" name="_msoanchor_1"&gt;&lt;/a&gt;&lt;!--[endif]--&gt;&lt;span style=""&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;that the procedure always generates valid SQL which compiles:&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;DECLARE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; params &lt;span style="color:blue;"&gt;CURSOR&lt;/span&gt; LOCAL STATIC&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;FOR&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:blue;"&gt;SELECT&lt;/span&gt; FirstName&lt;span style="color:gray;"&gt;,&lt;/span&gt; LastName&lt;span style="color:gray;"&gt;,&lt;/span&gt; Status&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;FROM&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:blue;"&gt;SELECT&lt;/span&gt; &lt;span style="color:red;"&gt;'Jeff'&lt;/span&gt; FirstName &lt;span style="color:blue;"&gt;UNION&lt;/span&gt; &lt;span style="color:gray;"&gt;ALL&lt;/span&gt; &lt;span style="color:blue;"&gt;SELECT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL)&lt;/span&gt; t1 &lt;span style="color:gray;"&gt;CROSS&lt;/span&gt; &lt;span style="color:gray;"&gt;JOIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:gray;"   &gt;(&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;SELECT&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:red;"&gt;'Jones'&lt;/span&gt; LastName &lt;span style="color:blue;"&gt;UNION&lt;/span&gt; &lt;span style="color:gray;"&gt;ALL&lt;/span&gt; &lt;span style="color:blue;"&gt;SELECT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL)&lt;/span&gt; t2 &lt;span style="color:gray;"&gt;CROSS&lt;/span&gt; &lt;span style="color:gray;"&gt;JOIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:gray;"   &gt;(&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;SELECT&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:red;"&gt;'Active'&lt;/span&gt; Status &lt;span style="color:blue;"&gt;UNION&lt;/span&gt; &lt;span style="color:gray;"&gt;ALL&lt;/span&gt; &lt;span style="color:blue;"&gt;SELECT&lt;/span&gt; &lt;span style="color:red;"&gt;'Retired'&lt;/span&gt; &lt;span style="color:blue;"&gt;UNION&lt;/span&gt; &lt;span style="color:gray;"&gt;ALL&lt;/span&gt; &lt;span style="color:blue;"&gt;SELECT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL)&lt;/span&gt; t3&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;DECLARE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;   &lt;/span&gt;@FirstName &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;20&lt;span style="color:gray;"&gt;),&lt;/span&gt; &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@LastName &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;20&lt;span style="color:gray;"&gt;),&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@Status &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;6&lt;span style="color:gray;"&gt;),&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@keepFetching &lt;span style="color:blue;"&gt;INT&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;SET&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @keepFetching &lt;span style="color:gray;"&gt;=&lt;/span&gt; 1&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;OPEN&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; params&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;WHILE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @keepFetching &lt;span style="color:gray;"&gt;=&lt;/span&gt; 1 &lt;span style="color:blue;"&gt;BEGIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;FETCH&lt;/span&gt; params &lt;span style="color:blue;"&gt;INTO&lt;/span&gt; @FirstName&lt;span style="color:gray;"&gt;,&lt;/span&gt; @LastName&lt;span style="color:gray;"&gt;,&lt;/span&gt; @Status &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;IF&lt;/span&gt; &lt;span style="color:fuchsia;"&gt;@@FETCH_STATUS&lt;/span&gt; &lt;span style="color:gray;"&gt;&lt;&gt;&lt;/span&gt; 0 &lt;span style="color:blue;"&gt;BEGIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;    &lt;/span&gt;&lt;span style="color:blue;"&gt;SET&lt;/span&gt; @keepFetching &lt;span style="color:gray;"&gt;=&lt;/span&gt; 0&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;END&lt;/span&gt; &lt;span style="color:blue;"&gt;ELSE&lt;/span&gt; &lt;span style="color:blue;"&gt;BEGIN&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;    &lt;/span&gt;&lt;span style="color:blue;"&gt;EXEC&lt;/span&gt; Readers&lt;span style="color:gray;"&gt;.&lt;/span&gt;SelectEmployeesByName @FirstName&lt;span style="color:gray;"&gt;,&lt;/span&gt; @LastName&lt;span style="color:gray;"&gt;,&lt;/span&gt; @Status&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;span style="color:blue;"&gt;END&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;END&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;CLOSE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; params&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;DEALLOCATE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; params&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Even though the procedure takes only three parameters, there are twelve different combinations that need to be considered. Five combinations are invalid and are rejected without running dynamic SQL. Seven combinations out of twelve are valid and as such need to be tested.&lt;span style=""&gt;  &lt;/span&gt;Running seven unit tests is quite reasonable. Suppose, however, that more parameters are added to the procedure:&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;CREATE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:blue;"&gt;PROCEDURE&lt;/span&gt; Readers&lt;span style="color:gray;"&gt;.&lt;/span&gt;SelectEmployees&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@FirstName &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;20&lt;span style="color:gray;"&gt;),&lt;/span&gt; &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@LastName &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;20&lt;span style="color:gray;"&gt;),&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@Status &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;6&lt;span style="color:gray;"&gt;),&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@HireDateFrom &lt;span style="color:blue;"&gt;DATETIME&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@HireDateTo &lt;span style="color:blue;"&gt;DATETIME&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;Now you need to test at least forty eight combinations. It is still easy to make sure that dynamic SQL always compiles - you just need to add two more cross joins to your cursor. However, adding more and more unit tests to test results for all possible combinations &lt;span class="MsoCommentReference"&gt;&lt;span style="font-size:8;"&gt;&lt;span style=""&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;becomes less and less feasible. Usually you want to test only the combinations that you actually need, and you do not want to expose untested combinations. For example, you could create the following two procedures:&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;CREATE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:blue;"&gt;PROCEDURE&lt;/span&gt; Readers&lt;span style="color:gray;"&gt;.&lt;/span&gt;SelectEmployeesByNameAndStatus&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@FirstName &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;20&lt;span style="color:gray;"&gt;),&lt;/span&gt; &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@LastName &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;20&lt;span style="color:gray;"&gt;),&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@Status &lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;6&lt;span style="color:gray;"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;AS&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:green;"   &gt;-- twelve combinations exposed by this procedure&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;DECLARE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @ret &lt;span style="color:blue;"&gt;INT&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;EXEC&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @ret &lt;span style="color:gray;"&gt;=&lt;/span&gt; Readers&lt;span style="color:gray;"&gt;.&lt;/span&gt;SelectEmployees @FirstName&lt;span style="color:gray;"&gt;,&lt;/span&gt; @LastName&lt;span style="color:gray;"&gt;,&lt;/span&gt; @Status&lt;span style="color:gray;"&gt;,&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;RETURN&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @ret&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;GO&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;CREATE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; &lt;span style="color:blue;"&gt;PROCEDURE&lt;/span&gt; Readers&lt;span style="color:gray;"&gt;.&lt;/span&gt;SelectEmployeesByHireDate&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@HireDateFrom &lt;span style="color:blue;"&gt;DATETIME&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style=""&gt;  &lt;/span&gt;@HireDateTo &lt;span style="color:blue;"&gt;DATETIME&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;AS&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:green;"   &gt;-- four combinations exposed by this procedure&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;DECLARE&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @ret &lt;span style="color:blue;"&gt;INT&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;EXEC&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @ret &lt;span style="color:gray;"&gt;=&lt;/span&gt; Readers&lt;span style="color:gray;"&gt;.&lt;/span&gt;SelectEmployees &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt; @HireDateFrom&lt;span style="color:gray;"&gt;,&lt;/span&gt; @HireDateTo&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;color:blue;"   &gt;RETURN&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt; @ret&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;GO&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;That done, you could revoke permissions to execute the original procedure, &lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;Readers&lt;span style="color:gray;"&gt;.&lt;/span&gt;SelectEmployees&lt;/span&gt; - that would reduce the number of combinations which you need to test fully to only sixteen. Clearly there are other ways to deal with this problem, but in any case &lt;b style=""&gt;&lt;i style=""&gt;avoid exposing untested code.&lt;/i&gt;&lt;/b&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;  &lt;div style=""&gt;&lt;!--[if !supportAnnotations]--&gt;  &lt;hr class="msocomoff" align="left"  width="33%" style="font-size:78%;"&gt;  &lt;!--[endif]--&gt;  &lt;div style=""&gt;&lt;!--[if !supportAnnotations]--&gt;  &lt;div id="_com_1" class="msocomtxt" language="JavaScript" onmouseover="msoCommentShow('_anchor_1','_com_1')" onmouseout="msoCommentHide('_com_1')"&gt;&lt;!--[endif]--&gt;&lt;span style=""&gt;&lt;!--[if !supportAnnotations]--&gt;&lt;a name="_msocom_1"&gt;&lt;/a&gt;&lt;!--[endif]--&gt;&lt;/span&gt;  &lt;p class="MsoCommentText"&gt;&lt;span class="MsoCommentReference"&gt;&lt;span style="font-size:8;"&gt;&lt;span style=""&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;  &lt;!--[if !supportAnnotations]--&gt;&lt;/div&gt;  &lt;!--[endif]--&gt;&lt;/div&gt;  &lt;div style=""&gt;&lt;!--[if !supportAnnotations]--&gt;  &lt;div id="_com_2" class="msocomtxt" language="JavaScript" onmouseover="msoCommentShow('_anchor_2','_com_2')" onmouseout="msoCommentHide('_com_2')"&gt;&lt;!--[endif]--&gt;&lt;span style=""&gt;&lt;!--[if !supportAnnotations]--&gt;&lt;a name="_msocom_2"&gt;&lt;/a&gt;&lt;!--[endif]--&gt;&lt;/span&gt;  &lt;p class="MsoCommentText"&gt;&lt;span class="MsoCommentReference"&gt;&lt;span style="font-size:8;"&gt;&lt;span style=""&gt;&lt;!--[if !supportAnnotations]--&gt;&lt;!--[endif]--&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;  &lt;!--[if !supportAnnotations]--&gt;&lt;/div&gt;  &lt;!--[endif]--&gt;&lt;/div&gt;  &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-4134077686808832905?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/4134077686808832905/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=4134077686808832905' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/4134077686808832905'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/4134077686808832905'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/08/testing-stored-procedures-using-dynamic.html' title='Testing stored procedures which use dynamic SQL'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-105018545223826239</id><published>2007-08-14T10:17:00.000-07:00</published><updated>2007-08-14T10:18:10.181-07:00</updated><title type='text'>My first article on simple-talk.com</title><content type='html'>"Close These Loopholes in Your Database Testing"&lt;br /&gt;[http://www.simple-talk.com/sql/t-sql-programming/close-these-loopholes-in-your-database-testing/]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-105018545223826239?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/105018545223826239/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=105018545223826239' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/105018545223826239'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/105018545223826239'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/08/my-first-article-on-simple-talkcom.html' title='My first article on simple-talk.com'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-7062401835137638098</id><published>2007-08-01T10:20:00.001-07:00</published><updated>2007-08-01T10:22:23.124-07:00</updated><title type='text'>Making Sure All The Objects Are Qualified</title><content type='html'>is quite simple: I do not have any objects in dbo schema at all. Tables are in created data. or staging. schemas.&lt;br /&gt;Procedures are created in Writers and Readers schemas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-7062401835137638098?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/7062401835137638098/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=7062401835137638098' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/7062401835137638098'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/7062401835137638098'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/08/making-sure-all-objects-are-qualified.html' title='Making Sure All The Objects Are Qualified'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-2161000195815013275</id><published>2007-07-23T14:13:00.000-07:00</published><updated>2007-07-23T14:14:30.428-07:00</updated><title type='text'>All Permutations For A String</title><content type='html'>Suppose you have a auxiliary Numbers table with integer numbers.&lt;br /&gt;&lt;p&gt;DECLARE @s VARCHAR(5);&lt;br /&gt;SET @s = 'ABCDE';&lt;br /&gt;&lt;/p&gt;&lt;p&gt;WITH Subsets AS (&lt;br /&gt;SELECT CAST(SUBSTRING(@s, Number, 1) AS VARCHAR(5)) AS Token,&lt;br /&gt;CAST('.'+CAST(Number AS CHAR(1))+'.' AS VARCHAR(11)) AS Permutation,&lt;br /&gt;CAST(1 AS INT) AS Iteration&lt;br /&gt;FROM dbo.Numbers WHERE Number BETWEEN 1 AND 5&lt;br /&gt;UNION ALL&lt;br /&gt;SELECT CAST(Token+SUBSTRING(@s, Number, 1) AS VARCHAR(5)) AS Token,&lt;br /&gt;CAST(Permutation+CAST(Number AS CHAR(1))+'.' AS VARCHAR(11)) AS&lt;br /&gt;Permutation,&lt;br /&gt;s.Iteration + 1 AS Iteration&lt;br /&gt;FROM Subsets s JOIN dbo.Numbers n ON s.Permutation NOT LIKE&lt;br /&gt;'%.'+CAST(Number AS CHAR(1))+'.%' AND s.Iteration &lt; 5 AND Number&lt;br /&gt;BETWEEN 1 AND 5&lt;br /&gt;--AND s.Iteration = (SELECT MAX(Iteration) FROM Subsets)&lt;br /&gt;)&lt;br /&gt;SELECT * FROM Subsets&lt;br /&gt;WHERE Iteration = 5&lt;br /&gt;ORDER BY Permutation&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Token Permutation Iteration&lt;br /&gt;----- ----------- -----------&lt;br /&gt;ABCDE .1.2.3.4.5. 5&lt;br /&gt;ABCED .1.2.3.5.4. 5&lt;br /&gt;ABDCE .1.2.4.3.5. 5&lt;br /&gt;(snip)&lt;br /&gt;EDBCA .5.4.2.3.1. 5&lt;br /&gt;EDCAB .5.4.3.1.2. 5&lt;br /&gt;EDCBA .5.4.3.2.1. 5&lt;br /&gt;&lt;/p&gt;(120 row(s) affected)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-2161000195815013275?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/2161000195815013275/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=2161000195815013275' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/2161000195815013275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/2161000195815013275'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/07/all-permutations-for-string.html' title='All Permutations For A String'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-2982646320066784703</id><published>2007-07-23T06:19:00.000-07:00</published><updated>2007-07-23T06:20:22.695-07:00</updated><title type='text'>Which Roles Does The User Belong To?</title><content type='html'>SELECT r.[name] Role_Name FROM sys.database_principals u&lt;br /&gt;JOIN sys.database_role_members rm ON u.principal_id = rm.member_principal_id&lt;br /&gt;JOIN sys.database_principals r ON rm.role_principal_id = r.principal_id&lt;br /&gt;WHERE u.[name] = USER_NAME()&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-2982646320066784703?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/2982646320066784703/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=2982646320066784703' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/2982646320066784703'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/2982646320066784703'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/07/which-roles-does-user-belong-to.html' title='Which Roles Does The User Belong To?'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-8060607345209758632</id><published>2007-07-03T11:50:00.000-07:00</published><updated>2007-07-03T11:51:34.802-07:00</updated><title type='text'>I got a SQL Server MVP award.</title><content type='html'>I got a SQL Server MVP award.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-8060607345209758632?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/8060607345209758632/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=8060607345209758632' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/8060607345209758632'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/8060607345209758632'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/07/i-got-sql-server-mvp-award.html' title='I got a SQL Server MVP award.'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-5278192493714747071</id><published>2007-06-22T11:35:00.000-07:00</published><updated>2007-06-22T11:37:16.227-07:00</updated><title type='text'>One More Article on Devx.com</title><content type='html'>"Enforce Business Rules for All Your Data with Constraints"&lt;br /&gt;http://www.devx.com/dbzone/Article/34479&lt;br /&gt;&lt;br /&gt;This article is a reply to Paul Nielsen's post on sqlblog.com&lt;br /&gt;&lt;br /&gt;http://sqlblog.com/blogs/paul_nielsen/archive/2006/12/18/430.aspx#comments&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-5278192493714747071?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/5278192493714747071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=5278192493714747071' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5278192493714747071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5278192493714747071'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/06/one-more-article-on-devxcom.html' title='One More Article on Devx.com'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-7827565575470327185</id><published>2007-06-21T15:53:00.000-07:00</published><updated>2007-06-21T15:57:15.318-07:00</updated><title type='text'>Materialized Path: Making Sure a Tree has No Cycles</title><content type='html'>The following DDL guarantees no cycles for Materialized Path:&lt;br /&gt;&lt;br /&gt;CREATE TABLE dbo.OrgChart(GroupName VARCHAR(20) NOT NULL,&lt;br /&gt;  ParentPath VARCHAR(900) NULL,&lt;br /&gt;  FullPath VARCHAR(900) NOT NULL,&lt;br /&gt;  CONSTRAINT PK_OrgChart PRIMARY KEY(GroupName),&lt;br /&gt;  CONSTRAINT OrgChart_NoBackslashInGroupName CHECK(GroupName =&lt;br /&gt;REPLACE(GroupName, '\', '')),&lt;br /&gt;  CONSTRAINT OrgChart_CorrectFullPath CHECK(FullPath = CASE WHEN&lt;br /&gt;ParentPath IS NULL THEN '' ELSE ParentPath + '\' END&lt;br /&gt;                        + GroupName),&lt;br /&gt;  CONSTRAINT OrgChart_ParentPath_FK FOREIGN KEY(ParentPath) REFERENCES&lt;br /&gt;OrgChart(FullPath),&lt;br /&gt;  CONSTRAINT OrgChart_NoCycles CHECK (ParentPath NOT LIKE GroupName +&lt;br /&gt;'\%' AND ParentPath NOT LIKE '%\'+ GroupName + '\%')&lt;br /&gt;)&lt;br /&gt;go&lt;br /&gt;SET NOCOUNT ON&lt;br /&gt;-- adding some valid data&lt;br /&gt;INSERT dbo.OrgChart(GroupName, ParentPath, FullPath) VALUES('US', NULL,&lt;br /&gt;'US')&lt;br /&gt;INSERT dbo.OrgChart(GroupName, ParentPath, FullPath)&lt;br /&gt;VALUES('Chicagoland', 'US', 'US\Chicagoland')&lt;br /&gt;INSERT dbo.OrgChart(GroupName, ParentPath, FullPath) VALUES('Loop&lt;br /&gt;Office', 'US\Chicagoland', 'US\Chicagoland\Loop Office')&lt;br /&gt;INSERT dbo.OrgChart(GroupName, ParentPath, FullPath)&lt;br /&gt;  VALUES('Naperville Lab', 'US\Chicagoland', 'US\Chicagoland\Naperville&lt;br /&gt;Lab')&lt;br /&gt;INSERT dbo.OrgChart(GroupName, ParentPath, FullPath)&lt;br /&gt;  VALUES('Lisle Datacenter', 'US\Chicagoland', 'US\Chicagoland\Lisle&lt;br /&gt;Datacenter')&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-7827565575470327185?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/7827565575470327185/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=7827565575470327185' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/7827565575470327185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/7827565575470327185'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/06/materialized-path-making-sure-tree-has.html' title='Materialized Path: Making Sure a Tree has No Cycles'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-2233374531282790382</id><published>2007-06-20T14:30:00.000-07:00</published><updated>2007-06-20T14:41:05.416-07:00</updated><title type='text'>Getting All Subsets of a Set</title><content type='html'>For example, let us generate all subsets of a set with 10 different elements.&lt;br /&gt;&lt;br /&gt;-- you will need two auxiliary tables&lt;br /&gt;SELECT * INTO dbo.Numbers FROM AnotherDB.dbo.Numbers WHERE Number BETWEEN 1 AND 1024&lt;br /&gt;GO&lt;br /&gt;CREATE UNIQUE CLUSTERED INDEX abcd ON Numbers(Number)&lt;br /&gt;GO&lt;br /&gt;-- another auxiliary table&lt;br /&gt;CREATE TABLE NumbersIn2Power(Number INT NOT NULL, Power2 INT NOT NULL)&lt;br /&gt;INSERT NumbersIn2Power VALUES(1,1)&lt;br /&gt;INSERT NumbersIn2Power VALUES(2,2)&lt;br /&gt;INSERT NumbersIn2Power VALUES(3,4)&lt;br /&gt;INSERT NumbersIn2Power VALUES(4,8)&lt;br /&gt;INSERT NumbersIn2Power VALUES(5,16)&lt;br /&gt;INSERT NumbersIn2Power VALUES(6,32)&lt;br /&gt;INSERT NumbersIn2Power VALUES(7,64)&lt;br /&gt;INSERT NumbersIn2Power VALUES(8,128)&lt;br /&gt;INSERT NumbersIn2Power VALUES(9,256)&lt;br /&gt;INSERT NumbersIn2Power VALUES(10,512)&lt;br /&gt;GO&lt;br /&gt;CREATE UNIQUE CLUSTERED INDEX abcdef ON NumbersIn2Power(Number)&lt;br /&gt;GO&lt;br /&gt;&lt;br /&gt;-- the set itself has 10 elements&lt;br /&gt;&lt;br /&gt;CREATE TABLE #t(itemNum INT, val INT)&lt;br /&gt;INSERT #t VALUES(1,11)&lt;br /&gt;INSERT #t VALUES(2,12)&lt;br /&gt;INSERT #t VALUES(3,13)&lt;br /&gt;INSERT #t VALUES(4,14)&lt;br /&gt;INSERT #t VALUES(5,15)&lt;br /&gt;INSERT #t VALUES(6,16)&lt;br /&gt;INSERT #t VALUES(7,17)&lt;br /&gt;INSERT #t VALUES(8,18)&lt;br /&gt;INSERT #t VALUES(9,19)&lt;br /&gt;INSERT #t VALUES(10,20)&lt;br /&gt;GO&lt;br /&gt;&lt;br /&gt;SET STATISTICS TIME ON&lt;br /&gt;SET STATISTICS IO ON&lt;br /&gt;GO&lt;br /&gt;&lt;br /&gt;-- approach 1, using a cross join&lt;br /&gt;SELECT n.number AS SetNumber, #t.val FROM NumbersIn2Power n2&lt;br /&gt;JOIN dbo.Numbers n ON n2.Power2 &amp; n.number &gt; 0&lt;br /&gt;JOIN #t ON n2.number = #t.itemNum&lt;br /&gt;ORDER BY n.number, #t.itemNum&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;Table 'Numbers'. Scan count 1, logical reads 40, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;Table 'NumbersIn2Power'. Scan count 0, logical reads 20, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;Table '#t__________________________________________________________________________________________________________________000000000031'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;&lt;br /&gt;SQL Server Execution Times:&lt;br /&gt;   CPU time = 0 ms,  elapsed time = 71 ms.&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;-- approach two, using recursion&lt;br /&gt;&lt;br /&gt;WITH Subsets AS (&lt;br /&gt;  SELECT itemNum, CAST(',' + CAST(itemNum AS VARCHAR(10)) + ',' AS&lt;br /&gt;CHAR(50)) AS LinesList, CAST(1 AS INT) AS Iteration, val&lt;br /&gt;  FROM #t&lt;br /&gt;UNION ALL&lt;br /&gt;  SELECT t.itemNum, CAST(CAST(RTRIM(s.LinesList) AS VARCHAR(200)) +&lt;br /&gt;CAST(t.itemNum AS VARCHAR(10)) + ',' AS CHAR(50)) AS LinesList,&lt;br /&gt;s.Iteration + 1 AS Iteration, s.val + t.val AS val&lt;br /&gt;  FROM Subsets s JOIN #t t ON s.itemNum &gt;= t.itemNum&lt;br /&gt;  WHERE s.Iteration &lt;&gt; t.itemNum&lt;br /&gt;)&lt;br /&gt;SELECT * FROM Subsets ORDER BY LinesList&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;Table 'Worktable'. Scan count 2, logical reads 6121, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;Table '#t__________________________________________________________________________________________________________________000000000031'. Scan count 2, logical reads 1023, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;&lt;br /&gt;SQL Server Execution Times:&lt;br /&gt;   CPU time = 32 ms,  elapsed time = 87 ms.&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;As you have seen, elapsed time is similar, but the first approach uses less CPU. Note that I ran each script more than once, so that parse/compile time is not included in the costs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-2233374531282790382?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/2233374531282790382/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=2233374531282790382' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/2233374531282790382'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/2233374531282790382'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/06/getting-all-subsets-of-set.html' title='Getting All Subsets of a Set'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-5029083844870835870</id><published>2007-06-19T15:28:00.000-07:00</published><updated>2007-06-19T15:32:54.228-07:00</updated><title type='text'>Calculating Pi Using Only Pure SQL</title><content type='html'>Suppose table Numbers has numbers from 1 to 1000.&lt;br /&gt;The problem: using pure SQL find the closest approximation to Pi number as a ratio of two integers. The integers should not exceed 999.&lt;br /&gt;The solution is:&lt;br /&gt;&lt;br /&gt;&lt;p class="MsoNormal" style="margin-left: 0.25in; font-family: arial;"&gt;&lt;span style=";font-size:10;color:blue;"  &gt;SELECT&lt;/span&gt;&lt;span style=";font-size:10;" &gt; &lt;span style="color:blue;"&gt;TOP&lt;/span&gt; 100 m&lt;span style="color:gray;"&gt;,&lt;/span&gt; n&lt;span style="color:gray;"&gt;,&lt;/span&gt; c &lt;span style="color:blue;"&gt;FROM&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.25in; font-family: arial;"&gt;&lt;span style=";font-size:10;color:blue;"  &gt;SELECT&lt;/span&gt;&lt;span style=";font-size:10;" &gt; n1&lt;span style="color:gray;"&gt;.&lt;/span&gt;Number m&lt;span style="color:gray;"&gt;,&lt;/span&gt; n2&lt;span style="color:gray;"&gt;.&lt;/span&gt;Number n&lt;span style="color:gray;"&gt;,&lt;/span&gt; &lt;span style="color:fuchsia;"&gt;ABS&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:fuchsia;"&gt;SIN&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;1.0&lt;span style="color:gray;"&gt;*&lt;/span&gt;n1&lt;span style="color:gray;"&gt;.&lt;/span&gt;Number&lt;span style="color:gray;"&gt;/&lt;/span&gt;n2&lt;span style="color:gray;"&gt;.&lt;/span&gt;Number&lt;span style="color:gray;"&gt;))&lt;/span&gt; c&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.25in; font-family: arial;"&gt;&lt;span style=";font-size:10;color:blue;"  &gt;FROM&lt;/span&gt;&lt;span style=";font-size:10;" &gt; Numbers n1&lt;span style="color:gray;"&gt;,&lt;/span&gt; Numbers n2&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.25in; font-family: arial;"&gt;&lt;span style=";font-size:10;color:blue;"  &gt;WHERE&lt;/span&gt;&lt;span style=";font-size:10;" &gt; n1&lt;span style="color:gray;"&gt;.&lt;/span&gt;Number &lt;span style="color:gray;"&gt;BETWEEN&lt;/span&gt; 100 &lt;span style="color:gray;"&gt;AND&lt;/span&gt; 999&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.25in; font-family: arial;"&gt;&lt;span style=";font-size:10;color:gray;"  &gt;AND&lt;/span&gt;&lt;span style=";font-size:10;" &gt; n2&lt;span style="color:gray;"&gt;.&lt;/span&gt;Number &lt;span style="color:gray;"&gt;BETWEEN&lt;/span&gt; 100 &lt;span style="color:gray;"&gt;AND&lt;/span&gt; 999&lt;span style="color:gray;"&gt;)&lt;/span&gt; t&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.25in;"&gt;&lt;span style="font-family: arial;font-family:&amp;quot;;font-size:10;color:blue;"   &gt;ORDER&lt;/span&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;span style="font-family: arial;"&gt; &lt;/span&gt;&lt;span style="font-family: arial;color:blue;" &gt;BY&lt;/span&gt;&lt;span style="font-family: arial;"&gt; c&lt;/span&gt; &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;br /&gt;&lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;&lt;/span&gt;The answer is &lt;span style=";font-family:&amp;quot;;font-size:10;"  &gt;355/113.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-5029083844870835870?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/5029083844870835870/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=5029083844870835870' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5029083844870835870'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/5029083844870835870'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2007/06/calculating-pi-using-only-pure-sql.html' title='Calculating Pi Using Only Pure SQL'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116570386816890073</id><published>2006-12-09T14:30:00.000-08:00</published><updated>2007-02-09T13:08:43.060-08:00</updated><title type='text'>Maximum selectivity for bookmark lookups depends on row size.</title><content type='html'>Consider a range query on a column that has a non clusterd index. For narrow ranges the optimizer chooses to access the table via bookmark lookups. For wider ranges it just scans the clustered index, as it is cheaper than bookmark lookups. There is no hardcoded threshold - it depeneds on row size and bookmark size. A simple example - in all three cases the optimizer chooses bookmark lookups, but if you increase the range just a little bit, it scans the whole clustered index.&lt;br /&gt;&lt;br /&gt;-- Assuming Numbers table with 10000 rows &lt;br /&gt;&lt;br /&gt;CREATE TABLE WideRows(i INT NOT NULL PRIMARY KEY, j INT NOT NULL, c &lt;br /&gt;CHAR(1000)) &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;INSERT WideRows SELECT Numbers.Number, Numbers.Number + 1, 'asdf' FROM &lt;br /&gt;dbo.Numbers &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;CREATE INDEX WideRows_j ON WideRows(j) &lt;br /&gt;&lt;br /&gt;---- break even point close to 4%&lt;br /&gt;SELECT i, j, c FROM WideRows WHERE j BETWEEN 1000 AND 1400 &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;DROP TABLE WideRows &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;CREATE TABLE HugeRows(i INT NOT NULL PRIMARY KEY, j INT NOT NULL, c &lt;br /&gt;CHAR(3500)) &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;INSERT HugeRows SELECT Numbers.Number, Numbers.Number + 1, 'asdf' FROM &lt;br /&gt;dbo.Numbers &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;CREATE INDEX HugeRows_j ON HugeRows(j) &lt;br /&gt;&lt;br /&gt;---- break even point almost 20% &lt;br /&gt;SELECT i, j, c FROM HugeRows WHERE j BETWEEN 1000 AND 2900 &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;DROP TABLE HugeRows &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;CREATE TABLE NarrowRows(i INT NOT NULL PRIMARY KEY, j INT NOT NULL, c &lt;br /&gt;CHAR(10)) &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;INSERT NarrowRows SELECT Numbers.Number, Numbers.Number + 1, 'asdf' &lt;br /&gt;FROM dbo.Numbers &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;CREATE INDEX NarrowRows_j ON NarrowRows(j) &lt;br /&gt;&lt;br /&gt;---- break even point less than 1% &lt;br /&gt;SELECT i, j, c FROM NarrowRows WHERE j BETWEEN 1000 AND 1040 &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;DROP TABLE NarrowRows &lt;br /&gt;go&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116570386816890073?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116570386816890073/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116570386816890073' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116570386816890073'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116570386816890073'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/12/maximum-selectivity-for-bookmark.html' title='Maximum selectivity for bookmark lookups depends on row size.'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116570320088541589</id><published>2006-12-09T14:23:00.000-08:00</published><updated>2006-12-09T14:26:40.896-08:00</updated><title type='text'>The price of BEGIN TRANSACTION</title><content type='html'>A question came up in a newsgroup:&lt;br /&gt;Is there any performance penalty for explicitly wrapping a single DML statement in BEGIN TRANSACTION / COMMIT brackets? &lt;br /&gt;&lt;br /&gt;I did a quick benchmark: &lt;br /&gt;&lt;br /&gt;CREATE TABLE aa(i INT) &lt;br /&gt;go &lt;br /&gt;CREATE PROCEDURE Test1 &lt;br /&gt;AS &lt;br /&gt;BEGIN &lt;br /&gt;SET NOCOUNT ON &lt;br /&gt;DECLARE @i INT &lt;br /&gt;SET @i = 0 &lt;br /&gt;WHILE(@i &lt; 10000) BEGIN &lt;br /&gt;  INSERT aa(i) VALUES(@i) &lt;br /&gt;  SET @i = @i + 1 &lt;br /&gt;END &lt;br /&gt;END &lt;br /&gt;go &lt;br /&gt;CREATE PROCEDURE Test2 &lt;br /&gt;AS &lt;br /&gt;BEGIN &lt;br /&gt;SET NOCOUNT ON &lt;br /&gt;DECLARE @i INT &lt;br /&gt;SET @i = 0 &lt;br /&gt;WHILE(@i &lt; 10000) BEGIN &lt;br /&gt;  BEGIN TRANSACTION &lt;br /&gt;  INSERT aa(i) VALUES(@i) &lt;br /&gt;  SET @i = @i + 1 &lt;br /&gt;  IF @@ERROR&lt;&gt;0 BEGIN &lt;br /&gt;    ROLLBACK &lt;br /&gt;  END ELSE BEGIN &lt;br /&gt;    COMMIT &lt;br /&gt;  END &lt;br /&gt;END &lt;br /&gt;END &lt;br /&gt;go &lt;br /&gt;Test1 &lt;br /&gt;go &lt;br /&gt;Test1 &lt;br /&gt;/* &lt;br /&gt;Profiler results: &lt;br /&gt;CPU:406 &lt;br /&gt;Reads: 10720 &lt;br /&gt;Writes: 104 &lt;br /&gt;Duration: 2406 &lt;br /&gt;*/ &lt;br /&gt;go &lt;br /&gt;Test2 &lt;br /&gt;go &lt;br /&gt;Test2 &lt;br /&gt;/* &lt;br /&gt;Profiler results: &lt;br /&gt;CPU:373 &lt;br /&gt;Reads: 10844 &lt;br /&gt;Writes: 116 &lt;br /&gt;Duration: 2406 &lt;br /&gt;*/ &lt;br /&gt;go &lt;br /&gt;DROP PROCEDURE Test1 &lt;br /&gt;go &lt;br /&gt;DROP PROCEDURE Test2 &lt;br /&gt;go &lt;br /&gt;DROP TABLE aa &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;I ran the tests 3 times and I did not notice any significant differences in &lt;br /&gt;neither of 4 counters. I would not worry about performance penalties of BEGIN TRAN in my environment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116570320088541589?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116570320088541589/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116570320088541589' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116570320088541589'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116570320088541589'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/12/price-of-begin-transaction.html' title='The price of BEGIN TRANSACTION'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116528457441503586</id><published>2006-12-04T18:00:00.000-08:00</published><updated>2006-12-04T18:16:38.800-08:00</updated><title type='text'>Bypassing A Trigger</title><content type='html'>Several ways to bypass a trigger can be found in an excellent book "Inside MQ SQL Server 2005: T_SQl Querying". Here is yet another one. &lt;br /&gt;&lt;br /&gt;Suppose you have a trigger that records all the changes against a table, as follows:&lt;br /&gt;&lt;br /&gt;CREATE TABLE Party(PartyID INT NOT NULL PRIMARY KEY, LastName VARCHAR(20),&lt;br /&gt;FirstName VARCHAR(20), Salutation VARCHAR(20))&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;CREATE TABLE PartyHistory(PartyID INT NOT NULL, LastName VARCHAR(20), FirstName&lt;br /&gt;VARCHAR(20), Salutation VARCHAR(20), &lt;br /&gt;  ModifiedBy VARCHAR(100), ModifiedAt DATETIME)&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;CREATE TRIGGER PartyUpd ON Party&lt;br /&gt;FOR UPDATE&lt;br /&gt;AS&lt;br /&gt;PRINT 'Entering Trigger PartyUpd'&lt;br /&gt;INSERT INTO [dbo].[PartyHistory]([PartyID], [LastName], [FirstName],&lt;br /&gt;[Salutation], [ModifiedBy], [ModifiedAt])&lt;br /&gt;  SELECT [PartyID], [LastName], [FirstName], [Salutation], SYSTEM_USER,&lt;br /&gt;CURRENT_TIMESTAMP&lt;br /&gt;  FROM Deleted&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;INSERT dbo.[Party]([PartyID], [LastName], [FirstName], [Salutation])&lt;br /&gt;  VALUES(1, 'Doe', 'John', 'Junior')&lt;br /&gt;INSERT dbo.[Party]([PartyID], [LastName], [FirstName], [Salutation])&lt;br /&gt;  VALUES(2, 'Doe', 'John', 'Junior')&lt;br /&gt;go&lt;br /&gt;UPDATE dbo.[Party] SET [LastName] = 'Cooper', [FirstName] = 'Stanley' &lt;br /&gt;  WHERE partyID = 1&lt;br /&gt;go&lt;br /&gt;SELECT * FROM PartyHistory&lt;br /&gt;/*&lt;br /&gt;PartyID     LastName             FirstName            Salutation          &lt;br /&gt;ModifiedBy                                                                                          &lt;br /&gt;ModifiedAt                                             &lt;br /&gt;----------- -------------------- -------------------- --------------------&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;------------------------------------------------------ &lt;br /&gt;1           Doe                  John                 Junior              &lt;br /&gt;US\akuznetsov                                                                                       &lt;br /&gt;2006-12-04 16:42:12.480&lt;br /&gt;&lt;br /&gt;(1 row(s) affected)&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;Suppose you want to modify Salutation column, as follows&lt;br /&gt;&lt;br /&gt;UPDATE Party SET Salutation = 'Jr.' WHERE Salutation = 'Junior'&lt;br /&gt;&lt;br /&gt;And you do not want the trigger to fire. Make sure that RECURSIVE_TRIGGERS is disabled (which is the default):&lt;br /&gt;&lt;br /&gt;SELECT DATABASEPROPERTYEX('SANDBOX','IsRecursiveTriggersEnabled')&lt;br /&gt;----&lt;br /&gt;0&lt;br /&gt;&lt;br /&gt;Retrieve your spid:&lt;br /&gt;&lt;br /&gt;SELECT @@spid&lt;br /&gt;/*&lt;br /&gt;------ &lt;br /&gt;58&lt;br /&gt;&lt;br /&gt;(1 row(s) affected)&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;And wrap your update in a trigger:&lt;br /&gt;&lt;br /&gt;CREATE TRIGGER Bypass_PartyUpd ON Party&lt;br /&gt;FOR UPDATE&lt;br /&gt;AS&lt;br /&gt;PRINT 'Entering Trigger Bypass_PartyUpd'&lt;br /&gt;PRINT @@SPID&lt;br /&gt;IF @@spid = 58 &lt;br /&gt;  UPDATE Party SET Salutation = 'Jr.' WHERE Salutation = 'Junior'&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Now all you need to do is run an update which does not modify any rows&lt;br /&gt;&lt;br /&gt;UPDATE Party Set LastName ='asfdsdf' WHERE PartyID&lt;0&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;The new trigger will fire and update Salutation. Because RESURSIVE_TRIGGERS is turned off, the old trigger will not fire:&lt;br /&gt;&lt;br /&gt;SELECT * FROM Party&lt;br /&gt;/*&lt;br /&gt;PartyID     LastName             FirstName            Salutation           &lt;br /&gt;----------- -------------------- -------------------- -------------------- &lt;br /&gt;1           Cooper               Stanley              Jr.&lt;br /&gt;2           Doe                  John                 Jr.&lt;br /&gt;*/&lt;br /&gt;go&lt;br /&gt;SELECT * FROM PartyHistory&lt;br /&gt;/*&lt;br /&gt;--- still only one row in PartyHistory&lt;br /&gt;PartyID     LastName             FirstName            Salutation          &lt;br /&gt;ModifiedBy                                                                                          &lt;br /&gt;ModifiedAt                                             &lt;br /&gt;----------- -------------------- -------------------- --------------------&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;------------------------------------------------------ &lt;br /&gt;1           Doe                  John                 Junior              &lt;br /&gt;US\akuznetsov                                                                                       &lt;br /&gt;2006-12-04 16:51:49.257&lt;br /&gt;*/&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;If you try an update which clearly will not update any rows no matter what:&lt;br /&gt;&lt;br /&gt;UPDATE Party Set LastName ='asfdsdf' WHERE (1=0)&lt;br /&gt;&lt;br /&gt;The optimizer will be smart enough to not execute the update, so the trigger will not run at all.&lt;br /&gt;&lt;br /&gt;The advantage of this approach is that you do not need to touch the original trigger at all. The disadvantage is that you must not forget to drop the new trigger.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116528457441503586?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116528457441503586/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116528457441503586' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116528457441503586'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116528457441503586'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/12/bypassing-trigger.html' title='Bypassing A Trigger'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116259472168623975</id><published>2006-11-03T14:56:00.000-08:00</published><updated>2006-11-03T14:58:41.696-08:00</updated><title type='text'>Running Concurrency Tests</title><content type='html'>Starting concurrency tests simultaneously could be tricky, especially they need to start at the same time from different workstations.&lt;br /&gt;&lt;br /&gt;One way to do it:&lt;br /&gt;&lt;br /&gt;CREATE TABLE WhistleSound(i INT)&lt;br /&gt;go&lt;br /&gt;CREATE PROCEDURE WaitForWhistle&lt;br /&gt;AS&lt;br /&gt;DECLARE @cnt INT&lt;br /&gt;SET @cnt = 0&lt;br /&gt;WHILE (@cnt = 0) BEGIN&lt;br /&gt;  SELECT @cnt = COUNT(*) FROM WhistleSound WITH(NOLOCK)&lt;br /&gt;  IF @cnt = 0&lt;br /&gt;    WAITFOR DELAY '00:00:01'&lt;br /&gt;END&lt;br /&gt;go&lt;br /&gt;CREATE PROCEDURE BlowWhistle&lt;br /&gt;AS&lt;br /&gt;INSERT WhistleSound(i) VALUES(1)&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;From one connection run&lt;br /&gt;&lt;br /&gt;DELETE FROM WhistleSound&lt;br /&gt;SELECT Getdate()&lt;br /&gt;EXEC WaitForWhistle&lt;br /&gt;SELECT Getdate()&lt;br /&gt;&lt;br /&gt;From another connection run&lt;br /&gt;&lt;br /&gt;SELECT Getdate()&lt;br /&gt;EXEC WaitForWhistle&lt;br /&gt;SELECT Getdate()&lt;br /&gt;&lt;br /&gt;From third connection run &lt;br /&gt;&lt;br /&gt;EXEC dbo.BlowWhistle&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116259472168623975?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116259472168623975/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116259472168623975' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116259472168623975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116259472168623975'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/11/running-concurrency-tests.html' title='Running Concurrency Tests'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116256648245624493</id><published>2006-11-03T07:06:00.000-08:00</published><updated>2006-11-03T07:08:02.463-08:00</updated><title type='text'>When Snapshot Isolation Helps and When It Hurts</title><content type='html'>My next article on devx.com:&lt;br /&gt;&lt;br /&gt;You know SQL Server 2005's new snapshot isolation feature can help you reduce lock contention and deadlocks, but did you know that it also can help you fix intermittent errors in reports and detect lost updates? You know that snapshot isolation requires some effort from your DBA, but did you know it may introduce some breaking changes into applications? Its benefits are well known, but SQL Server developers need to understand the downstream ramifications of snapshot isolation. This article discusses a couple of common real-world scenarios that illustrate the often-unanticipated consequences of using this feature. &lt;br /&gt;&lt;br /&gt;http://www.devx.com/dbzone/Article/32957&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116256648245624493?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116256648245624493/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116256648245624493' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116256648245624493'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116256648245624493'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/11/when-snapshot-isolation-helps-and-when.html' title='When Snapshot Isolation Helps and When It Hurts'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116200081815768633</id><published>2006-10-27T18:55:00.000-07:00</published><updated>2006-10-27T19:00:18.166-07:00</updated><title type='text'>Child Intervals Of Time Inside Parent Ones - Enforced By RI</title><content type='html'>I needed to enforce a business rule that appointments fit into shifts.&lt;br /&gt;The only one watertight way of doing it that i know about is to use RI, as follows:&lt;br /&gt;&lt;br /&gt;CREATE Table  Shifts(ShiftID INT NOT NULL PRIMARY KEY,  &lt;br /&gt;StartedAt DATETIME NOT NULL, &lt;br /&gt;FinishedAt DATETIME NOT NULL,&lt;br /&gt;CONSTRAINT UK_Shifts UNIQUE(ShiftID, StartedAt, FinishedAt)) &lt;br /&gt;go &lt;br /&gt;SET NOCOUNT ON&lt;br /&gt;INSERT Shifts VALUES(1, '20061025 08:00AM', '20061025 05:00PM')&lt;br /&gt;INSERT Shifts VALUES(2, '20061026 08:30AM', '20061026 05:00PM')&lt;br /&gt;INSERT Shifts VALUES(3, '20061027 08:30AM', '20061027 05:00PM')&lt;br /&gt;go &lt;br /&gt;CREATE Table  Appointments ( ShiftID INT NOT NULL, &lt;br /&gt;StartedAt DATETIME NOT NULL, &lt;br /&gt;FinishedAt DATETIME NOT NULL,&lt;br /&gt;ShiftStartedAt DATETIME NOT NULL, &lt;br /&gt;ShiftFinishedAt DATETIME NOT NULL,&lt;br /&gt;CONSTRAINT FK_Appointments FOREIGN KEY(ShiftID, ShiftStartedAt, ShiftFinishedAt) &lt;br /&gt; REFERENCES Shifts(ShiftID, StartedAt, FinishedAt) ON UPDATE CASCADE, &lt;br /&gt;CONSTRAINT CHK_TimesInRange CHECK((StartedAt BETWEEN ShiftStartedAt AND&lt;br /&gt;ShiftFinishedAt) AND (FinishedAt BETWEEN ShiftStartedAt AND ShiftFinishedAt))) &lt;br /&gt;go &lt;br /&gt;INSERT Appointments VALUES(1, '20061025 09:00AM', '20061025 05:00PM', '20061025&lt;br /&gt;08:00AM', '20061025 05:00PM')&lt;br /&gt;INSERT Appointments VALUES(2, '20061026 08:30AM', '20061026 01:00PM', '20061026&lt;br /&gt;08:30AM', '20061026 05:00PM')&lt;br /&gt;INSERT Appointments VALUES(3, '20061027 10:30AM', '20061027 01:00PM', '20061027&lt;br /&gt;08:30AM', '20061027 05:00PM')&lt;br /&gt;---- these updates succeed: appointments still fit into their shifts&lt;br /&gt;UPDATE Shifts SET StartedAt  =  '20061027 09:31AM' WHERE ShiftID = 3&lt;br /&gt;UPDATE Shifts SET FinishedAt =  '20061027 04:31PM' WHERE ShiftID = 2&lt;br /&gt;go &lt;br /&gt;-- all fail: range not completely inside the parent range&lt;br /&gt;INSERT Appointments VALUES(3, '20061027 7:30AM', '20061027 01:00PM', '20061027&lt;br /&gt;08:30AM', '20061027 05:00PM')&lt;br /&gt;INSERT Appointments VALUES(3, '20061027 10:30AM', '20061027 08:00PM', '20061027&lt;br /&gt;08:30AM', '20061027 05:00PM')&lt;br /&gt;-- fails because there is an appoinment starting at 10:30&lt;br /&gt;UPDATE Shifts SET StartedAt =  '20061027 10:31AM' WHERE ShiftID = 3&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116200081815768633?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116200081815768633/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116200081815768633' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116200081815768633'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116200081815768633'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/10/child-intervals-of-time-inside-parent.html' title='Child Intervals Of Time Inside Parent Ones - Enforced By RI'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116164502617672847</id><published>2006-10-23T16:09:00.000-07:00</published><updated>2006-10-23T16:10:26.183-07:00</updated><title type='text'>Think ANSI Standard SQL Is Fully Portable Between Databases? Think Again.</title><content type='html'>My next article went live on devx.com:&lt;br /&gt;&lt;br /&gt;Think ANSI Standard SQL Is Fully Portable Between Databases? Think Again.&lt;br /&gt;&lt;br /&gt;The same ANSI Standard SQL query can run against identical data on SQL Server and Oracle and return different results. So just because your application issues ANSI standard SQL, don't assume it's portable. &lt;br /&gt;&lt;br /&gt;http://www.devx.com/dbzone/Article/32852&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116164502617672847?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116164502617672847/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116164502617672847' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116164502617672847'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116164502617672847'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/10/think-ansi-standard-sql-is-fully.html' title='Think ANSI Standard SQL Is Fully Portable Between Databases? Think Again.'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116104575760120959</id><published>2006-10-16T17:37:00.000-07:00</published><updated>2006-10-16T17:42:37.603-07:00</updated><title type='text'>Implementing One-To-One Relationship.</title><content type='html'>Suppose you have two tables, as follows:&lt;br /&gt;&lt;br /&gt;CREATE TABLE LeftHalf(PairID INT NOT NULL PRIMARY KEY,&lt;br /&gt;SomeData VARCHAR(20))&lt;br /&gt;go&lt;br /&gt;CREATE TABLE RightHalf(PairID INT NOT NULL PRIMARY KEY,&lt;br /&gt;SomeData VARCHAR(20))&lt;br /&gt;go&lt;br /&gt;INSERT LeftHalf(PairID, SomeData) VALUES(1, 'Left Half 1')&lt;br /&gt;INSERT RightHalf(PairID, SomeData) VALUES(1, 'Right Half 1')&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Assume you want to enforse one-to-one relationship between them:&lt;br /&gt;&lt;br /&gt;ALTER TABLE LeftHalf ADD CONSTRAINT LeftHalfHasMatchingRight FOREIGN KEY(PairID) REFERENCES RightHalf&lt;br /&gt;go&lt;br /&gt;ALTER TABLE RightHalf ADD CONSTRAINT RightHalfHasMatchingLeft FOREIGN KEY(PairID) REFERENCES LeftHalf&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Unfortunately, this straightforward approach does not allow you to add new rows:&lt;br /&gt;&lt;br /&gt;INSERT LeftHalf(PairID, SomeData) VALUES(2, 'Some Data')&lt;br /&gt;/*&lt;br /&gt;Msg 547, Level 16, State 0, Line 1&lt;br /&gt;The INSERT statement conflicted with the FOREIGN KEY constraint "LeftHalfHasMatchingRight". The conflict occurred in database "Sandbox", table "dbo.RightHalf", column 'PairID'.&lt;br /&gt;The statement has been terminated.&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;Let us implement deferrable constraints instead:&lt;br /&gt;&lt;br /&gt;ALTER TABLE LeftHalf DROP CONSTRAINT LeftHalfHasMatchingRight&lt;br /&gt;go&lt;br /&gt;ALTER TABLE RightHalf DROP CONSTRAINT RightHalfHasMatchingLeft&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;ALTER TABLE LeftHalf ADD InTransientState CHAR(1) CHECK(InTransientState IN('Y', 'N'))&lt;br /&gt;go&lt;br /&gt;ALTER TABLE RightHalf ADD InTransientState CHAR(1) CHECK(InTransientState IN('Y', 'N'))&lt;br /&gt;go&lt;br /&gt;ALTER TABLE LeftHalf ADD MatchingRight AS CASE WHEN InTransientState = 'N' THEN PairID END PERSISTED&lt;br /&gt;go&lt;br /&gt;ALTER TABLE RightHalf ADD MatchingLeft AS CASE WHEN InTransientState = 'N' THEN PairID END PERSISTED&lt;br /&gt;go&lt;br /&gt;ALTER TABLE LeftHalf ADD CONSTRAINT LeftHalfHasMatchingRight FOREIGN KEY(MatchingRight) REFERENCES RightHalf&lt;br /&gt;go&lt;br /&gt;ALTER TABLE RightHalf ADD CONSTRAINT RightHalfHasMatchingLeft FOREIGN KEY(MatchingLeft) REFERENCES LeftHalf&lt;br /&gt;go&lt;br /&gt;UPDATE LeftHalf SET InTransientState = 'N'&lt;br /&gt;UPDATE RightHalf SET InTransientState = 'N'&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Now you can add rows all right as long as you defer RI checking until both rows in a pair are added, as follows:&lt;br /&gt;&lt;br /&gt;BEGIN TRAN&lt;br /&gt;INSERT LeftHalf(PairID, SomeData, InTransientState) VALUES(2, 'Left Half 2', 'Y')&lt;br /&gt;INSERT RightHalf(PairID, SomeData, InTransientState) VALUES(2, 'Right Half 2', 'Y')&lt;br /&gt;UPDATE LeftHalf SET InTransientState = 'N' WHERE PairID = 2&lt;br /&gt;UPDATE RightHalf SET InTransientState = 'N' WHERE PairID = 2&lt;br /&gt;COMMIT&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Note that straightforward insert will not work, as before:&lt;br /&gt;&lt;br /&gt;INSERT LeftHalf(PairID, SomeData, InTransientState) VALUES(3, 'Left Half 3', 'N')&lt;br /&gt;INSERT RightHalf(PairID, SomeData, InTransientState) VALUES(3, 'Right Half 3', 'N')&lt;br /&gt;&lt;br /&gt;Msg 547, Level 16, State 0, Line 1&lt;br /&gt;The INSERT statement conflicted with the FOREIGN KEY constraint "LeftHalfHasMatchingRight". The conflict occurred in database "Sandbox", table "dbo.RightHalf", column 'PairID'.&lt;br /&gt;The statement has been terminated.&lt;br /&gt;Msg 547, Level 16, State 0, Line 2&lt;br /&gt;The INSERT statement conflicted with the FOREIGN KEY constraint "RightHalfHasMatchingLeft". The conflict occurred in database "Sandbox", table "dbo.LeftHalf", column 'PairID'.&lt;br /&gt;The statement has been terminated.&lt;br /&gt;&lt;br /&gt;But the data not in transient state is guaranteed to be consistent:&lt;br /&gt;&lt;br /&gt;SELECT * FROM LeftHalf WHERE InTransientState = 'N'&lt;br /&gt;SELECT * FROM RightHalf WHERE InTransientState = 'N'&lt;br /&gt;/*&lt;br /&gt;PairID      SomeData             InTransientState MatchingRight&lt;br /&gt;----------- -------------------- ---------------- -------------&lt;br /&gt;1           Left Half 1          N                1&lt;br /&gt;2           Left Half 2          N                2&lt;br /&gt;&lt;br /&gt;(2 row(s) affected)&lt;br /&gt;&lt;br /&gt;PairID      SomeData             InTransientState MatchingLeft&lt;br /&gt;----------- -------------------- ---------------- ------------&lt;br /&gt;1           Right Half 1         N                1&lt;br /&gt;2           Right Half 2         N                2&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116104575760120959?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116104575760120959/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116104575760120959' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116104575760120959'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116104575760120959'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/10/implementing-one-to-one-relationship.html' title='Implementing One-To-One Relationship.'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-116104545573700429</id><published>2006-10-16T17:34:00.000-07:00</published><updated>2006-10-16T17:37:35.753-07:00</updated><title type='text'>Mimicking Deferrable Constraints With Persisted Computed Columns.</title><content type='html'>In Oracle you can utilize deferrable constraints (when RI checks are delayed until commit time) - this is very convenient in some cases.&lt;br /&gt;In SQL Server deferrable constraints are not supported, but you can mimick this behaviour using persisted computed columns.&lt;br /&gt;For instance, suppose two columns in your child table reference one and the same primary key as follows:&lt;br /&gt;&lt;br /&gt;CREATE TABLE Employee(EmployeeName VARCHAR(30) NOT NULL PRIMARY KEY, Extension CHAR(4))&lt;br /&gt;go&lt;br /&gt;CREATE TABLE Task(CompletedBy VARCHAR(30) NOT NULL, ReviewedBy VARCHAR(30) NULL, Description VARCHAR(30))&lt;br /&gt;go&lt;br /&gt;ALTER TABLE Task ADD CONSTRAINT TaskCompletedFK FOREIGN KEY(CompletedBy) REFERENCES Employee(EmployeeName)&lt;br /&gt;go&lt;br /&gt;ALTER TABLE Task ADD CONSTRAINT TaskReviewedByFK FOREIGN KEY(ReviewedBy) REFERENCES Employee(EmployeeName)&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Also suppose that your primary key is not immutable and you need to cascade the changes in the parent's primary key down to the child table&lt;br /&gt;&lt;br /&gt;ALTER TABLE Task DROP CONSTRAINT TaskCompletedFK&lt;br /&gt;go&lt;br /&gt;ALTER TABLE Task DROP CONSTRAINT TaskReviewedByFK&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;You can create one foreign key with cascading updates all right:&lt;br /&gt;&lt;br /&gt;ALTER TABLE Task ADD CONSTRAINT TaskCompletedFK FOREIGN KEY(CompletedBy) REFERENCES Employee(EmployeeID)&lt;br /&gt;  ON UPDATE CASCADE &lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;But the second constraint cannot be not created with ON UPDATE CASCADE ON DELETE CASCADE option:&lt;br /&gt;&lt;br /&gt;ALTER TABLE Task ADD CONSTRAINT TaskReviewedByFK FOREIGN KEY(ReviewedBy) REFERENCES Employee(EmployeeID)&lt;br /&gt;  ON UPDATE CASCADE &lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Msg 1785, Level 16, State 0, Line 2&lt;br /&gt;Introducing FOREIGN KEY constraint 'TaskReviewedByFK' on table 'Task' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.&lt;br /&gt;Msg 1750, Level 16, State 0, Line 2&lt;br /&gt;Could not create constraint. See previous errors.&lt;br /&gt;&lt;br /&gt;You have no choice but implement cascading updates manually.&lt;br /&gt;Note that dealing with mutating primary keys in triggers is very inconvenient to put it mildly. However, you can add a couple of columns to your table and easily cascade the changes without troubling yourself too terribly much. Add two columns and a constraint as follows:&lt;br /&gt;&lt;br /&gt;ALTER TABLE Task ADD InTransientState CHAR(1) CHECK(InTransientState IN('Y', 'N'))&lt;br /&gt;go&lt;br /&gt;ALTER TABLE Task ADD ReviewedByNotTransient AS CASE WHEN InTransientState = 'N' THEN ReviewedBy END PERSISTED&lt;br /&gt;go&lt;br /&gt;ALTER TABLE Task ADD CONSTRAINT TaskReviewedByFK FOREIGN KEY(ReviewedByNotTransient) REFERENCES Employee(EmployeeName)&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Let us populate the tables &lt;br /&gt;&lt;br /&gt;INSERT Employee VALUES('Doe, John', '1234')&lt;br /&gt;INSERT Employee VALUES('Doe, Jane', '1235')&lt;br /&gt;INSERT Employee VALUES('Jill, Ruben', '1236')&lt;br /&gt;go&lt;br /&gt;INSERT Task(CompletedBy, ReviewedBy, InTransientState) VALUES('Doe, John', 'Doe, Jane', 'N')&lt;br /&gt;INSERT Task(CompletedBy, ReviewedBy, InTransientState) VALUES('Doe, Jane', 'Jill, Ruben', 'N')&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;Note that a straightforward UPDATE will not work, because it is not cascaded down to ReviewedBy column:&lt;br /&gt;&lt;br /&gt;UPDATE Employee SET EmployeeName = 'Doe, Janette' WHERE EmployeeName = 'Doe, Jane'&lt;br /&gt;/*&lt;br /&gt;Msg 547, Level 16, State 0, Line 1&lt;br /&gt;The UPDATE statement conflicted with the REFERENCE constraint "TaskReviewedByFK". The conflict occurred in database "Sandbox", table "dbo.Task", column 'ReviewedByNotTransient'.&lt;br /&gt;The statement has been terminated.&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;But a slightly more complex script will work all right (error handling skipped for brevity):&lt;br /&gt;&lt;br /&gt;BEGIN TRAN&lt;br /&gt;--- Turn off TaskReviewedByFK temporarily for Doe, Jane only&lt;br /&gt;UPDATE Task SET InTransientState = 'Y' WHERE ReviewedBy = 'Doe, Jane'&lt;br /&gt;--- update the PK&lt;br /&gt;UPDATE Employee SET EmployeeName = 'Doe, Janette' WHERE EmployeeName = 'Doe, Jane'&lt;br /&gt;--- change the FK accordingly and get the rows back into RI scope&lt;br /&gt;UPDATE Task SET ReviewedBy = 'Doe, Janette', InTransientState = 'N' WHERE ReviewedBy = 'Doe, Jane'&lt;br /&gt;COMMIT&lt;br /&gt;-- verify that it worked. Note that changes to CompletedBy cascaded automatically.&lt;br /&gt;SELECT * FROM Task&lt;br /&gt;&lt;br /&gt;As you have seen, a persisted computed column allowed you to postpone RI verification.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-116104545573700429?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/116104545573700429/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=116104545573700429' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116104545573700429'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/116104545573700429'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/10/mimicking-deferrable-constraints-with.html' title='Mimicking Deferrable Constraints With Persisted Computed Columns.'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-115923861758699377</id><published>2006-09-25T19:42:00.000-07:00</published><updated>2006-09-25T19:43:37.620-07:00</updated><title type='text'>Loading Legacy Data Into parent/Child Tables With Identities</title><content type='html'>It is one of those cases when OUTPUT clause comes very handy.&lt;br /&gt;&lt;br /&gt;CREATE TABLE Staging(EmployeeCode CHAR(30), FullName VARCHAR(30), &lt;br /&gt;PhoneNumber VARCHAR(30)) &lt;br /&gt;INSERT INTO Staging VALUES('ABC1234567890XYZ0987654321', 'Doe, John', &lt;br /&gt;'(123)456-7890 home') &lt;br /&gt;INSERT INTO Staging VALUES('ABC1234567890XYZ0987654321', 'Doe, John', &lt;br /&gt;'(312)456-7890 cell') &lt;br /&gt;INSERT INTO Staging VALUES('ABD123FHSDJKFH367FHASJ4321', 'Doe, Jane', &lt;br /&gt;'(567)456-7891 home') &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;CREATE TABLE Party(PartyID INT IDENTITY PRIMARY KEY, EmployeeCode &lt;br /&gt;CHAR(30), FullName VARCHAR(30)) &lt;br /&gt;CREATE TABLE Phone(PartyID INT NOT NULL, PhoneNumber VARCHAR(30)) &lt;br /&gt;go &lt;br /&gt;&lt;br /&gt;DECLARE @InsertedIds TABLE(PartyID INT, EmployeeCode CHAR(30)) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;INSERT Party(EmployeeCode, FullName) &lt;br /&gt;  OUTPUT INSERTED.PartyID, INSERTED.EmployeeCode INTO @InsertedIds &lt;br /&gt;  SELECT DISTINCT EmployeeCode, FullName &lt;br /&gt;  FROM Staging &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SELECT * FROM Party &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;PartyID     EmployeeCode                   FullName &lt;br /&gt;----------- ------------------------------ &lt;br /&gt;------------------------------ &lt;br /&gt;1           ABC1234567890XYZ0987654321     Doe, John &lt;br /&gt;2           ABD123FHSDJKFH367FHASJ4321     Doe, Jane &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SELECT PartyID, EmployeeCode FROM @InsertedIds &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;PartyID     EmployeeCode &lt;br /&gt;----------- ------------------------------ &lt;br /&gt;1           ABC1234567890XYZ0987654321 &lt;br /&gt;2           ABD123FHSDJKFH367FHASJ4321 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;INSERT Phone(PartyID, PhoneNumber) &lt;br /&gt;  SELECT &lt;br /&gt;    --(SELECT PartyID FROM @InsertedIds i WHERE Staging.EmployeeCode = &lt;br /&gt;i.EmployeeCode) &lt;br /&gt;    PartyID, &lt;br /&gt;    PhoneNumber &lt;br /&gt;  FROM Staging JOIN @InsertedIds i ON Staging.EmployeeCode = &lt;br /&gt;i.EmployeeCode &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SELECT * FROM Phone &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;PartyID     PhoneNumber &lt;br /&gt;----------- ------------------------------ &lt;br /&gt;1           (123)456-7890 home &lt;br /&gt;1           (312)456-7890 cell &lt;br /&gt;2           (567)456-7891 home&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-115923861758699377?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/115923861758699377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=115923861758699377' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115923861758699377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115923861758699377'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/09/loading-legacy-data-into-parentchild.html' title='Loading Legacy Data Into parent/Child Tables With Identities'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-115871902524738701</id><published>2006-09-19T19:17:00.000-07:00</published><updated>2007-01-16T19:38:13.560-08:00</updated><title type='text'>When Eliminataing Cursors Hurts Performance</title><content type='html'>Not every non-cursor so9lution is performant. I think there are at least two different situations:&lt;br /&gt;&lt;br /&gt;1. You open a cursor and use it to modify the underlying table. &lt;br /&gt;2. You open a cursor, do something, but you do not touch the underlying &lt;br /&gt;at all. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I think that we need to distinguish between these two cases. I think &lt;br /&gt;the popular rule of thumb that "cursors should be avoided whatever it &lt;br /&gt;takes" not always applies in case 2. &lt;br /&gt;Have a look at this simple script (Table Numbers stored numbers from 1 &lt;br /&gt;to 10000): &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;DROP PROCEDURE CursorTest &lt;br /&gt;go &lt;br /&gt;CREATE PROCEDURE CursorTest &lt;br /&gt;AS &lt;br /&gt;DECLARE @KeepLooping INT, @n INT, @d1 DATETIME, @d2 DATETIME &lt;br /&gt;DECLARE Test1 CURSOR LOCAL FORWARD_ONLY &lt;br /&gt;  FOR SELECT Number &lt;br /&gt;      FROM dbo.Numbers &lt;br /&gt;      ORDER BY Number &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SET @KeepLooping = 1 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SET @d1 = GETDATE() &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;OPEN Test1 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;WHILE @KeepLooping = 1 BEGIN &lt;br /&gt;  FETCH Test1 INTO @n &lt;br /&gt;  IF @@FETCH_STATUS &lt;&gt; 0 BEGIN &lt;br /&gt;    SET @KeepLooping = 0 &lt;br /&gt;  END &lt;br /&gt;END &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SET @d2 = GETDATE() &lt;br /&gt;SELECT 'CursorTest' testname, DATEDIFF(ms, @d1, @d2) duration &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;CLOSE Test1 &lt;br /&gt;DEALLOCATE Test1 &lt;br /&gt;go &lt;br /&gt;DROP PROCEDURE LoopTest &lt;br /&gt;go &lt;br /&gt;CREATE PROCEDURE LoopTest &lt;br /&gt;AS &lt;br /&gt;DECLARE @n INT, @d1 DATETIME, @d2 DATETIME &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SET @n = -1 &lt;br /&gt;SET @d1 = GETDATE() &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;WHILE @n IS NOT NULL BEGIN &lt;br /&gt;  SELECT @n = MIN(Number) &lt;br /&gt;      FROM dbo.Numbers WHERE Number &gt; @n &lt;br /&gt;END &lt;br /&gt;SET @d2 = GETDATE() &lt;br /&gt;SELECT 'LoopTest' testname, DATEDIFF(ms, @d1, @d2) duration &lt;br /&gt;go &lt;br /&gt;EXEC LoopTest &lt;br /&gt;EXEC CursorTest &lt;br /&gt;EXEC LoopTest &lt;br /&gt;EXEC CursorTest &lt;br /&gt;EXEC LoopTest &lt;br /&gt;EXEC CursorTest &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;testname duration &lt;br /&gt;-------- ----------- &lt;br /&gt;LoopTest 30000 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;(1 row(s) affected) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;testname   duration &lt;br /&gt;---------- ----------- &lt;br /&gt;CursorTest 106 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;(1 row(s) affected) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;testname duration &lt;br /&gt;-------- ----------- &lt;br /&gt;LoopTest 32190 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;(1 row(s) affected) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;testname   duration &lt;br /&gt;---------- ----------- &lt;br /&gt;CursorTest 106 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;(1 row(s) affected) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;testname duration &lt;br /&gt;-------- ----------- &lt;br /&gt;LoopTest 29736 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;(1 row(s) affected) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;testname   duration &lt;br /&gt;---------- ----------- &lt;br /&gt;CursorTest 106 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;(1 row(s) affected)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-115871902524738701?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/115871902524738701/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=115871902524738701' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115871902524738701'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115871902524738701'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/09/when-eliminataing-cursors-hurts.html' title='When Eliminataing Cursors Hurts Performance'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-115871743666362520</id><published>2006-09-19T18:55:00.000-07:00</published><updated>2006-09-19T18:57:16.673-07:00</updated><title type='text'>How To Quickly Verify That A Date Has No Time Part</title><content type='html'>I did a quick benchmarking. I always knew that DATEDIFF approach should &lt;br /&gt;be faster than converting to CHAR, but I had no idea it is that faster (at least 7 times faster): &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;DECLARE @d1 DATETIME, @d2 DATETIME, @i INT, @cnt INT &lt;br /&gt;DECLARE @d TABLE(ddd DATETIME) &lt;br /&gt;SET NOCOUNT ON &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SET @i = 0 &lt;br /&gt;WHILE @i&lt;100000 BEGIN &lt;br /&gt;  INSERT @d VALUES('20060101') &lt;br /&gt;  SET @i = @i + 1 &lt;br /&gt;END &lt;br /&gt;SET @d1 = GETDATE() &lt;br /&gt;SET @i = (SELECT COUNT(*) FROM @d WHERE (convert(char(8), ddd, 112) = &lt;br /&gt;ddd)) &lt;br /&gt;SET @d2 = GETDATE() &lt;br /&gt;SELECT DATEDIFF(ms, @d1, @d2), 'char' &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SET @d1 = GETDATE() &lt;br /&gt;SET @i = (SELECT COUNT(*) FROM @d WHERE &lt;br /&gt;(dateadd(d,datediff(d,'1990-01-01',ddd),'1990-01-01') = ddd)) &lt;br /&gt;SET @d2 = GETDATE() &lt;br /&gt;SELECT DATEDIFF(ms, @d1, @d2), 'datediff' &lt;br /&gt;&lt;br /&gt;----------- ---- &lt;br /&gt;346         char &lt;br /&gt;----------- -------- &lt;br /&gt;46          datediff&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-115871743666362520?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/115871743666362520/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=115871743666362520' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115871743666362520'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115871743666362520'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/09/how-to-quickly-verify-that-date-has-no.html' title='How To Quickly Verify That A Date Has No Time Part'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-115863070114145264</id><published>2006-09-18T18:51:00.000-07:00</published><updated>2006-09-18T18:51:41.143-07:00</updated><title type='text'>Yet Another Index Covering Tip</title><content type='html'>Suppose you have a table:&lt;br /&gt;&lt;br /&gt;create table a(email_alias varchar(20), full_name varchar(20), /*many other columns*/)&lt;br /&gt;&lt;br /&gt;Suppose you need to make sure email_alias are unique. Obviously you will create a unique index on email_alias. Suppose you also need an index on (email_alias, full_name) to cover several frequently run queries. In SQL Server 2005 you can have one and the same index accomplish both goals. Use a new INCLUDE option in CREATE INDEX statement:&lt;br /&gt;&lt;br /&gt;create unique index a1 on a(email_alias) include(full_name)&lt;br /&gt;&lt;br /&gt;Both columns will be stored in the index, which will guarantee index covering, but the uniqueness of email_alias will be also preserved:&lt;br /&gt;&lt;br /&gt;--- succeeds&lt;br /&gt;insert into a values('jsmith', 'Jack Smith')&lt;br /&gt;go&lt;br /&gt;--- fails&lt;br /&gt;insert into a values('jsmith', 'Jared Smith')&lt;br /&gt;&lt;br /&gt;Msg 2601, Level 14, State 1, Line 1&lt;br /&gt;Cannot insert duplicate key row in object 'dbo.a' with unique index 'a1'.&lt;br /&gt;The statement has been terminated.&lt;br /&gt;&lt;br /&gt;It is very important to keep the number of indexes as low as possible, and the new INCLUDE option comes very handy in accomplishing that goal.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-115863070114145264?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/115863070114145264/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=115863070114145264' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115863070114145264'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115863070114145264'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/09/yet-another-index-covering-tip.html' title='Yet Another Index Covering Tip'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-115863065390209475</id><published>2006-09-18T18:50:00.000-07:00</published><updated>2006-12-19T08:50:15.356-08:00</updated><title type='text'>Mimicking MERGE Statement in SQL Server 2005</title><content type='html'>Suppose you have a table&lt;br /&gt;&lt;br /&gt;create table permanent(id int, d float, comment varchar(15))&lt;br /&gt;go&lt;br /&gt;insert into permanent values(1, 10., 'Original Row')&lt;br /&gt;insert into permanent values(2, 10., 'Original Row')&lt;br /&gt;insert into permanent values(3, 10., 'Original Row')&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;And you want to modify it with incoming values from a staging table&lt;br /&gt;&lt;br /&gt;create table staging(id int, d float)&lt;br /&gt;go&lt;br /&gt;insert into staging values(2, 15.)&lt;br /&gt;insert into staging values(3, 15.)&lt;br /&gt;insert into staging values(4, 15.)&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;You want to modify rows 2 and 3 and to add a new row 4. In DB2 and oracle there is a very convenient MERGE statement which does precisely this. In SQL Server 2005 you can use OUTPUT clause of an UPDATE statement:&lt;br /&gt;&lt;br /&gt;go&lt;br /&gt;declare @updated_ids table(id int)&lt;br /&gt;update permanent set d=s.d, comment = 'Modified Row'&lt;br /&gt;output inserted.id into @updated_ids&lt;br /&gt;from permanent p, staging s&lt;br /&gt;where p.id=s.id&lt;br /&gt;&lt;br /&gt;insert into permanent&lt;br /&gt;select id, d, 'New Row' from staging where id not in(select id from @updated_ids)&lt;br /&gt;go&lt;br /&gt;select * from permanent&lt;br /&gt;go&lt;br /&gt;id d comment&lt;br /&gt;----------- ---------------------- ---------------&lt;br /&gt;1 10 Original Row&lt;br /&gt;2 15 Modified Row&lt;br /&gt;3 15 Modified Row&lt;br /&gt;4 15 New Row&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-115863065390209475?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/115863065390209475/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=115863065390209475' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115863065390209475'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115863065390209475'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/09/mimicking-merge-statement-in-sql.html' title='Mimicking MERGE Statement in SQL Server 2005'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-115863057945786348</id><published>2006-09-18T18:48:00.000-07:00</published><updated>2006-09-18T18:49:39.466-07:00</updated><title type='text'>Populating a Column with Random Values</title><content type='html'>Suppose you have a table:&lt;br /&gt;&lt;br /&gt;create table t(i int, d float)&lt;br /&gt;insert into t values(1, 0.0)&lt;br /&gt;insert into t values(2, 0.0)&lt;br /&gt;insert into t values(3, 0.0)&lt;br /&gt;insert into t values(4, 0.0)&lt;br /&gt;&lt;br /&gt;Suppose you want to populate d with random values:&lt;br /&gt;&lt;br /&gt;update t set d = rand()&lt;br /&gt;&lt;br /&gt;Unfortunately it will assign one and the same value to all the rows:&lt;br /&gt;&lt;br /&gt;select * from t&lt;br /&gt;&lt;br /&gt;i d----------- ----------------------&lt;br /&gt;1 0.430774201738828&lt;br /&gt;2 0.430774201738828&lt;br /&gt;3 0.430774201738828&lt;br /&gt;4 0.430774201738828&lt;br /&gt;(4 row(s) affected)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Here is the right way to do it:&lt;br /&gt;&lt;br /&gt;create view wrapped_rand_view&lt;br /&gt;as&lt;br /&gt;select rand( ) as random_value&lt;br /&gt;go&lt;br /&gt;create function wrapped_rand()&lt;br /&gt;returns float&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;declare @f float&lt;br /&gt;set @f = (select random_value from wrapped_rand_view)&lt;br /&gt;return @f&lt;br /&gt;end&lt;br /&gt;go&lt;br /&gt;update t set d = dbo.wrapped_rand()&lt;br /&gt;&lt;br /&gt;select * from t&lt;br /&gt;&lt;br /&gt;i d&lt;br /&gt;----------- ----------------------&lt;br /&gt;1 0.665934939858962&lt;br /&gt;2 0.99919527046644&lt;br /&gt;3 0.844039650356367&lt;br /&gt;4 0.146401486624627&lt;br /&gt;(4 row(s) affected)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-115863057945786348?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/115863057945786348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=115863057945786348' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115863057945786348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115863057945786348'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/09/populating-column-with-random-values.html' title='Populating a Column with Random Values'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-115853791485624717</id><published>2006-09-17T16:58:00.000-07:00</published><updated>2006-09-17T17:05:14.856-07:00</updated><title type='text'>My DB2 UDB articles</title><content type='html'>1. When We Think That the Optimizer Doesn't Get It Right &lt;br /&gt;Some simple tips to make sure the optimizer makes good decisions for your queries. &lt;br /&gt;&lt;br /&gt;http://www.devx.com/ibm/Article/11179/0&lt;br /&gt;&lt;br /&gt;2. Essential Facts about Index Covering in DB2 Universal Database&lt;br /&gt;xamples to illustrate cases in which adding a column or two to an index can boost a query's performance by "covering" the columns used in a SELECT statement. The DB2 optimizer is very good in recognizing such situations and can choose a very efficient execution plan.&lt;br /&gt;&lt;br /&gt;http://www-128.ibm.com/developerworks/db2/library/techarticle/0303kuznetsov/0303kuznetsov.html&lt;br /&gt;&lt;br /&gt;3. Using DB2 UDB OLAP functions&lt;br /&gt;Online analytical processing (OLAP) functions are very flexible and powerful. Using them, you may come up with simple solutions to some problems that would otherwise require either iteration through one or several cursors or recursion. In some other cases it is much easier to write a query using OLAP functions or auxiliary tables than to write an equivalent query not using them.&lt;br /&gt;&lt;br /&gt;http://www-128.ibm.com/developerworks/db2/library/techarticle/dm-0401kuznetsov/index.html&lt;br /&gt;&lt;br /&gt;4.  Using Materialized QueryTables to Speed up Queries in DB2 UDB &lt;br /&gt;Sometimes a simple change in physical database structure dramatically improves query performance. In addition to indexes, DB2 UDB provides you with materialized query tables, which in some cases are more efficient than indexes. This article provides ...&lt;br /&gt;&lt;br /&gt;http://www-128.ibm.com/developerworks/db2/library/techarticle/0208kuznetsov/0208kuznetsov.html&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-115853791485624717?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/115853791485624717/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=115853791485624717' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115853791485624717'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115853791485624717'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/09/my-db2-udb-articles.html' title='My DB2 UDB articles'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-34589901.post-115853748167654962</id><published>2006-09-17T16:53:00.000-07:00</published><updated>2006-09-17T16:58:01.686-07:00</updated><title type='text'>My MS SQL Server Articles</title><content type='html'>My MS SQL Server Articles&lt;br /&gt;&lt;br /&gt;1. Indexes on Computed Columns: Speed Up Queries, Add Business Rules &lt;br /&gt;Using an index on a computed column, you can implement complex business rules or just give your SQL Server queries a performance boost. &lt;br /&gt;&lt;br /&gt;http://www.devx.com/dbzone/Article/30786/0&lt;br /&gt;&lt;br /&gt;2. Avoid These Common Business Rule Implementation Mistakes &lt;br /&gt;Some solutions for implementing business rules can allow invalid data to get into the database. Find out where the loopholes lie in these all-too-common approaches. &lt;br /&gt;&lt;br /&gt;http://www.devx.com/dbzone/Article/31985/0&lt;br /&gt;&lt;br /&gt;3.Index Covering Boosts SQL Server Query Performance &lt;br /&gt;Index covering is a quick and easy solution to many query performance problems. By just adding a column or two to an index you can significantly boost a query's performance. &lt;br /&gt;&lt;br /&gt;http://www.devx.com/dbzone/Article/29530/0&lt;br /&gt;&lt;br /&gt;4. The Less SQL Server Sorts, the Faster It Responds (with Rajiv Rajak)&lt;br /&gt;Learn coding techniques to tweak your SQL queries so that SQL Server sorts less data and returns much faster.&lt;br /&gt;&lt;br /&gt;http://www.devx.com/dbzone/Article/30149/0&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/34589901-115853748167654962?l=sqlserver-tips.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sqlserver-tips.blogspot.com/feeds/115853748167654962/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=34589901&amp;postID=115853748167654962' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115853748167654962'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/34589901/posts/default/115853748167654962'/><link rel='alternate' type='text/html' href='http://sqlserver-tips.blogspot.com/2006/09/my-ms-sql-server-articles.html' title='My MS SQL Server Articles'/><author><name>Alex Kuznetsov</name><uri>http://www.blogger.com/profile/00094638273349994292</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
