<?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 2005' and 'SQL Server 2008'</title><link>http://sqlblog.com/search/SearchResults.aspx?o=DateDescending&amp;tag=SQL+2005,SQL+Server+2008&amp;orTags=0</link><description>Search results matching tags 'SQL 2005' and 'SQL Server 2008'</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><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>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></channel></rss>