<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://sqlblog.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Search results matching tags 'SQL', 'SQL Server 2008', and 'SQL Server'</title><link>http://sqlblog.com/search/SearchResults.aspx?o=DateDescending&amp;tag=SQL,SQL+Server+2008,SQL+Server&amp;orTags=0</link><description>Search results matching tags 'SQL', 'SQL Server 2008', and 'SQL Server'</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>High Performance Dimensional Data Loads With SSIS Presentation</title><link>http://sqlblog.com/blogs/michael_coles/archive/2010/11/20/high-performance-dimensional-data-loads-with-ssis-presentation.aspx</link><pubDate>Sat, 20 Nov 2010 18:25:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:30834</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;Just finished giving the SSIS High-Performance Dimensional Data Load presentation at &lt;A title="SQLSaturday #59 NYC" href="http://www.sqlsaturday.com/59/schedule.aspx"&gt;SQLSaturday #59 NYC&lt;/A&gt;.&amp;nbsp; Here are the slides in PDF format.&amp;nbsp; I'll upload the Try-N-Save&amp;nbsp;code and sample data later for attendees to play with.&lt;/P&gt;
&lt;P&gt;Thanks to everyone who attended my session and thanks to Melissa D. and NJSQL for putting this together.&amp;nbsp; For those who are interested in Alejandro Mesa's composable DML solution to the problem of Type 2 dimension updates, here's the complete statement from the demo:&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;INSERT INTO Dim.Geography_Hash&lt;BR&gt;(&amp;nbsp;&lt;BR&gt;&amp;nbsp;CityName, CountyFIPS, CountyName, StateFIPS, &lt;BR&gt;&amp;nbsp;StateUSPS, StateName, ZIP, TimeOffset, &lt;BR&gt;&amp;nbsp;DaylightSavingTime, StartDateID, CurrentFlag, BatchID, &lt;BR&gt;&amp;nbsp;LineageID, CubeInd, SortOrder, Hash&lt;BR&gt;)&lt;BR&gt;SELECT CityName, CountyFIPS, CountyName, StateFIPS, &lt;BR&gt;&amp;nbsp;StateUSPS, StateName, ZIP, TimeOffset, &lt;BR&gt;&amp;nbsp;DaylightSavingTime, StartDateID, CurrentFlag, BatchID, &lt;BR&gt;&amp;nbsp;LineageID, CubeInd, SortOrder, Hash&lt;BR&gt;FROM&lt;BR&gt;(&lt;BR&gt;&amp;nbsp;MERGE INTO Dim.Geography_Hash AS Target&lt;BR&gt;&amp;nbsp;USING Staging.Geography_Hash AS Source&lt;BR&gt;&amp;nbsp;ON Target.ZIP = Source.ZIP&lt;BR&gt;&amp;nbsp;&amp;nbsp;AND Target.CurrentFlag = Source.CurrentFlag&lt;BR&gt;&amp;nbsp;WHEN MATCHED AND Target.Hash &amp;lt;&amp;gt; Source.Hash&lt;BR&gt;&amp;nbsp;&amp;nbsp;THEN UPDATE SET CurrentFlag = 'N'&lt;BR&gt;&amp;nbsp;WHEN NOT MATCHED&lt;BR&gt;&amp;nbsp;&amp;nbsp;THEN INSERT &lt;BR&gt;&amp;nbsp;&amp;nbsp;(&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;CityName, CountyFIPS, CountyName, StateFIPS, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;StateUSPS, StateName, ZIP, TimeOffset, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DaylightSavingTime, StartDateID, CurrentFlag, BatchID, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;LineageID, CubeInd, SortOrder, Hash&lt;BR&gt;&amp;nbsp;&amp;nbsp;)&lt;BR&gt;&amp;nbsp;&amp;nbsp;VALUES&lt;BR&gt;&amp;nbsp;&amp;nbsp;(&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Source.CityName, Source.CountyFIPS, Source.CountyName, Source.StateFIPS, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Source.StateUSPS, Source.StateName, Source.ZIP, Source.TimeOffset, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Source.DaylightSavingTime, Source.StartDateID, Source.CurrentFlag, Source.BatchID, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Source.LineageID, Source.CubeInd, Source.SortOrder, Source.Hash&lt;BR&gt;&amp;nbsp;&amp;nbsp;)&lt;BR&gt;&amp;nbsp;OUTPUT $action, inserted.CityName, inserted.CountyFIPS, inserted.CountyName, inserted.StateFIPS, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;inserted.StateUSPS, inserted.StateName, inserted.ZIP, inserted.TimeOffset, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;inserted.DaylightSavingTime, inserted.StartDateID, inserted.CurrentFlag, inserted.BatchID, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;inserted.LineageID, inserted.CubeInd, inserted.SortOrder, inserted.Hash&lt;BR&gt;)&lt;BR&gt;AS T &lt;BR&gt;(&lt;BR&gt;&amp;nbsp;action, CityName, CountyFIPS, CountyName, StateFIPS, &lt;BR&gt;&amp;nbsp;StateUSPS, StateName, ZIP, TimeOffset, &lt;BR&gt;&amp;nbsp;DaylightSavingTime, StartDateID, CurrentFlag, BatchID, &lt;BR&gt;&amp;nbsp;LineageID, CubeInd, SortOrder, Hash&lt;BR&gt;)&lt;BR&gt;WHERE action = 'UPDATE';&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;As mentioned, the OUTPUT clause on the inner MERGE statement feeds the outer INSERT clause.&amp;nbsp; Next stop&amp;nbsp;is &lt;A title="SQLSaturday #61 DC" href="http://www.sqlsaturday.com/61/schedule.aspx"&gt;SQLSaturday #61 in&amp;nbsp;DC&lt;/A&gt; at the beginning of December.&lt;/P&gt;</description></item><item><title>Find a Hash Collision, Win $100</title><link>http://sqlblog.com/blogs/michael_coles/archive/2010/04/17/find-a-hash-collision-win-100.aspx</link><pubDate>Sat, 17 Apr 2010 21:38:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:24374</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;Margarity Kerns recently published a very nice article at SQL Server Central on &lt;A href="http://www.sqlservercentral.com/articles/Data+Warehouse/69679/"&gt;using hash functions to detect changes&lt;/A&gt; in rows during the data warehouse load ETL process.&amp;nbsp; On the discussion page for the article I noticed a lot of the same old arguments against using hash functions to detect change.&amp;nbsp; After having this same discussion several times over the past several months in public and private forums, I've decided to see if we can't put this argument to rest for a while.&amp;nbsp; To that end I'm going to hold a little contest:&amp;nbsp; &lt;B&gt;Generate an SHA-1 hash collision and win $100 and a book&lt;/B&gt; (see bottom section for details).&amp;nbsp; Before I get into the details of the contest I'm going to give a little background of how this came about.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;&lt;U&gt;Background Info&lt;/U&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;&lt;B&gt;NOTE: If you aren't familiar with hash functions I highly recommend first reading the Wikipedia article at &lt;A href="http://en.wikipedia.org/wiki/Cryptographic_hash_function"&gt;http://en.wikipedia.org/wiki/Cryptographic_hash_function&lt;/A&gt;.&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;The idea of using a hash function for change detection is not new.&amp;nbsp; Essentially a hash function generates a "fingerprint" of your data that you can use to compare an inbound row and an existing row.&lt;/P&gt;
&lt;P&gt;Some people are wary of hash functions because they map a theoretically infinite number of large inputs to a much smaller finite set of hash values.&amp;nbsp; Most of the arguments people make against using hash functions for change detection boil down to variations of Murphy's Law:&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;"There's a chance of a &lt;I&gt;hash&lt;/I&gt; &lt;I&gt;collision&lt;/I&gt; [generating the same hash value for two different inputs], so a collision &lt;I&gt;will&lt;/I&gt; happen!"&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;People have different ways of dealing with this issue, including taking one of the following positions:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;The chance of collision is negligible so no additional precautions are required.&lt;/LI&gt;
&lt;LI&gt;A collision will absolutely happen so I won't use hash functions for change detection at all!&lt;/LI&gt;
&lt;LI&gt;A collision may happen so I want to use hash values only to initially narrow down the number of rows I need to compare fully.&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;Positions #1 and #2 above are at different ends of the spectrum.&amp;nbsp; Position #3 sits in the middle as a compromise solution.&amp;nbsp; While compromises may make for good politics, they often make for terrible technical solutions, as I'll discuss below.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;&lt;U&gt;Position #1: Odds of Collision are Low Enough to be Ignored&lt;/U&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;As far as position #1 is concerned, it depends on which hash function you're using.&amp;nbsp; You need to choose a true one-way &lt;I&gt;collision-free&lt;/I&gt;* cryptographic hash function with a wide bit length.&amp;nbsp; I normally recommend an SHA-2 hash function (256, 384 or 512 bit hash value), or when that's not available the SHA-1 160 bit hash function.&amp;nbsp; The odds of generating a collision with a 160 bit hash function are 2^80.&amp;nbsp; That is to say&amp;nbsp;you can expect a collision after you generate hashes for 1,208,925,819,614,629,174,706,176 rows of data.&lt;/P&gt;
&lt;P&gt;Of course if you're identifying rows by their natural or business keys this alternatively means you need to generate 1,208,925,819,614,629,174,706,176 variations of that single row before you'll hit a collision with SHA-1.&lt;/P&gt;
&lt;P&gt;To put that number in perspective, consider that Google processes 20,000,000,000,000,000 bytes (20 petabytes) of data per day.&amp;nbsp; If you were to store a single row in a database table for &lt;I&gt;every single byte&lt;/I&gt; Google processes each day, it would take you 60,446,290 days (approximately 156,600 years) to store 1,208,925,819,614,629,174,706,176 rows in that table.&lt;/P&gt;
&lt;P&gt;I personally assume position #1 on this subject, with the assumption that you have chosen a good solid hash function for the job.&amp;nbsp; More on this later.&lt;/P&gt;
&lt;P&gt;*A &lt;I&gt;collision-free&lt;/I&gt; cryptographic hash function is a one-way hash function with negligible probability of generating the same hash value for two different inputs. SHA-1 and SHA-256 are examples of collision-free cryptographic hash functions.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;&lt;U&gt;Position #2: I Don't Trust Hash Functions&lt;/U&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;This position can't really be argued with.&amp;nbsp; As shown above the odds of a collision with SHA-1 or another collision-free hash function are extremely low.&amp;nbsp; But if you don't trust it, you just don't trust it.&amp;nbsp; So the alternative is to compare every inbound column with every existing column.&amp;nbsp; It will cost you in efficiency on wide tables, but if you're not concerned about processing power, server resources&amp;nbsp;and execution time this classic method of change detection is well-proven to be 100% effective.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;&lt;U&gt;Position #3: The Compromise - Use Hash Values to Initially Narrow Down Results&lt;/U&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;This position is the compromise position that combines the implementation of #1 and #2 above.&amp;nbsp; It sounds wonderful in theory - use a hash function to narrow down your results, eliminating rows that don't need to be compared column by column; then compare all of the columns in the remaining rows that haven't been eliminated.&amp;nbsp; So let's look at a scenario:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;You are processing Row A through your ETL process into a target table. &amp;nbsp;Row B is the equivalent row in the target table (it has the same natural key/business key as Row A).&amp;nbsp; This assumes we are first locating the equivalent row in the target table by natural key/business key of the incoming row.&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;There are three possible scenarios:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Row B exists in the target table, and is equal to Row A (no change).&lt;/LI&gt;
&lt;LI&gt;Row B exists in the target table, but it is not equal to Row A (update).&lt;/LI&gt;
&lt;LI&gt;Row B does not exist in the target table (insert Row A).&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Let's say you've generated two hash values, h(A) is the hash for Row A and h(B) is the hash for Row B.&amp;nbsp; Now we need to use h(A) and h(B) to eliminate rows to get rid of the extra column by column comparisons.&amp;nbsp; Here are the rules you need to implement to use h(A) and h(B) to eliminate extra comparisons in this compromise solution:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;A.&amp;nbsp; &lt;STRONG&gt;h(A) is equal to h(B)&lt;/STRONG&gt;: according to the compromise, if h(A) = h(B) we need to compare all columns of the inbound row against the existing row since the belief is that the hash function can/will generate collisions.&amp;nbsp; The idea is that h(A) may have generated the same value as h(B) even if A &amp;lt;&amp;gt; B.&amp;nbsp; So we need to:&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;BLOCKQUOTE&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;(1)&amp;nbsp; Compare all columns in A and B.&amp;nbsp; If A = B then perform no action.&lt;/P&gt;
&lt;P&gt;(2)&amp;nbsp; Compare all columns in A and B.&amp;nbsp; If A &amp;lt;&amp;gt; B then update.&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;/BLOCKQUOTE&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;B.&amp;nbsp; &lt;STRONG&gt;h(A) is not equal to h(B)&lt;/STRONG&gt;: cryptographic hash functions guarantee that they will generate the same hash value for the exact same inputs.&amp;nbsp; So we can eliminate full row comparisons if h(A) &amp;lt;&amp;gt; h(B).&amp;nbsp; We know automatically that if h(A) &amp;lt;&amp;gt; h(B) then A &amp;lt;&amp;gt; B.&amp;nbsp; Just perform the update.&lt;/P&gt;
&lt;P&gt;C.&amp;nbsp;&amp;nbsp;&lt;STRONG&gt;h(B) is NULL&lt;/STRONG&gt;: that is, if Row B does not exist in the target table than h(B) is NULL.&amp;nbsp; This is a case where no further full-row comparisons are necessary.&amp;nbsp; Just insert the row.&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Now consider a slowly changing dimension (SCD) in a datamart application.&amp;nbsp; Many SCDs change slowly over time (hence the name &lt;I&gt;slowly&lt;/I&gt; changing dimension).&amp;nbsp; This means that new rows (updates and inserts) are far less common than receiving duplicate rows during ETL.&amp;nbsp; So the vast majority of your inbound data will fall under rule A(1) above.&amp;nbsp; So you're still performing comparisons of all columns for the vast majority of rows in a given table just to figure out that you don't need to update them after all!&lt;/P&gt;
&lt;P&gt;If you eliminate even 90% of the inbound rows under rule A(1) above you haven't saved much processing (you're still comparing all columns for changes for 90% of your inbound rows).&amp;nbsp; You probably actually cost yourself a lot of time and efficiency since you haven't accounted for the overhead of generating hash values for 100% of the inbound rows.&lt;/P&gt;
&lt;P&gt;The only way this compromise is more efficient is if a very large percentage of your inbound rows (much greater than 50+%) are inserts under Rule C or updates under Rule B above.&amp;nbsp; If the majority of your inbound rows are duplicates of existing rows under Rule A, you gain nothing.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;&lt;U&gt;The Contest&lt;/U&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;One-way collision-free cryptographic hash functions are supposed to have negligible probability of a hash collision, or two different inputs generating the same output.&amp;nbsp; Hash collisions are what cause change detection with hashes to fail.&lt;/P&gt;
&lt;P&gt;For instance, consider the following example of an MD5 hash collision:&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;DECLARE @A varbinary(8000),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @B varbinary(8000),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @hA binary(16),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @hB binary(16);&lt;BR&gt;&lt;BR&gt;SELECT @A = 0xd131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f8955ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5bd8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a70,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @B = 0xd131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f8955ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5bd8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a70;&lt;BR&gt;&lt;BR&gt;SELECT @hA = HASHBYTES('MD5', @A),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @hB = HASHBYTES('MD5', @B);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;SELECT CASE WHEN @A = @B&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; THEN '@A Equals @B'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ELSE '@A Is Not Equal To @B'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; END AS AB_Equal,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CASE WHEN @hA = @hB&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; THEN '@hA Equals @hB'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ELSE '@hA Is Not Equal To @hB'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; END AS Hash_Equal;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The results are shown below:&lt;/P&gt;
&lt;P&gt;&lt;IMG style="WIDTH:474px;HEIGHT:130px;" src="http://e60ybw.bay.livefilestore.com/y1psxBkasfIDMgAHCMabS5PLB9ude1BiCrYSrlnX0bKKCpRgSTnMBYiy4gA2uLRjr5Tpf1Feki0LZ6WmJEDJiEl46UECVjVfPpt/hash_not_equal.png" width=474 height=130&gt;&lt;/P&gt;
&lt;P&gt;When you run this you'll notice that the query reports the two source varbinary strings @A and @B are not equal, yet the two MD5 hashes they generate are equal.&amp;nbsp; This is an example of a simple hash collision with MD5.&lt;/P&gt;
&lt;P&gt;Now the challenge is to populate the following script with two different binary values that generate the same hash value.&amp;nbsp; The output should be the same as shown above in the MD5 example.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;--&amp;nbsp; Begin script&lt;BR&gt;DECLARE @A varbinary(8000),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @B varbinary(8000),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @hA binary(20),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @hB binary(20);&lt;BR&gt;&lt;BR&gt;-- Replace the &lt;EM&gt;?&lt;/EM&gt; below with binary strings&lt;BR&gt;&lt;BR&gt;SELECT @A = &lt;EM&gt;?&lt;/EM&gt;,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @B = &lt;EM&gt;?&lt;/EM&gt;;&lt;BR&gt;&lt;BR&gt;SELECT @hA = HASHBYTES('SHA1', @A),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @hB = HASHBYTES('SHA1', @B);&lt;BR&gt;&lt;BR&gt;SELECT CASE WHEN @A = @B&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; THEN '@A Equals @B'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ELSE '@A Is Not Equal To @B'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; END AS AB_Equal,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CASE WHEN @hA = @hB&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; THEN '@hA Equals @hB'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ELSE '@hA Is Not Equal To @hB'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; END AS Hash_Equal;&lt;BR&gt;-- End script&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The first person who sends me an example of two varbinary strings that generate the same SHA1 hash value will win $100 (US$) and a copy of my book &lt;A href="http://www.amazon.com/T-SQL-2008-Programmer-rsquo-Guide/dp/143021001X"&gt;Pro T-SQL 2008 Programmer's Guide&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;And here are the inevitable conditions:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;&lt;B&gt;No NULLs.&lt;/B&gt;&amp;nbsp; @A and @B in the script above cannot be set to NULL for purposes of this contest.&lt;/LI&gt;
&lt;LI&gt;&lt;B&gt;8,000 bytes or less.&lt;/B&gt;&amp;nbsp; The T-SQL HASHBYTES function accepts varbinary(8000) values, so the values passed into it in this contest must be 8,000 bytes in length or less.&amp;nbsp; The values assigned to @A and @B above must be 8,000 bytes or less in length.&lt;/LI&gt;
&lt;LI&gt;&lt;B&gt;No unnecessary changes to the script.&lt;/B&gt;&amp;nbsp; The only change allowed to the script above are the replacement of the question marks (?) with binary strings.&amp;nbsp; No other changes to the script are authorized.&lt;/LI&gt;
&lt;LI&gt;&lt;B&gt;Only one person will win.&lt;/B&gt;&amp;nbsp; The first person who sends me a copy of the above script with two different binary values that generate an SHA-1 hash collision will win.&lt;/LI&gt;
&lt;LI&gt;&lt;B&gt;Void where prohibited.&lt;/B&gt;&amp;nbsp; Obviously if contests like this aren't legal in your country, state, county, city, etc. then you can't take part.&amp;nbsp; Petition your government to make it legal :)&lt;/LI&gt;
&lt;LI&gt;&lt;B&gt;Time limits.&lt;/B&gt;&amp;nbsp; Entries must be received prior to midnight U.S. Eastern Standard Time on October 31, 2010.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Decisions of the judge are final.&lt;/STRONG&gt;&amp;nbsp; For purposes of this contest that would be me.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;SQL Server 2005 or 2008.&lt;/STRONG&gt;&amp;nbsp; Entries must be runnable on&amp;nbsp;SQL Server 2005 and SQL Server 2008 Developer Edition, and the results must be reproducible.&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;If a winning entry is received prior to the deadline, I'll post an update entry to the blog with the winning script and the name of the winner.&lt;/P&gt;</description></item><item><title>Calculating Holidays in SQL</title><link>http://sqlblog.com/blogs/michael_coles/archive/2010/02/25/calculating-holidays-in-sql.aspx</link><pubDate>Thu, 25 Feb 2010 21:51:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:22626</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;Ask about&amp;nbsp;nearly any kind of SQL-based date calculation in the &lt;A title="SQL Public Newsgroup" href="http://groups.google.com/group/microsoft.public.sqlserver.programming/topics?hl=en"&gt;newsgroups&lt;/A&gt;, and you'll likely get responses that include use of an &lt;A title="Auxiliary Calendar Table @ ASPFAQ" href="http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html"&gt;auxiliary calendar table&lt;/A&gt;.&amp;nbsp; It's a really good idea, and something I highly recommend to anyone who has to do date-based calculations on the server.&amp;nbsp; For those who do dimensional modeling, you'll probably notice the auxiliary calendar table closely resembles a Time dimension with one-day intervals.&amp;nbsp; I won't get into too much detail on it, since ASPFAQ and the newsgroups cover it well.&lt;/P&gt;
&lt;P&gt;What I do want to talk about is calculating holidays, which can be important when populating your auxiliary calendar table.&amp;nbsp; When you create an auxiliary calendar table you may need to perform calculations based on business days, for instance; or you may need to schedule activities before, after, or even during certain holidays.&amp;nbsp; There are two types of holidays that I'll address:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Fixed holidays that fall on the same day every year&lt;/LI&gt;
&lt;LI&gt;Floating holidays for which the date can change from year to year&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Fixed holidays include holidays that always fall on the same day each year.&amp;nbsp; Some examples of fixed holidays include &lt;A title="Merry Christmas!" href="http://en.wikipedia.org/wiki/Christmas"&gt;Christmas&lt;/A&gt; (always December 25 in the West), &lt;A title="Oh, Canada!" href="http://en.wikipedia.org/wiki/Canada_Day"&gt;Canada Day&lt;/A&gt; (always July 1), &lt;A title="July 4th!" href="http://en.wikipedia.org/wiki/Independence_Day_(United_States)"&gt;U.S. Independence Day&lt;/A&gt; (always July 4).&amp;nbsp; These are fairly easy to detect and set in your auxiliary calendar table.&amp;nbsp; Just look for the static month + day combinations in your table and update it accordingly.&lt;/P&gt;
&lt;P&gt;Floating holidays are far more interesting and difficult to calculate.&amp;nbsp; Take Easter.&amp;nbsp; Easter actually has two definitions.&amp;nbsp; The popular definition is:&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;"Easter Day is the first Sunday after the full moon that occurs next after the vernal equinox"&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;This&amp;nbsp;is not the exact ecclesiastical definition though.&amp;nbsp; The full moon here is not the astronomical full moon but an "ecclesiastical moon" that's determined&amp;nbsp;through precalculated lookup tables.&amp;nbsp; The ecclesiastical rules are:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Easter falls on the first Sunday following the first ecclesiastical full moon that occurs on or after the day of the vernal equinox&lt;/LI&gt;
&lt;LI&gt;This particular ecclesiastical full moon is the 14th day of a tabular lunation (new moon)&lt;/LI&gt;
&lt;LI&gt;The vernal equinox is fixed as March 21&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;I'm not going to recount the long and painful history leading to the modern definition of Easter--but the details&amp;nbsp;can be found on &lt;A title="Easter Controversy" href="http://en.wikipedia.org/wiki/Easter_controversy"&gt;Wikipedia&lt;/A&gt;&amp;nbsp;if you're interested.&amp;nbsp; The main point is that Easter calculation is fairly complex.&amp;nbsp; The udf_CalculateEaster function below accepts the year (integer) as a parameter and returns the date of Easter for that year (datetime).&amp;nbsp; Notice the large number of calculations required to make this function work:&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;CREATE FUNCTION dbo.udf_CalculateEaster &lt;BR&gt;(&lt;BR&gt;&lt;/STRONG&gt;&lt;STRONG&gt;&amp;nbsp; @Year INT&lt;BR&gt;)&lt;BR&gt;RETURNS DATETIME&lt;BR&gt;AS&lt;BR&gt;BEGIN&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DECLARE @Date DATETIME,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @c INT, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @n INT, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @i INT, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @k INT, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @j INT, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @l INT, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @m INT, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @d INT;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT @n = @Year - 19 * (@Year / 19),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @c = @Year / 100,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @k = (@c - 17) / 25,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @i = @c - @c / 4 - (@c - @k) / 3 + 19 * @n + 15,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @i = @i - 30 * (@i / 30),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @i = @i - (@i / 28) * (1 - (@i / 28) * (29 / (@i + 1)) * ((21 - @n) / 11)),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @j = @Year + @Year / 4 + @i + 2 - @c + @c / 4,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @j = @j - 7 * (@j / 7),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @l = @i - @j,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @m = 3 + (@l + 40) / 44,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @d = @l + 28 - 31 * (@m / 4),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @Date = CAST&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CAST(@Year AS CHAR(4)) + &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RIGHT&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; '0' + CAST&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @m AS VARCHAR(2)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ), 2&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) + &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RIGHT&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; '0' +CAST&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @d AS VARCHAR(2)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ), 2&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) AS DATETIME&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; );&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; RETURN @Date;&lt;BR&gt;END;&lt;BR&gt;GO&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;If you want the date for Easter 2010, just run a query like the following:&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;SELECT dbo.udf_CalculateEaster (2010);&lt;BR&gt;GO&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The result is &lt;EM&gt;2010-04-04&lt;/EM&gt;.&lt;/P&gt;
&lt;P&gt;Another somewhat daunting type of floating holiday is the type that is based on the Nth weekday of the month. These holidays are defined using terms like "the 4th Thursday of November" (&lt;A title="Turkey Day!" href="http://en.wikipedia.org/wiki/Thanksgiving"&gt;U.S. Thanksgiving&lt;/A&gt;), "the&amp;nbsp;3rd&amp;nbsp;Monday in January" (&lt;A title="MLK Day!" href="http://en.wikipedia.org/wiki/Martin_Luther_King_Day"&gt;U.S. Martin Luther King Day&lt;/A&gt;), or even "the *last* Monday in May" (&lt;A title='Formerly "Decoration Day"' href="http://en.wikipedia.org/wiki/Memorial_day"&gt;U.S. Memorial Day&lt;/A&gt;). These floating holidays are not as hard to calculate as Easter, but still a little tricky (particularly Memorial Day).&lt;/P&gt;
&lt;P&gt;To calculate these dates we need a function like udf_nthWeekDay below. This function accepts 4 parameters:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;@n = integer that represents which instance of the weekday you're looking for.&amp;nbsp; This would be the 4 in "4th Thursday&amp;nbsp;in November".&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;@weekDay = 3-character day of week ("SUN" = Sunday, "MON" = Monday, etc.)&amp;nbsp; I used character abbreviations instead of numbers just to make it a little easier.&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;@year = integer year for which to calculate&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;@month = integer month for which to calculate (1 = January, 2 = February, etc.)&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;As you can see below, the Nth weekday calculation is pretty simple.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;CREATE FUNCTION dbo.udf_nthWeekDay&lt;BR&gt;(&lt;BR&gt;&amp;nbsp; @n&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; INT, &lt;BR&gt;&amp;nbsp; @weekDay CHAR(3),&lt;BR&gt;&amp;nbsp; @year&amp;nbsp;&amp;nbsp;&amp;nbsp; INT, &lt;BR&gt;&amp;nbsp; @month&amp;nbsp;&amp;nbsp; INT&lt;BR&gt;)&lt;BR&gt;RETURNS DATETIME&lt;BR&gt;AS&lt;BR&gt;BEGIN&lt;BR&gt;&amp;nbsp; DECLARE @date&amp;nbsp;&amp;nbsp;&amp;nbsp; DATETIME,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @dow&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; INT,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @offset&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; INT,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @wd&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; INT;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp; SELECT @wd = CASE @weekDay&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHEN 'SUN' THEN 1&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHEN 'MON' THEN 2&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHEN 'TUE' THEN 3&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHEN 'WED' THEN 4&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHEN 'THU' THEN 5&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHEN 'FRI' THEN 6&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHEN 'SAT' THEN 7&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; END,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @date = CAST&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CAST(@year AS VARCHAR(4)) +&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RIGHT&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; '0' + CAST&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @month AS VARCHAR(2)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ), 2&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) +&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; '01' AS DATETIME&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @dow = DATEPART(dw, @date),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @offset = @wd - @dow,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @date = DATEADD(day, @offset + (@n - CASE WHEN @offset &amp;gt;= 0 THEN 1 ELSE 0 END) * 7, @date);&lt;BR&gt;&amp;nbsp; RETURN @date;&lt;BR&gt;END;&lt;BR&gt;GO&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;To get the date for Thanksgiving 2024 you could call the function like this:&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;SELECT dbo.udf_nthWeekDay&lt;BR&gt;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'THU',&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2024, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 11&lt;BR&gt;&amp;nbsp; );&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The result is &lt;EM&gt;2024-11-28&lt;/EM&gt;.&amp;nbsp; Now for a holiday like U.S. Memorial Day ("the last Monday in May") the calculation is a little trickier.&amp;nbsp; You have no idea up front how many Mondays there are in any given May.&amp;nbsp; As an example, in 2010 and 2011 there are 5 Mondays in May; in 2012 and 2013 there are only 4.&amp;nbsp; You could try to figure out the number of Mondays in May of the given year, but that's way too much work.&lt;/P&gt;
&lt;P&gt;There's one thing we know about the last Monday in May with absolute certainty:&amp;nbsp; after the last Monday in May, the next Monday we encounter will *always* be the first Monday in June.&amp;nbsp; So the easier way to figure out the last Monday in May is to calculate the first Monday in June and then back into it (subtract 7 days), like this:&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;SELECT DATEADD&lt;BR&gt;(&lt;BR&gt;&amp;nbsp; day, &lt;BR&gt;&amp;nbsp; -7,&lt;BR&gt;&amp;nbsp; dbo.udf_nthWeekDay&lt;BR&gt;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'MON',&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2012, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6&lt;BR&gt;&amp;nbsp; )&lt;BR&gt;);&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;By calculating the date of the first Monday in June 2012, we can then subtract 7 days to get the date of the last Monday of May: &lt;EM&gt;2012-05-28&lt;/EM&gt;.&lt;/P&gt;
&lt;P&gt;You can use the functions to calculate holidays for your auxiliary calendar table.&amp;nbsp; I covered U.S. federal (and some other national) holidays in this one, but it can be extended to cover other national, state, and local holidays.&lt;/P&gt;</description></item><item><title>Service Packs... Coming to a Download Near You!</title><link>http://sqlblog.com/blogs/michael_coles/archive/2010/02/14/service-packs-coming-to-a-download-near-you.aspx</link><pubDate>Sun, 14 Feb 2010 23:07:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:22289</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;Based on feedback from the community (&lt;A href="https://connect.microsoft.com/SQLServer/feedback/details/522123/sql-server-2008-service-pack-2"&gt;https://connect.microsoft.com/SQLServer/feedback/details/522123/sql-server-2008-service-pack-2&lt;/A&gt;&amp;nbsp;and &lt;A href="https://connect.microsoft.com/SQLServer/feedback/details/522122/service-pack-4-for-sql-server-2005"&gt;https://connect.microsoft.com/SQLServer/feedback/details/522122/service-pack-4-for-sql-server-2005&lt;/A&gt;), Microsoft has announced plans to release Service Pack 2 for SQL Server 2008 and Service Pack 4 for SQL Server 2005. The official announcement from the SQL Server Group Project Manager Matthias&amp;nbsp;Berndt is &lt;A title="Service Pack Announcement" href="http://blogs.msdn.com/sqlreleaseservices/archive/2010/02/12/sql-server-servicing-plans.aspx" target=_blank&gt;here&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;Here's the abbreviated version:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Service Pack 2 for SQL Server 2008 is targeted for a Q3 release.&lt;/LI&gt;
&lt;LI&gt;Service Pack 4 for SQL Server 2005 is targeted for a Q4 release.&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;SP 4 is currently scheduled to be the final SP for 2005. The announcement also contains some info. about SQL 2005 going into extended support from April 2011 to April 2016.&lt;/P&gt;</description></item><item><title>NJ .NET User Group Presentation: Building a SQL Server Search Engine in .NET</title><link>http://sqlblog.com/blogs/michael_coles/archive/2010/02/07/nj-net-user-group-presentation-building-a-sql-server-search-engine-in-net.aspx</link><pubDate>Sun, 07 Feb 2010 18:11:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:21933</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;I'll be presenting at the Northern NJ .NET User Group meeting on Tuesday February 9.&amp;nbsp; Topics we'll cover include:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Intro to SQL Server 2008 Integrated Full-Text Search (iFTS) features and functionality, including:&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;Full-text indexes&lt;/LI&gt;
&lt;LI&gt;Thesaurus&lt;/LI&gt;
&lt;LI&gt;Word breakers, filters and stemming&lt;/LI&gt;
&lt;LI&gt;Multilanguage support&lt;/LI&gt;&lt;/UL&gt;
&lt;LI&gt;Intro to SQL Server 2008 FILESTREAM storage&lt;/LI&gt;
&lt;LI&gt;Troubleshooting with iFTS dynamic management views and functions&lt;/LI&gt;
&lt;LI&gt;Building a simple and powerful .NET-based search engine-style interface&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Click here for more information: &lt;A href="http://www.setfocus.com/n3ug/welcome.aspx"&gt;http://www.setfocus.com/n3ug/welcome.aspx&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;See you there!&lt;/P&gt;</description></item><item><title>It's Official - SQLSaturday is Coming to NYC!</title><link>http://sqlblog.com/blogs/michael_coles/archive/2010/02/06/it-s-official-sqlsaturday-is-coming-to-nyc.aspx</link><pubDate>Sat, 06 Feb 2010 19:59:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:21916</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;&lt;A title="NJSQL Home Page" href="http://www.njsql.org/" target=_blank&gt;New Jersey SQL Server User Group (NJSQL)&lt;/A&gt; is bringing &lt;A title="SQLSaturday #39 Home Page" href="http://www.sqlsaturday.com/39/eventhome.aspx" target=_blank&gt;SQLSaturday #39&lt;/A&gt;&amp;nbsp;to NYC on April 24, 2010!&amp;nbsp; The free all-day training event will be hosted by Microsoft at their Midtown Manhattan offices.&amp;nbsp; The speaker line-up is growing fast—if you'd like to present, visit the event's open &lt;A title="SQLSaturday #39 Call for Speakers" href="http://www.sqlsaturday.com/39/callforspeakers.aspx" target=_blank&gt;call for speakers&lt;/A&gt;.&amp;nbsp;&amp;nbsp;This is a free full-day training&amp;nbsp;event, but &lt;A title="SQLSaturday #39 Registration Page" href="http://www.sqlsaturday.com/39/register.aspx" target=_blank&gt;registration is required&lt;/A&gt;&amp;nbsp;to attend.&amp;nbsp; Seating is limited.&lt;/P&gt;
&lt;P&gt;Registration, speaker, and sponsorship details are posted at &lt;A href="http://www.sqlsaturday.com/39/eventhome.aspx"&gt;http://www.sqlsaturday.com/39/eventhome.aspx&lt;/A&gt;.&lt;/P&gt;</description></item><item><title>Parent-Child Build Scripts with SQLCMD</title><link>http://sqlblog.com/blogs/michael_coles/archive/2010/01/10/parent-child-build-scripts-with-sqlcmd.aspx</link><pubDate>Sun, 10 Jan 2010 18:33:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:20821</guid><dc:creator>Mike C</dc:creator><description>&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;On the &lt;A href="http://groups.google.com/group/microsoft.public.sqlserver.programming/browse_thread/thread/7837d5428e6c83fd?hl=en#"&gt;SQL Server public programming newsgroup&lt;/A&gt; someone recently posted a question about an SSMS error (&lt;A href="https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=269566#details"&gt;"Cannot parse script. 'System.OutOfMemoryException' thrown."&lt;/A&gt;) I hadn’t encountered this error myself, but the workaround is to break up very large scripts (50+ MB) into smaller scripts. Adam Machanic posted a T-SQL Tuesday challenge to post &lt;A href="http://sqlblog.com/blogs/adam_machanic/archive/2010/01/04/invitation-for-t-sql-tuesday-002-a-puzzling-situation.aspx"&gt;a solution to a puzzling situation&lt;/A&gt;, so this actually gives me a good opportunity to share how I structure my own build scripts -- which avoids this issue entirely.&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;When I create database build scripts, I use the&amp;nbsp;&lt;A href="http://msdn.microsoft.com/en-us/library/ms162773.aspx"&gt;SQLCMD&lt;/A&gt; utility to run them from the command line instead of using SSMS or another tool. SQLCMD has its own &lt;A href="http://msdn.microsoft.com/en-us/library/ms162773.aspx#sectionToggle3"&gt;commands&lt;/A&gt;, which it parses separately from SQL/T-SQL statements. These commands are not understood by SQL Server or other scripting tools like SSMS (&lt;I style="mso-bidi-font-style:normal;"&gt;exception: you can run SSMS in &lt;A href="http://msdn.microsoft.com/en-us/library/ms174187.aspx"&gt;SQLCMD mode&lt;/A&gt;, but that’s another story&lt;/I&gt;). These special SQLCMD commands all start with a ":" at the front of the line.&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;The SQLCMD command that makes parent-child structured build-scripts possible is the ":r" or "run" command, which tells SQLCMD to run another script file from within the current script file. In the figure below I’ve set up a local directory structure with database object creation scripts in subdirectories:&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;IMG style="WIDTH:367px;HEIGHT:544px;" src="http://e60ybw.bay.livefilestore.com/y1pbBwOsoJdF21J9eW0lf7zCk782rocpyFX5YFOkwiggop15Lzj9HOBHrhOEoj0jRq7wdUr8BaYFFcpRou_irLDIAXzS_bY7al3/sqlcmd-folders.png" width=367 height=544&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;SPAN style="mso-no-proof:yes;"&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;The &lt;EM&gt;\Scripts&lt;/EM&gt; directory contains a &lt;EM&gt;Create.All.Sql&lt;/EM&gt; script. This script uses the SQLCMD &lt;EM&gt;run&lt;/EM&gt; command to execute the &lt;EM&gt;Database\Create.Database.Sql&lt;/EM&gt; script, the &lt;EM&gt;Create.All.Schemas.Sql&lt;/EM&gt; script, and so on.&lt;SPAN style="mso-spacerun:yes;"&gt;&amp;nbsp; &lt;/SPAN&gt;The &lt;EM&gt;Create.All.Schemas.Sql&lt;/EM&gt; script calls the &lt;EM&gt;Person.Schema.Sql&lt;/EM&gt; and &lt;EM&gt;Sales.Schema.Sql&lt;/EM&gt; scripts in turn. The other &lt;EM&gt;Create.All.*&lt;/EM&gt; scripts each call the object creation scripts in their subdirectories as well. Here’s what my Create.All.Sql script looks like:&lt;/FONT&gt;&lt;/P&gt;&lt;PRE&gt;/*&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Create All Items&lt;BR&gt;*/&lt;BR&gt;&lt;BR&gt;
:r Database\Create.Database.sql&lt;BR&gt;:r Schemas\Create.All.Schemas.sql&lt;BR&gt;:r Types\Create.All.Types.sql&lt;BR&gt;:r Tables\Create.All.Tables.sql&lt;FONT size=3 face=Calibri&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/PRE&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;Each &lt;EM&gt;:r&lt;/EM&gt; command kicks off the next level of child packages in turn.&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;SQLCMD has another great feature known as scripting variables that you can use to create dynamic scripts. Essentially you define a scripting variable on the command line with SQLCMD's &lt;EM&gt;-v&lt;/EM&gt; command line option. Now the way scripting variables work, they are replaced wholesale in your scripts with their replacement value. So if you define a scripting variable named &lt;EM&gt;environment&lt;/EM&gt; you can replace it with a value like "Dev", "QA" or "Prod" anywhere it occurs in your script. This is great for making dynamic scripts that need to be built across multiple environments.&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;In the example I've used a scripting variable named &lt;EM&gt;database&lt;/EM&gt;. You can set the value of the &lt;EM&gt;database&lt;/EM&gt; variable from the command line with the &lt;EM&gt;-v&lt;/EM&gt; option. In the example below I set the &lt;EM&gt;database&lt;/EM&gt; variable to the value "Test".&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;&lt;IMG style="WIDTH:843px;HEIGHT:187px;" title="SQLCMD Sample Command Line" alt="SQLCMD Sample Command Line" src="http://e60ybw.bay.livefilestore.com/y1py7Y3IXJHMFCeZDgZtZNWmVfQwxljC67X_AsNqD8JsJ1OM2OCxuApxSfz5V1Ze44963nb3_tZV4GUah4-4Dj0disbCrc5c7FD/Set-Scripting-Variable.png" width=843 height=187&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;SPAN style="mso-no-proof:yes;"&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;The nice thing about the SQLCMD scripting variables is that once you declare them you can access them from the parent script you run (in this case &lt;EM&gt;Create.All.Sql&lt;/EM&gt;) or from any child scripts that are run (like &lt;EM&gt;Create.Database.Sql&lt;/EM&gt;, &lt;EM&gt;Create.All.Schemas.Sql&lt;/EM&gt;, &lt;EM&gt;Person.Schema.Sql&lt;/EM&gt; and &lt;EM&gt;Sales.Schema.Sql&lt;/EM&gt;). Here’s the &lt;EM&gt;Create.Database.Sql&lt;/EM&gt; script from the example:&lt;/FONT&gt;&lt;/P&gt;&lt;PRE&gt;/*&lt;BR&gt;&lt;BR&gt;&amp;nbsp;Create database&lt;BR&gt;&lt;BR&gt;*/&lt;BR&gt;&lt;BR&gt;USE master;&lt;BR&gt;GO&lt;BR&gt;&lt;BR&gt;
CREATE DATABASE &lt;STRONG&gt;&lt;U&gt;$(database)&lt;/U&gt;&lt;/STRONG&gt;;&lt;BR&gt;GO&lt;FONT size=3 face=Calibri&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/PRE&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;The scripting variable is accessed in the script with &lt;EM&gt;$(database)&lt;/EM&gt;. The scripting variable is replaced with its value by SQLCMD, so in the example SQL Server sees this:&lt;/FONT&gt;&lt;/P&gt;&lt;PRE&gt;/*&lt;BR&gt;&lt;BR&gt;&amp;nbsp;Create database&lt;BR&gt;&lt;BR&gt;*/&lt;BR&gt;&lt;BR&gt;USE master;&lt;BR&gt;GO&lt;BR&gt;&lt;BR&gt;
CREATE DATABASE &lt;STRONG&gt;&lt;U&gt;Test&lt;/U&gt;&lt;/STRONG&gt;;&lt;BR&gt;GO&lt;FONT size=3 face=Calibri&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/PRE&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;One thing to keep in mind when you use this scripting pattern is that every script should end with the batch terminator (default is "GO").&amp;nbsp; If not you could end up with one script running into another and get some strange, not-very-helpful&amp;nbsp;error messages.&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;Another thing you need to know is that scripting variables are replaced &lt;EM&gt;wholesale&lt;/EM&gt; with their replacement text. This makes&amp;nbsp;them very flexible, since you can replace text anywhere in the script with anything you want. It can also be dangerous if your script is run by someone with malicious intent. A malicious user can replace a scripting variable with T-SQL statements that could damage your data or database structure; so keep your scripting variable-enabled scripts out of the hands of potentially malicious users.&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN:0in 0in 10pt;" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;I've attached a sample ZIP file with the directory structure shown in the example above. The scripts build out a few database objects from the AdventureWorks sample database.&lt;/FONT&gt;&lt;/P&gt;</description></item><item><title>NJSQL Saturday Event (10/24)</title><link>http://sqlblog.com/blogs/michael_coles/archive/2009/10/06/njsql-saturday-event-10-24.aspx</link><pubDate>Tue, 06 Oct 2009 19:37:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:17325</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;The NJSQL user group is hosting a one-day Saturday SQL Server event at the Microsoft Iselin offices.&amp;nbsp; On October 24th Jacob Sebastian, Robert Pearl and I will be presenting on the following SQL Server topics:&lt;/P&gt;
&lt;P&gt;* T-SQL Defensive coding and exception handling best practices&lt;BR&gt;* Database forensics in SQL Server ("Who did it and ran?")&lt;BR&gt;* SQL Server 2008 spatial data ("Where in the World?")&lt;/P&gt;
&lt;P&gt;This one-day&amp;nbsp;event is free, but registration is required.&amp;nbsp; More details, including registration link at: &lt;A href="http://njsql.org/Default.aspx"&gt;http://njsql.org/Default.aspx&lt;/A&gt;&lt;/P&gt;</description></item><item><title>No Backup For Asymmetric Keys</title><link>http://sqlblog.com/blogs/michael_coles/archive/2009/07/10/no-backup-for-asymmetric-keys.aspx</link><pubDate>Fri, 10 Jul 2009 15:32:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:15183</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;The encryption features in SQL Server 2005 and 2008 provide the ability to create asymmetric encryption key pairs (RSA public/private key pairs) using the T-SQL &lt;FONT face=courier&gt;CREATE ASYMMETRIC KEY&lt;/FONT&gt; statement. One feature that was left out, however, is the ability to backup and restore asymmetric key pairs generated on the server. Once an asymmetric key pair has been created there's no way to export it out of the server. Unlike symmetric keys, you can't create a duplicate asymmetric key with T-SQL statement options. This can be a serious issue for your disaster recovery (DR) program - if a server goes down you'll never regenerate that same asymmetric key again.&lt;/P&gt;
&lt;P&gt;What you can do, however, is import asymmetric keys that were generated by an external source. You can import asymmetric key pairs from strong name key (SNK) files (and public keys only from executable files and assemblies). As long as you import your asymmetric key pairs from SNK files, you can always back up those files to a secure location to handle your DR requirements.&lt;/P&gt;
&lt;P&gt;Alternatively you can use certificates (via the &lt;FONT face=courier&gt;CREATE CERTIFICATE&lt;/FONT&gt; statement), which contain an asymmetric key pair and some additional metadata (certificate name, subject, expiration date, etc.) SQL Server does provide the necessary facilities to backup and restore certificates.&lt;/P&gt;
&lt;P&gt;Basically if you need to use asymmetric encryption on SQL Server, my recommendation is to (1) generate your asymmetric keys elsewhere and import them into SQL Server, or (2) use certificates instead of asymmetric keys.&lt;/P&gt;
&lt;P&gt;So the question is just sort of hanging there -- what purpose does it server to create an asymmetric key pair that you can never export or&amp;nbsp;back up? The best answer I've come up with so far is that you can use these randomly-generated non-exportable asymmetric key pairs for testing, when you don't want to provide your developers with your real production asymmetric keys. This answer is a little unsatisfying, so if anyone out there has a better answer, hit me up!&lt;/P&gt;</description></item><item><title>&amp;quot;Cloning&amp;quot; Symmetric Keys</title><link>http://sqlblog.com/blogs/michael_coles/archive/2009/06/17/cloning-symmetric-keys.aspx</link><pubDate>Thu, 18 Jun 2009 01:40:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:14741</guid><dc:creator>Mike C</dc:creator><description>&lt;P&gt;It's well-known by now that SQL Server 2005 and 2008 include new encryption-related statements that allow you to create and administer encryption keys. You can use &lt;FONT face=courier&gt;CREATE CERTIFICATE&lt;/FONT&gt; to create or&amp;nbsp;import a certificate or &lt;FONT face=courier&gt;DROP ASYMMETRIC KEY&lt;/FONT&gt; to remove an asymmetric key from the database, for instance.&amp;nbsp; One of the interesting ommissions from the T-SQL encryption statements is the statements necessary to backup and restore a symmetric key.&amp;nbsp; Why would you want to do this?&amp;nbsp; I can think of a couple of reasons off the top of my head:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;You need to backup symmetric keys (and all other encryption keys, in fact) as part of an overall disaster recovery (DR) program.&amp;nbsp; If a server needs to be rebuilt you obviously need a way to restore all encryption keys.&lt;/LI&gt;
&lt;LI&gt;You need to implement the same symmetric keys on multiple servers.&amp;nbsp; There could be a couple of reasons for this -- you might encrypt data on one server and decrypt it on another, or you might be load-balancing across a server farm and need identical encryption keys on multiple servers simultaneously.&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;It seems like a bit of an oversight to not include &lt;FONT face=courier&gt;BACKUP&lt;/FONT&gt; and &lt;FONT face=courier&gt;RESTORE SYMMETRIC KEY&lt;/FONT&gt; options in T-SQL, but in practice you can effectively achieve the same end results with the standard &lt;FONT face=courier&gt;CREATE SYMMETRIC KEY&lt;/FONT&gt; statement.&amp;nbsp; Basically the &lt;FONT face=courier&gt;CREATE SYMMETRIC KEY&lt;/FONT&gt; statement gives you an option to "clone" the exact same symmetric key on any SQL Server 2005 or 2008 instance, anywhere, at any time.&amp;nbsp; To create a cloneable symmetric key you need to specify two special &lt;FONT face=courier&gt;CREATE SYMMETRIC KEY&lt;/FONT&gt; options:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;The &lt;FONT face=courier&gt;IDENTITY_VALUE&lt;/FONT&gt; option, which SQL Server uses to generate a GUID (uniqueidentifier) for the key&lt;/LI&gt;
&lt;LI&gt;The &lt;FONT face=courier&gt;KEY_SOURCE&lt;/FONT&gt; option, which SQL Server uses as key material to generate the actual key&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;As long as you specify the same values for the &lt;FONT face=courier&gt;IDENTITY_VALUE&lt;/FONT&gt; and &lt;FONT face=courier&gt;KEY_SOURCE&lt;/FONT&gt; options (and the same &lt;FONT face=courier&gt;ALGORITHM&lt;/FONT&gt;), your symmetric key will be exactly the same no matter where, when, or how many times&amp;nbsp;you create it.&amp;nbsp; To be honest, if I were creating a list, always use &lt;FONT face=courier&gt;IDENTITY_VALUE&lt;/FONT&gt; and &lt;FONT face=courier&gt;KEY_SOURCE&lt;/FONT&gt; would be listed in the top 10 list of SQL Server encryption best practices.&lt;/P&gt;
&lt;P&gt;Here's a quick sample demonstrating the &lt;FONT face=courier&gt;CREATE SYMMETRIC KEY&lt;/FONT&gt; statement with &lt;FONT face=courier&gt;IDENTITY_VALUE&lt;/FONT&gt; and &lt;FONT face=courier&gt;KEY_SOURCE&lt;/FONT&gt; specified:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=courier&gt;CREATE SYMMETRIC KEY test_aes128_key&lt;BR&gt;WITH KEY_SOURCE = 'I am the very model of a modern major general',&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IDENTITY_VALUE = 'E pluribus unum',&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ALGORITHM = AES_128&lt;BR&gt;ENCRYPTION BY PASSWORD = &lt;A href="mailto:'p@$$w0rd'"&gt;'p@$$w0rd'&lt;/A&gt;;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;This &lt;FONT face=courier&gt;CREATE SYMMETRIC KEY&lt;/FONT&gt; statement will create the same symmetric encryption key on any SQL Server 2005 or 2008 instance on which you run it.&amp;nbsp; This brings up another point, about security.&amp;nbsp; If the same &lt;FONT face=courier&gt;KEY_SOURCE&lt;/FONT&gt; and &lt;FONT face=courier&gt;IDENTITY_VALUE&lt;/FONT&gt; options can create the exact same encryption key on any of your servers, they will create the exact same encryption key on any of my servers, or any server owned by any hacker anywhere in the world.&amp;nbsp; So once you've run your &lt;FONT face=courier&gt;CREATE SYMMETRIC KEY&lt;/FONT&gt; statement, the &lt;FONT face=courier&gt;IDENTITY_VALUE&lt;/FONT&gt; and &lt;FONT face=courier&gt;KEY_SOURCE&lt;/FONT&gt; need to be handled like any other secure information.&amp;nbsp; Don't leave them lying around where just anyone can access them.&amp;nbsp; Store them with your certificates, key backups, and other confidential materials in a secure off-site location.&lt;/P&gt;
&lt;P&gt;So what happens when you don't specify the &lt;FONT face=courier&gt;IDENTITY_VALUE&lt;/FONT&gt; and &lt;FONT face=courier&gt;KEY_SOURCE&lt;/FONT&gt; options?&amp;nbsp; Well, basically SQL Server generates an unpredictable GUID to identify the symmetric key and the encryption key source material is randomly generated.&amp;nbsp; Basically you'll never regenerate the exact same key again.&amp;nbsp; Ever.&amp;nbsp; There could be situations where this would be handy.&amp;nbsp; The concept of "session keys" comes to mind.&amp;nbsp; A session key is basically a "temporary" key that's only required to encrypt data for a user during a single session.&amp;nbsp; Since it only exists for the life of the session, a totally randomly-generated key is just fine.&lt;/P&gt;
&lt;P&gt;For my tastes, it would make more sense to require &lt;FONT face=courier&gt;IDENTITY_VALUE&lt;/FONT&gt; and &lt;FONT face=courier&gt;KEY_SOURCE&lt;/FONT&gt; options by default. If you didn't want to specify both of these options there should be another option/indicator specifically to say that you want these options generated randomly.&amp;nbsp; At any rate, it's a good idea to get into the habit of treating these options as if they are mandatory unless they have a very specific special-purpose requirement (e.g., "session keys").&lt;/P&gt;</description></item></channel></rss>