<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>Articles</title>
        <link>http://www.jasonfollas.com/blog/category/1.aspx</link>
        <description>Short articles written and published by Jason Follas</description>
        <language>en-US</language>
        <copyright>Jason Follas</copyright>
        <managingEditor>jason@jasonfollas.com</managingEditor>
        <generator>Subtext Version 2.0.0.43</generator>
        <item>
            <title>Install Zune 3.0 Software on Windows 2008 Server</title>
            <link>http://jasonfollas.com/blog/archive/2008/09/16/install-zune-3.0-software-on-windows-2008-server.aspx</link>
            <description>&lt;p&gt;I use Windows 2008 Server as a workstation.  I find that certain things actually work better (like Sleep Mode, for instance, which is weird because servers shouldn't need to sleep, but Vista would often not go to sleep and drain my battery the entire couple of hours that it continued to run in my computer bag, and Windows Server 2008 has never done that.... but I digress).&lt;/p&gt;
&lt;p&gt;However, when the new Zune 3.0 software was released today, I found, like many others, that the software explicitly listed three operating systems that were supported, and Server 2008 was unfortunately not one of them.  The install refused to continue. &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/9/r_zune3pic2.png" /&gt;&lt;/p&gt;
&lt;p&gt;While experimenting, I found a way to install the Zune 3.0 software manually.  It appears to be fully functional on my machine, but I assume no responsibility and will provide no support if you decide to follow these instructions on your own machine.&lt;/p&gt;
&lt;p&gt;First, I noticed that the setup bootstrap (zunesetuppkg-x86.exe) says where it's extracting its files to:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/9/r_zune3pic1.png" /&gt;&lt;/p&gt;
&lt;p&gt;(This was weird to me, since the E: drive on my machine is an external USB hard drive...)&lt;/p&gt;
&lt;p&gt;Next, I opened that directory in Explorer.  There, I found a "packages" directory:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/9/r_zune3pic3.png" /&gt;&lt;/p&gt;
&lt;p&gt;And inside of there, I found a MSI file called Zune-x86.msi:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/9/r_zune3pic4.png" /&gt;&lt;/p&gt;
&lt;p&gt;Ok, learn from my trial-and-error here: uninstall any previous version of the Zune software, because this MSI will not do it for you, and will not install if a previous version exists.  Other than that, I just double-clicked on the MSI, and a minute later, I had the 3.0 software on my machine!&lt;/p&gt;
&lt;p&gt;Note that I am currently behind a ISA Server proxy/firewall that requires authentication with Active Directory.  Therefore, in order to download the firmware update, I had to change my UseLmCompat to "0" per this blog post:&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;a href="http://jasonfollas.com/blog/archive/2007/11/13/how-to-get-the-zune-2.2-firmware-when-your-corporate.aspx"&gt;http://jasonfollas.com/blog/archive/2007/11/13/how-to-get-the-zune-2.2-firmware-when-your-corporate.aspx&lt;/a&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/52.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/09/16/install-zune-3.0-software-on-windows-2008-server.aspx</guid>
            <pubDate>Tue, 16 Sep 2008 18:53:40 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/52.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/09/16/install-zune-3.0-software-on-windows-2008-server.aspx#feedback</comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/52.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/52.aspx</trackback:ping>
        </item>
        <item>
            <title>Announcing Tourniquet</title>
            <link>http://jasonfollas.com/blog/archive/2008/08/18/announcing-tourniquet.aspx</link>
            <description>&lt;p&gt;Have you ever gotten an idea stuck in your head?  One that you start your day thinking about in the shower, and then try as you might, you just can't get rid of it?&lt;/p&gt;
&lt;p&gt;That's what happened to me a few months ago.  Specifically, I started thinking about &lt;a href="http://twitter.com/jfollas"&gt;Twitter&lt;/a&gt;, and the problems that it was experiencing.  At that time, the "Fail Whale" was making very frequent appearances, indicating that Twitter was having problems keeping up with the demands being made on it.  My Tweeps (social networking friends on Twitter), all with short attention spans like myself, began chattering about moving to a more reliable platform.  &lt;/p&gt;
&lt;p&gt;But, in my estimation, Twitter was still the best platform to remain on.  Among other things, it was actually the most "mature" in its class - if you can call something a mere two years old as being mature.&lt;/p&gt;
&lt;h4&gt;So, Jason, what is Tourniquet?&lt;/h4&gt;
&lt;p&gt;&lt;font face="Arial"&gt;After quite a few weeks of thinking about the various problems that I was aware of, I came up with a pretty simple solution.  I needed to bounce some ideas off of a sounding board, so I fired off an email to some of my friends: &lt;a target="_blank" href="http://netcave.org/"&gt;Alan Stevens&lt;/a&gt;, &lt;a target="_blank" href="http://keithelder.net"&gt;Keith Elder&lt;/a&gt;, and &lt;a target="_blank" href="http://www.michaeleatonconsulting.com/"&gt;Micheal Eaton&lt;/a&gt;.  &lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;The contents of that email still serve as a pretty good overview for my vision, which has been realized as "Tourniquet":&lt;/font&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Think of a multi-faceted approach to fixing Twitter's issues that ultimately concentrates on reducing the number of calls that you make into Twitter itself, as well as provides transparent access to Twitter data during Twitter downtimes:&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;1. A personal Twitter proxy.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;This is simply an API passthrough service that you host yourself.  The thought process here is that Witty and other clients could be configured as to what server is accessed to execute an API call (i.e., by default, it's &lt;font face="Courier New"&gt;http://www.twitter.com/&lt;/font&gt;, but someone hosting a Twitter proxy would be able to specify something like &lt;font face="Courier New"&gt;http://thisismydomainyo.com/tourniquet/&lt;/font&gt; ).  Aside from the server, there's nothing different about the request or response.  That is, the client might hit &lt;font face="Courier New"&gt;http://thisismydomainyo.com/tourniquet/statuses/friends_timeline.xml &lt;/font&gt;instead of&lt;br /&gt;
&lt;font face="Courier New"&gt;http://www.twitter.com/statuses/friends_timeline.xml&lt;/font&gt;.  &lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;This is not a public/shared service - it would be intended for just the user.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;2. Obfuscation/encryption&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Network Nazis suck, and so do people who brag about having smart phones and data plans.  :-)  There may be other reasons why someone would want the URL obfuscated and the response from Twitter encrypted when transfered between the Twitter Proxy and the client.  But, that's what I'm talking about.  Clients would need to be modified to support encryption/obfuscation before the user can utilize it.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;3. Caching&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;While the Tourniquet proxy is fetching the information from Twitter, it might as well cache it to some form of persistent storage.  This can be used to save some calls into the Twitter API, especially for historical data (which is also useful for when they disable access to historical data during times of heavy demand)&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt; &lt;font face="Arial"&gt;4. Store and Forward&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Is Twitter down?  Damn!  But, no worries with Tourniquet!  Your status update is saved until Twitter comes back up.  This is really no different than other store-and-forward services, except you're not giving your twitter credentials to some unknown third party website.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt; &lt;font face="Arial"&gt;5. Automated fetching&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Perhaps Tourniquet can periodically fetch your timeline for you and cache it, either by means of some external triggering or by a timer. Then, when you hit the proxy to check for updates, it's already there (and your current request would likely trigger another fetch just to make sure that it has the latest data)&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt; &lt;font face="Arial"&gt;6. Tribe-Net Sharing/Synchronization&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Here's where the service gets interesting: @keithelder and @jfollas follow each other, and both run Tourniquet.  So, both proxies can be configured to be able to sync statuses between each other, hopefully saving some Twitter API hits that count towards your hourly usage. (I'm thinking that Direct Messages can somehow be used to announce Tourniquet endpoints).  If Twitter is down and there are some status updates that are available (but not yet on Twitter), those can be propogated across the cloud via proxy-to-proxy synchronization. Eventually, they'll show up as actual Twitter statuses.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;I picture the communication resembling something like this:&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;@jfollas's proxy calls @keithelder's proxy, and announces the highest message ID for each person that @jfollas follows.  @keithelder's proxy returns a list statuses for each of those people that it has cached where the message ID is higher (plus any new/unpublished statuses that are in store-and-forward).  @keithelder's proxy will need to call @jfollas's proxy to reciprocate the process.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Another possibility is to also synchronize a single person, perhaps with the goal of maintaining a cache of the last 100 tweets per person on your follow list.  In this case, @jfollas's proxy will call @keithelder's proxy and list all of the message id's that it has cached for the person.  If @keithelder is also following that person (or otherwise happens to have some tweets cached for the person), then any new messages (or any in-between messages that @jfollas's proxy might have missed) will be supplied in the response.&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That was the original concept.  The name "Tourniquet" came from the same place where all good project names come from: the Thesaurus.  I simply looked for synonyms for the word "bandage", stumbled upon this word, and discovered that it was not already well-known as a software product.&lt;/p&gt;
&lt;h4&gt;Alright, sounds good.  Where is Tourniquet?&lt;/h4&gt;
&lt;p&gt;Tourniquet is not a product, per se.  It's a project, and an open source project at that (MIT license).  You can download the source and do just about anything with it from the project site on Codeplex:&lt;/p&gt;
&lt;p align="center"&gt;&lt;a href="http://codeplex.com/tourniquet"&gt;&lt;img alt="Tourniquet Logo" border="0" src="http://www.codeplex.com/Project/Download/FileDownload.aspx?ProjectName=tourniquet&amp;amp;DownloadId=41394" /&gt;&lt;br /&gt;
http://codeplex.com/tourniquet&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To run Tourniquet, you will need to grab the release from Codeplex, set up a database (i.e., run the create scripts), copy the files from the release to your webserver, and then set up a new web application on your webserver.  If this sounds too complicated, then perhaps you should wait until it's a little more refined before checking it out.  I'm just sayin'...   :-P&lt;/p&gt;
&lt;p&gt;As I wrote the code, I tried to keep in mind that not everyone has a really sweet hosting deal.  Therefore, I targeted what I thought to be the lowest common denominators: ASP.NET 2.0 and SQL Server 2005.  &lt;/p&gt;
&lt;p&gt;Classifying SQL Server as a LCD, though, bothered me, for a lot of "common man" hosting plans do not include access to a database at all.  But, being a SQL Server MVP, I found this to be the quickest way to build the prototype and release the project to Codeplex.  The persistance layer actually uses the Provider model, so my goal is to make alternative providers that do not require SQL Server (i.e., maybe XML on the filesystem, or Amazon SimpleDB, etc).&lt;/p&gt;
&lt;p&gt;The point that I'd like to drive home is that the current codebase is very much a proof-of-concept or prototype, albeit a fully functional one (I've been using it for a few weeks).  People may point at my code and say "Why did you do this like that?  Where are your tests?  This code sucks!" and that's okay.  In its current form, it does most of what I outlined in the email above, so I'm content (working software is the #1 measure of success).&lt;/p&gt;
&lt;p&gt;I encourage anyone who wants to participate in taking this prototype to the next level to join the development team.  Contact me through the site, Codeplex, or Twitter (@jfollas).  I'd be very happy if some enthusiastic people could take on the development of parts of the system and run with it.&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/49.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/08/18/announcing-tourniquet.aspx</guid>
            <pubDate>Mon, 18 Aug 2008 04:16:43 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/49.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/08/18/announcing-tourniquet.aspx#feedback</comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/49.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/49.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL Server 2008: Spatial Data, Part 8</title>
            <link>http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx</link>
            <description>&lt;p&gt; &lt;/p&gt;
&lt;p&gt;In this, the eighth part in a series on the new Spatial Data types in SQL Server 2008, I'll step away from the database and do a little spatial coding using .NET.&lt;/p&gt;
&lt;h2&gt;Redistributable .NET Library&lt;/h2&gt;
&lt;p&gt;Up to this point in &lt;a target="_blank" href="http://www.jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;the series&lt;/a&gt;, I have demonstrated a lot of interesting (?) things that you can do with the new Spatial data types (&lt;strong&gt;Geometry&lt;/strong&gt; and &lt;strong&gt;Geography&lt;/strong&gt;) in SQL Server 2008.  You might be thinking, "That's swell, and all, but I wish I could do some of that stuff without needing to be tethered to a database."  Well, you know what?  You can!&lt;/p&gt;
&lt;p&gt;I mentioned in a previous post that the Spatial data types were implemented as SQLCLR User-Defined Types.  I've since been corrected by &lt;a target="_blank" href="http://blogs.msdn.com/isaac"&gt;Isaac Kunen&lt;/a&gt;, who stated that they are more accurately described as System-Defined Types, with the difference being that these are automatically installed and available for use as part of SQL Server 2008, regardless of whether the ENABLE CLR bit has been activated.  Semantics aside, these types are merely classes within a .NET assembly, and Microsoft is making this freely available as part of a Feature Pack for SQL Server (which will be redistributable as part of your stand-alone application, according to Isaac):&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" href="http://www.microsoft.com/downloads/details.aspx?FamilyId=089A9DAD-E2DF-43E9-9CD8-C06320520B40&amp;amp;displaylang=en"&gt;SQL Server 2008 RC0 Feature Pack Download Page&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(Look for "Microsoft SQL Server System CLR Types," which includes the two Spatial types plus the HierarchyID type.  This link is for RC0, and may not be applicable to future versions as the product is finalized and released.)&lt;/p&gt;
&lt;h2&gt;Builder API&lt;/h2&gt;
&lt;p&gt;A new feature that was included with the first Release Candidate (RC0) is the &lt;a target="_blank" href="http://blogs.msdn.com/isaac/archive/2008/05/30/our-upcoming-builder-api.aspx"&gt;Builder API&lt;/a&gt;.  This is a collection of interfaces and classes that helps you to construct spatial types by specifying one point at a time until all points have been added.&lt;/p&gt;
&lt;p&gt;The Builder API is not only useful for creating new instances of spatial data, but also for consuming existing instances one point at a time (maybe to convert an instance into another format).  Documentation is light at the moment, so I'm still trying to grok exactly how to best utilize it.&lt;/p&gt;
&lt;p&gt;For my first experiment with the API, I obtained some Zip Code Boundary data in ASCII format from the U.S. Census Bureau:&lt;/p&gt;
&lt;p&gt;&lt;a title="http://www.census.gov/geo/www/cob/z52000.html#ascii" href="http://www.census.gov/geo/www/cob/z52000.html#ascii"&gt;http://www.census.gov/geo/www/cob/z52000.html#ascii&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My goal was to parse the data, and then create a new &lt;strong&gt;SqlGeography&lt;/strong&gt; instance for each zip code.  (Note: &lt;strong&gt;SqlGeography&lt;/strong&gt; is the .NET class name that T-SQL refers to simply as &lt;strong&gt;Geography&lt;/strong&gt;).  The &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; class proved to be perfect for accomplishing this task.&lt;/p&gt;
&lt;p&gt;At its core, the &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; implements the &lt;strong&gt;IGeographySink&lt;/strong&gt; interface.  If you wanted to consume an existing &lt;strong&gt;SqlGeography&lt;/strong&gt; instance, you could implement &lt;strong&gt;IGeographySink&lt;/strong&gt; in your own class, and then invoke the &lt;strong&gt;SqlGeography&lt;/strong&gt;'s Populate() instance method, passing in your object as the parameter.  The Populate() method takes care of calling the appropriate &lt;strong&gt;IGeographySink&lt;/strong&gt; methods within your class.&lt;/p&gt;
&lt;p&gt;In this case, I'm not starting with an existing &lt;strong&gt;SqlGeography&lt;/strong&gt; instance, so my code will need to call the methods of the &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; in the correct order:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart8_139B8/IGeographySink_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="386" alt="IGeographySink" width="416" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart8_139B8/IGeographySink_thumb.png" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;After EndGeography() has been invoked, the new instance is available via the ConstructedGeography property of the &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; class.  &lt;/p&gt;
&lt;p&gt;Simple enough, right?  Yeah, I'm still a little lost myself...  But, here's some code to help demonstrate what's going on!&lt;/p&gt;
&lt;p&gt;First, let's look at the ASCII data.  A single zip code's boundary might be defined as:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;      1469      -0.824662148292608E+02       0.413848583827499E+02
      -0.824602851767940E+02       0.413864290595145E+02
      -0.824610630000000E+02       0.413860590000000E+02
      -0.824685900000000E+02       0.413841470000000E+02
      -0.824686034536111E+02       0.413843846804627E+02
      -0.824605990000000E+02       0.413863160000000E+02
      -0.824602851767940E+02       0.413864290595145E+02
END&lt;/pre&gt;
&lt;style type="text/css"&gt;&lt;![CDATA[
.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }]]&gt;&lt;/style&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;The very first line happens to contain an identifier (maps to a second file that lists the actual USPS zip code).  The coordinate listed in the first line is not actually part of the boundary, but rather appears to be the population center of that area.  The actual boundary begins with the second line, and continues until you encounter the "END".  Also, in case you couldn't tell, coordinates in this data are in Longitude-Latitude order.&lt;/p&gt;
&lt;p&gt;Since a Zip Code is a polygon, and since we are working with &lt;strong&gt;SqlGeography&lt;/strong&gt;, we must be aware of ring ordering.  That is, the exterior ring of a polygon must be defined in a counter-clockwise order so that as you "walk the ring", the interior is always to your left.  If you reverse the order, then &lt;strong&gt;SqlGeography&lt;/strong&gt; assumes that you're trying to define a polygon containing the entire world &lt;em&gt;except&lt;/em&gt; for the small area inside of the polygon.&lt;/p&gt;
&lt;p&gt;Well, in this case, the order of the points of the Zip Code boundary is defined in clockwise order... so, we must be aware of this and call into the &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; in the opposite order (so the last point defined in the ASCII data is the first point used while building our new instance).  &lt;/p&gt;
&lt;p&gt;To accomplish this, I simply parse the Lat/Long coordinates as "double" types, and then push them onto a stack.  Then, I pop the stack and call into the Builder API with each point.  At the end, I obtain the new &lt;strong&gt;SqlGeography&lt;/strong&gt; instance from the ConstructedGeography property.  &lt;/p&gt;
&lt;p&gt;(Note: This is demonstrative code - some things should probably be cleaned up/refactored/error handled... You have been warned)&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; SqlGeography ParseAsGeography(&lt;span class="kwrd"&gt;string&lt;/span&gt; zipcode_points)
{
    StringReader sr = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringReader(zipcode_points);
    &lt;span class="kwrd"&gt;string&lt;/span&gt; line = sr.ReadLine();

    Stack&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;[]&amp;gt; Points = &lt;span class="kwrd"&gt;new&lt;/span&gt; Stack&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;[]&amp;gt;();

    &lt;span class="kwrd"&gt;while&lt;/span&gt; (line != &lt;span class="kwrd"&gt;null&lt;/span&gt;  &amp;amp;&amp;amp; line != &lt;span class="str"&gt;"END"&lt;/span&gt;)
    {
        &lt;span class="kwrd"&gt;if&lt;/span&gt; (line != String.Empty)
        {
            Points.Push(ParseLatLngValues(line));
        }

        line = sr.ReadLine();
    }

    &lt;span class="kwrd"&gt;return&lt;/span&gt; CreateGeography(Points);
}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[] ParseLatLngValues(&lt;span class="kwrd"&gt;string&lt;/span&gt; line)
{
    &lt;span class="rem"&gt;//      -0.838170700000000E+02       0.409367390000000E+02&lt;/span&gt;

    &lt;span class="kwrd"&gt;double&lt;/span&gt;[] ret = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[2];

    &lt;span class="kwrd"&gt;string&lt;/span&gt; lng = System.Text.RegularExpressions.Regex&lt;br /&gt;                            .Matches(line, &lt;span class="str"&gt;"\\S+"&lt;/span&gt;)[0].Value;
    &lt;span class="kwrd"&gt;string&lt;/span&gt; lat = System.Text.RegularExpressions.Regex&lt;br /&gt;                            .Matches(line, &lt;span class="str"&gt;"\\S+"&lt;/span&gt;)[1].Value;

    &lt;span class="kwrd"&gt;double&lt;/span&gt;.TryParse(lat, &lt;span class="kwrd"&gt;out&lt;/span&gt; ret[0]);
    &lt;span class="kwrd"&gt;double&lt;/span&gt;.TryParse(lng, &lt;span class="kwrd"&gt;out&lt;/span&gt; ret[1]);

    &lt;span class="kwrd"&gt;return&lt;/span&gt; ret;
}&lt;/pre&gt;
&lt;p&gt;&lt;a title="http://www.microsoft.com/downloads/details.aspx?FamilyId=089A9DAD-E2DF-43E9-9CD8-C06320520B40&amp;amp;displaylang=en" href="http://www.microsoft.com/downloads/details.aspx?FamilyId=089A9DAD-E2DF-43E9-9CD8-C06320520B40&amp;amp;displaylang=en"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; SqlGeography CreateGeography(Stack&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;[]&amp;gt; points)
{
    SqlGeographyBuilder builder = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlGeographyBuilder();
    builder.SetSrid(4326);
    builder.BeginGeography(OpenGisGeographyType.Polygon);

    &lt;span class="kwrd"&gt;double&lt;/span&gt;[] point = points.Pop();

    builder.BeginFigure(point[0], point[1]);

    &lt;span class="kwrd"&gt;while&lt;/span&gt; (points.Count &amp;gt; 0)
    {
        point = points.Pop();
        builder.AddLine(point[0], point[1]);
    }

    builder.EndFigure();
    builder.EndGeography();

    &lt;span class="kwrd"&gt;return&lt;/span&gt; builder.ConstructedGeography;
}&lt;/pre&gt;
&lt;p&gt;&lt;a href="http://www.jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;SQL Server 2008: Spatial Data, Part 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/27/sql-server-2008-spatial-data-part-2.aspx"&gt;SQL Server 2008: Spatial Data, Part 2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/28/sql-server-2008-spatial-data-part-3.aspx"&gt;SQL Server 2008: Spatial Data, Part 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;SQL Server 2008: Spatial Data, Part 4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx"&gt;SQL Server 2008: Spatial Data, Part 5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx"&gt;SQL Server 2008: Spatial Data, Part 6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx"&gt;SQL Server 2008: Spatial Data, Part 7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f06%2f23%2fsql-server-2008-spatial-data-part-8.aspx"&gt;&lt;img alt="kick it on DotNetKicks.com" border="0" src="http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f06%2f23%2fsql-server-2008-spatial-data-part-8.aspx" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/46.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx</guid>
            <pubDate>Tue, 24 Jun 2008 02:18:47 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/46.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx#feedback</comments>
            <slash:comments>9</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/46.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/46.aspx</trackback:ping>
        </item>
        <item>
            <title>Coding in SQL Server: An Evolution</title>
            <link>http://jasonfollas.com/blog/archive/2008/06/19/coding-in-sql-server-an-evolution.aspx</link>
            <description>&lt;p&gt;Tuesday at the &lt;a target="_blank" href="http://www.nwnug.com/PermaLink,guid,95b33c73-aca9-4f3f-842e-3eec9158795b.aspx"&gt;NWNUG&lt;/a&gt; meeting, &lt;a target="_blank" href="http://aspadvice.com/blogs/ssmith/"&gt;Steven Smith&lt;/a&gt; spoke on various ways to squeeze performance out of your ASP.NET applications.  This was a fantastic talk, and gave me plenty to think about (since ASP.NET is not my forte, I only consider myself to have an intermediate skillset on this topic).&lt;/p&gt;
&lt;p&gt;One suggestion that he made involved caching database writes.  That is, instead of immediately writing logging-type information to the database for every request, which is a relatively expensive operation considering the small payload size, that you could accumulate them in a short-term cache, and then perform the write operation periodically.  Fewer database calls = faster performance.&lt;/p&gt;
&lt;p&gt;In his example, he spoke of his advertisement server that might serve many impressions per second, but he doesn't want each impression to incur an expensive database write.  So, he keeps track of the activity locally, and then persists to the database every 5 seconds using a single database call containing multiple data points.&lt;/p&gt;
&lt;p&gt;The code that Steve demonstrated utilized XML to contain the data within a single block of text (read: can be passed in as a single parameter to a stored procedure):&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;ROOT&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;Activity&lt;/span&gt; &lt;span class="attr"&gt;customerId&lt;/span&gt;&lt;span class="kwrd"&gt;="ALFKI"&lt;/span&gt; &lt;span class="attr"&gt;viewCount&lt;/span&gt;&lt;span class="kwrd"&gt;="5"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;Activity&lt;/span&gt; &lt;span class="attr"&gt;customerId&lt;/span&gt;&lt;span class="kwrd"&gt;="ANATR"&lt;/span&gt; &lt;span class="attr"&gt;viewCount&lt;/span&gt;&lt;span class="kwrd"&gt;="7"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="html"&gt;ROOT&lt;/span&gt;&lt;span class="kwrd"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Now, consuming XML from T-SQL is an area that I know very well, so I cringed a little bit when Steve showed the actual stored procedure code itself:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;PROCEDURE&lt;/span&gt; dbo.BulkLogCustomerViews
  @@doc text &lt;span class="rem"&gt;-- XML Doc...&lt;/span&gt;
&lt;span class="kwrd"&gt;AS&lt;/span&gt;

&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @idoc   &lt;span class="kwrd"&gt;int&lt;/span&gt;

&lt;span class="rem"&gt;-- Create an internal representation (virtual table) of &lt;br /&gt;   the XML document...&lt;/span&gt;
&lt;span class="kwrd"&gt;EXEC&lt;/span&gt; sp_xml_preparedocument @idoc &lt;span class="kwrd"&gt;OUTPUT&lt;/span&gt;, @@doc

&lt;span class="rem"&gt;-- Perform UPDATES&lt;/span&gt;
&lt;span class="kwrd"&gt;UPDATE&lt;/span&gt; TopCustomerLog
&lt;span class="kwrd"&gt;SET&lt;/span&gt; TopCustomerLog.ViewCount = TopCustomerLog.ViewCount &lt;br /&gt;    + ox2.viewCount
&lt;span class="kwrd"&gt;FROM&lt;/span&gt; &lt;span class="kwrd"&gt;OPENXML&lt;/span&gt; (@idoc, &lt;span class="str"&gt;'/ROOT/Activity'&lt;/span&gt;,1)
          &lt;span class="kwrd"&gt;WITH&lt;/span&gt; ( [customerId]  &lt;span class="kwrd"&gt;NCHAR&lt;/span&gt;(5)
        , viewCount &lt;span class="kwrd"&gt;int&lt;/span&gt;
        ) ox2
&lt;span class="kwrd"&gt;WHERE&lt;/span&gt; TopCustomerLog.[customerId] = ox2.[customerId]
 
&lt;span class="rem"&gt;-- Perform INSERTS&lt;/span&gt;
INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; TopCustomerLog
     ( CustomerID
     , ViewCount
     )
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; [customerId]
     , viewCount
  &lt;span class="kwrd"&gt;FROM&lt;/span&gt; &lt;span class="kwrd"&gt;OPENXML&lt;/span&gt; (@idoc, &lt;span class="str"&gt;'/ROOT/Activity'&lt;/span&gt;,1)
          &lt;span class="kwrd"&gt;WITH&lt;/span&gt; ( customerId  &lt;span class="kwrd"&gt;NCHAR&lt;/span&gt;(5)
        , viewCount  &lt;span class="kwrd"&gt;int&lt;/span&gt;
        ) ox
  &lt;span class="kwrd"&gt;WHERE&lt;/span&gt; &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;EXISTS&lt;/span&gt; 
(&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; customerId &lt;span class="kwrd"&gt;FROM&lt;/span&gt; TopCustomerLog 
&lt;span class="kwrd"&gt;WHERE&lt;/span&gt; TopCustomerLog.customerId = ox.customerId)

&lt;span class="rem"&gt;-- Remove the 'virtual table' now...&lt;/span&gt;
&lt;span class="kwrd"&gt;EXEC&lt;/span&gt; sp_xml_removedocument @idoc&lt;/pre&gt;
&lt;p&gt;&lt;span class="kwrd"&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="kwrd"&gt;Now, to Steve's credit, this code works just fine, and can probably be used as-is on all versions of SQL Server from 7.0 through 2008.  But, since we really don't write ASP applications consisting entirely of Response.Write any longer, I'd like to see Steve update his demo to use more modern techniques on the database as well.  ;-)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="kwrd"&gt;The first thing that he could do is update the procedure to utilize the XML data type that was first introduced in SQL Server 2005.  This would simplify the code a little bit, and would get rid of the dependency on the COM-based MSXML.dll, which the sp_xml_preparedocument and OPENXML() uses.&lt;/span&gt;&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;PROCEDURE&lt;/span&gt; dbo.BulkLogCustomerViews
  @doc xml
&lt;span class="kwrd"&gt;AS&lt;/span&gt;

&lt;span class="rem"&gt;-- Perform UPDATES&lt;/span&gt; &lt;br /&gt;&lt;span class="kwrd"&gt;UPDATE&lt;/span&gt; TopCustomerLog
&lt;span class="kwrd"&gt;SET&lt;/span&gt;    TopCustomerLog.ViewCount = TopCustomerLog.ViewCount &lt;br /&gt;       + ox2.viewCount
&lt;span class="kwrd"&gt;FROM&lt;/span&gt;   (
       &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; T.activity.&lt;span class="kwrd"&gt;value&lt;/span&gt;(&lt;span class="str"&gt;'@customerId'&lt;/span&gt;, &lt;span class="str"&gt;'nchar(5)'&lt;/span&gt;) &lt;br /&gt;              as CustomerID, 
              T.activity.&lt;span class="kwrd"&gt;value&lt;/span&gt;(&lt;span class="str"&gt;'@viewCount'&lt;/span&gt;, &lt;span class="str"&gt;'int'&lt;/span&gt;) viewCount
       &lt;span class="kwrd"&gt;FROM&lt;/span&gt;   @doc.nodes(&lt;span class="str"&gt;'/ROOT/Activity'&lt;/span&gt;) &lt;span class="kwrd"&gt;as&lt;/span&gt; T(activity)
       ) ox2
&lt;span class="kwrd"&gt;WHERE&lt;/span&gt;  TopCustomerLog.[customerId] = ox2.[customerId]
 
&lt;span class="rem"&gt;-- Perform INSERTS&lt;/span&gt;
INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; TopCustomerLog
     ( CustomerID
     , ViewCount
     )
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; [customerId]
     , viewCount
&lt;span class="kwrd"&gt;FROM&lt;/span&gt; (
       &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; T.activity.&lt;span class="kwrd"&gt;value&lt;/span&gt;(&lt;span class="str"&gt;'@customerId'&lt;/span&gt;, &lt;span class="str"&gt;'nchar(5)'&lt;/span&gt;) &lt;br /&gt;              as CustomerID, 
              T.activity.&lt;span class="kwrd"&gt;value&lt;/span&gt;(&lt;span class="str"&gt;'@viewCount'&lt;/span&gt;, &lt;span class="str"&gt;'int'&lt;/span&gt;) viewCount
       &lt;span class="kwrd"&gt;FROM&lt;/span&gt;   @doc.nodes(&lt;span class="str"&gt;'/ROOT/Activity'&lt;/span&gt;) &lt;span class="kwrd"&gt;as&lt;/span&gt; T(activity)
       ) ox
&lt;span class="kwrd"&gt;WHERE&lt;/span&gt; &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;EXISTS&lt;/span&gt; (
    &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; customerId &lt;span class="kwrd"&gt;FROM&lt;/span&gt; TopCustomerLog 
    &lt;span class="kwrd"&gt;WHERE&lt;/span&gt; TopCustomerLog.customerId = ox.customerId )&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
Note that the XML data type in SQL Server doesn't need to be a well-formed document.  In this case, Steve could just pass in series of "Activity" elements (no "ROOT" element would be required by SQL Server, so he would also be able to simplify the .NET code that actually creates the XML string):&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;Activity&lt;/span&gt; &lt;span class="attr"&gt;customerId&lt;/span&gt;&lt;span class="kwrd"&gt;="ALFKI"&lt;/span&gt; &lt;span class="attr"&gt;viewCount&lt;/span&gt;&lt;span class="kwrd"&gt;="5"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;&amp;lt;&lt;/span&gt;&lt;span class="html"&gt;Activity&lt;/span&gt; &lt;span class="attr"&gt;customerId&lt;/span&gt;&lt;span class="kwrd"&gt;="ANATR"&lt;/span&gt; &lt;span class="attr"&gt;viewCount&lt;/span&gt;&lt;span class="kwrd"&gt;="7"&lt;/span&gt; &lt;span class="kwrd"&gt;/&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Consequently, the XPath (XQuery, actually) within the nodes() method of the stored procedure code would need to change as well: &lt;/p&gt;
&lt;pre class="csharpcode"&gt;@doc.nodes(&lt;span class="str"&gt;'Activity'&lt;/span&gt;) &lt;span class="kwrd"&gt;as&lt;/span&gt; T(activity)&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;
But, we can kick this up a notch and use some SQL Server 2008 features as well. First, there's new "Upsert" capabilities (MERGE statement) that tries to simplify what Steve does with the UPDATE followed by INSERT:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;PROCEDURE&lt;/span&gt; dbo.BulkLogCustomerViews
  @doc xml
&lt;span class="kwrd"&gt;AS&lt;/span&gt;

MERGE TopCustomerLog &lt;span class="kwrd"&gt;AS&lt;/span&gt; target
&lt;span class="kwrd"&gt;USING&lt;/span&gt; (&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; T.activity.&lt;span class="kwrd"&gt;value&lt;/span&gt;(&lt;span class="str"&gt;'@customerId'&lt;/span&gt;, &lt;span class="str"&gt;'nchar(5)'&lt;/span&gt;) 
              &lt;span class="kwrd"&gt;as&lt;/span&gt; CustomerID, 
              T.activity.&lt;span class="kwrd"&gt;value&lt;/span&gt;(&lt;span class="str"&gt;'@viewCount'&lt;/span&gt;, &lt;span class="str"&gt;'int'&lt;/span&gt;) &lt;span class="kwrd"&gt;as&lt;/span&gt; viewCount
       &lt;span class="kwrd"&gt;FROM&lt;/span&gt;   @doc.nodes(&lt;span class="str"&gt;'Activity'&lt;/span&gt;) &lt;span class="kwrd"&gt;as&lt;/span&gt; T(activity)) &lt;span class="kwrd"&gt;AS&lt;/span&gt; source 
&lt;span class="kwrd"&gt;ON&lt;/span&gt;    (target.CustomerID = source.CustomerID)
&lt;span class="kwrd"&gt;WHEN&lt;/span&gt;  MATCHED 
      &lt;span class="kwrd"&gt;THEN&lt;/span&gt; &lt;span class="kwrd"&gt;UPDATE&lt;/span&gt; &lt;span class="kwrd"&gt;SET&lt;/span&gt; target.ViewCount = target.ViewCount + source.viewCount
&lt;span class="kwrd"&gt;WHEN&lt;/span&gt;  &lt;span class="kwrd"&gt;NOT&lt;/span&gt; MATCHED
      &lt;span class="kwrd"&gt;THEN&lt;/span&gt; INSERT (CustomerID, ViewCount)
      &lt;span class="kwrd"&gt;VALUES&lt;/span&gt; (source.CustomerID, source.viewCount);&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;One more thing that could be done to further simplify this T-SQL is to use a Table-valued Parameter instead of the XML.  This would allow Steve to pass a fully populated table of data into the stored procedure and consume it directly by the MERGE statement.&lt;/p&gt;
&lt;p&gt;The first step is to create a T-SQL type that defines the table structure of the parameter (this is a one-time operation, unless the table structure changes):&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;CREATE&lt;/span&gt; TYPE CustomerViewType &lt;span class="kwrd"&gt;AS&lt;/span&gt; &lt;span class="kwrd"&gt;TABLE&lt;/span&gt; 
( 
    CustomerID &lt;span class="kwrd"&gt;nchar&lt;/span&gt;(5) &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;NULL&lt;/span&gt;,
    ViewCount &lt;span class="kwrd"&gt;int&lt;/span&gt; &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;NULL&lt;/span&gt;
);&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Now, a parameter can be defined of this type, and used just like any other table-value variable:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;ALTER&lt;/span&gt; &lt;span class="kwrd"&gt;PROCEDURE&lt;/span&gt; dbo.BulkLogCustomerViews
  @views CustomerViewType READONLY
&lt;span class="kwrd"&gt;AS&lt;/span&gt;

MERGE TopCustomerLog &lt;span class="kwrd"&gt;AS&lt;/span&gt; target
&lt;span class="kwrd"&gt;USING&lt;/span&gt; @views &lt;span class="kwrd"&gt;AS&lt;/span&gt; source 
&lt;span class="kwrd"&gt;ON&lt;/span&gt;    (target.CustomerID = source.CustomerID)
&lt;span class="kwrd"&gt;WHEN&lt;/span&gt;  MATCHED 
      &lt;span class="kwrd"&gt;THEN&lt;/span&gt; &lt;span class="kwrd"&gt;UPDATE&lt;/span&gt; &lt;span class="kwrd"&gt;SET&lt;/span&gt; target.ViewCount = target.ViewCount &lt;br /&gt;                      + source.viewCount
&lt;span class="kwrd"&gt;WHEN&lt;/span&gt;  &lt;span class="kwrd"&gt;NOT&lt;/span&gt; MATCHED
      &lt;span class="kwrd"&gt;THEN&lt;/span&gt; INSERT (CustomerID, ViewCount)
      &lt;span class="kwrd"&gt;VALUES&lt;/span&gt; (source.CustomerID, source.viewCount);&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;On the ADO.NET side, the table-valued parameter could be represented as a DataTable object (other options also exist), and can be assigned directly as the value of the stored procedure's parameter object:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="rem"&gt;// Create a data table, and provide its structure&lt;/span&gt;
DataTable customerViews = &lt;span class="kwrd"&gt;new&lt;/span&gt; DataTable();
customerViews.Columns.Add(&lt;span class="str"&gt;"CustomerID"&lt;/span&gt;, &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;string&lt;/span&gt;));
customerViews.Columns.Add(&lt;span class="str"&gt;"ViewCount"&lt;/span&gt;, &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;int&lt;/span&gt;));

&lt;span class="rem"&gt;// Fill with rows&lt;/span&gt;
customerViews.Rows.Add(&lt;span class="str"&gt;"ALFKI"&lt;/span&gt;, 5);
customerViews.Rows.Add(&lt;span class="str"&gt;"ANATR"&lt;/span&gt;, 7);

&lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlConnection conn = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlConnection(&lt;span class="str"&gt;"..."&lt;/span&gt;))
{
  SqlCommand cmd = conn.CreateCommand();
  cmd.CommandType = System.Data.CommandType.StoredProcedure;
  cmd.CommandText = &lt;span class="str"&gt;"dbo.BulkLogCustomerViews"&lt;/span&gt;;

  SqlParameter param &lt;br /&gt;        = cmd.Parameters.AddWithValue(&lt;span class="str"&gt;"@views"&lt;/span&gt;, customerViews);

  conn.Open();

  cmd.ExecuteNonQuery();
}&lt;/pre&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/45.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/06/19/coding-in-sql-server-an-evolution.aspx</guid>
            <pubDate>Thu, 19 Jun 2008 20:50:36 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/45.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/06/19/coding-in-sql-server-an-evolution.aspx#feedback</comments>
            <slash:comments>2</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/45.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/45.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL Server 2008 RC0 Install: Sql2005SsmsExpressFacet</title>
            <link>http://jasonfollas.com/blog/archive/2008/06/19/sql-server-2008-rc0-install-sql2005ssmsexpressfacet.aspx</link>
            <description>&lt;p&gt;This morning's goal was to quickly install SQL Server 2008 RC0, and then move on with some project work.  Let's just say that my project work &lt;em&gt;should&lt;/em&gt; resume by this afternoon...&lt;/p&gt;
&lt;p&gt;In the interest of disk space, I removed an existing installation of SQL Server 2005 Developer Edition.  And then the installation of 2008 RC0 began by installing the "Microsoft.NET Framework 3.5 SP1 (Beta)"...  which is probably "install-smell" for me needing to pave my machine when the product finally RTM's.  But, I digress...&lt;/p&gt;
&lt;p&gt;The installation went pretty smoothly until it came time for the "System Configuration Check" that takes place after you select everything that you would like to install, but before the files actually get installed.  In my case, this check failed because "The SQL Server 2005 Express Tools are installed.  To continue, remove the SQL Server 2005 Express Tools."  (This is the "Sql2005SsmsExpressFacet" rule of the installation)&lt;/p&gt;
&lt;p&gt;Thank you, Microsoft, for that succinct failure message that includes instructions for resolution...  Except, I didn't have the SQL Server 2005 Express Tools installed.  They didn't show up in my Programs list, in the Start menu, or on my C: drive at all.  How am I to uninstall something that isn't installed?  Hrmmm....&lt;/p&gt;
&lt;p&gt;After about an hour's search around my hard drive, I finally went into the registry, and discovered the following key:&lt;/p&gt;
&lt;p&gt;HKLM\Software\Microsoft\Microsoft SQL Server\90\Tools\ShellSEM&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: &lt;/em&gt;&lt;a target="_blank" href="http://weblog.rebex.cz/blogs/honzas"&gt;&lt;em&gt;Jan Sotola&lt;/em&gt;&lt;/a&gt;&lt;em&gt; reports that the affected 64-bit version key is:&lt;br /&gt;
&lt;/em&gt;&lt;font face="Arial"&gt;&lt;em&gt;HKLM\Software\&lt;strong&gt;Wow6432Node&lt;/strong&gt;\Microsoft\...&lt;br /&gt;
   ...\Microsoft SQL Server\90\Tools\ShellSEM&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;Contained within was some registry information belonging to Red Gate SQL Prompt.  Apparently, despite my removing of the SQL 2005 Express Tools some time ago, this registry key was not removed because the Red Gate information was still there.&lt;/p&gt;
&lt;p&gt;On a hunch, I renamed the key to "ShellSEM.old", and the SQL Server 2008 installation carried on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; Shortly after posting this, Theo Spears from Red Gate sent the following email:&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;blockquote&gt;&lt;font face="Arial"&gt;&lt;em&gt;"I apologise for this issue; the SQL Prompt team here has been working to address it. You and your readers may be interested to hear that we now have a version which works with SQL Server 2008 RC0, and no longer blocks the installation. To get a copy send us an email at support@red-gate.com"&lt;/em&gt;&lt;/font&gt;&lt;/blockquote&gt;
&lt;p&gt;I should clarify that my little rant above was not targeted at Red Gate, but I'm so happy to hear that they are proactively working to resolve this little issue.  I would have just liked for Microsoft to use more than a single registry key as evidence of a conflicting product installation, that's all.&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/44.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/06/19/sql-server-2008-rc0-install-sql2005ssmsexpressfacet.aspx</guid>
            <pubDate>Thu, 19 Jun 2008 15:02:43 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/44.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/06/19/sql-server-2008-rc0-install-sql2005ssmsexpressfacet.aspx#feedback</comments>
            <slash:comments>21</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/44.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/44.aspx</trackback:ping>
        </item>
        <item>
            <title>Software Development Meme</title>
            <link>http://jasonfollas.com/blog/archive/2008/06/08/software-development-meme.aspx</link>
            <description>&lt;p&gt;&lt;font face="Arial"&gt;This &lt;a target="_blank" href="http://michaeleatonconsulting.com/blog/archive/2008/06/04/how-did-you-get-started-in-software-development.aspx"&gt;meme&lt;/a&gt; is brought to you by &lt;a target="_blank" href="http://www.michaeleatonconsulting.com/"&gt;Michael Eaton&lt;/a&gt;, who just so happens to be &lt;a target="_blank" href="http://twitter.com/mjeaton/statuses/829737455"&gt;a huge fan of my spatial talks&lt;/a&gt;.  ;-)&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;strong&gt;How old were you when you started programming? &lt;br /&gt;
&lt;/strong&gt;Well, we might need to better define "programming" here.  &lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;I started tinkering with computers when I was about 6 or 7 (is that right, Dad?).  In those days, it was more about reading a book about BASIC and typing in the program to run.  Then I would make changes to the program to see the results.  If modifying an existing program is considered "programming", then this is my epoch.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;I really didn't start creating my own programs from scratch until I was a little older, maybe 8 or 9.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How did you get started in programming?&lt;/strong&gt; &lt;br /&gt;
My father would give me and my siblings time on the computer (TRS-80s), which I spent going through books about BASIC.  I wanted to make the machine do something rather than just use the machine to do something, like play a game.  (Don't get me wrong, though: I spent &lt;em&gt;plenty&lt;/em&gt; of time playing games, too)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What was your first language?&lt;/strong&gt; &lt;br /&gt;
BASIC&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What was the first real program you wrote?&lt;br /&gt;
&lt;/strong&gt;I don't remember the small ones.  But an early one that sticks out in my mind was a "catch the falling egg in a basket" game, where a chicken would move randomly across the top of the screen, drop an egg, which you then had to catch in a basket at the bottom of the screen that you controlled using a joystick.  I think that was on my Commodore 64, so I had to be maybe 10 years old (side note: it's amazing how hard it is to pinpoint when these events take place as the years pile on).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What languages have you used since you started programming?&lt;br /&gt;
&lt;/strong&gt;BASIC, Visual Basic, C/C++. C#, SQL, Java, JavaScript, LUA&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What was your first professional programming gig?&lt;br /&gt;
&lt;/strong&gt;I was a PC Technicial (and, later correctly changed to Programmer/Analyst) in the IT department at the BAX Global Hub facility.  There I helped maintain a variety of home-grown systems, and created some of my own (like the Automated Load Planning System that was used to assign where freight should be placed on aircraft for optimal center of gravity).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you knew then what you know now, would you have started programming?&lt;br /&gt;
&lt;/strong&gt;I don't see why not.  I really haven't been jaded in my career, or anything, and it still holds my interest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If there is one thing you learned along the way that you would tell new developers, what would it be?&lt;br /&gt;
&lt;/strong&gt;Choose a technology that you are passionate about and become an expert in it... and then evangelize that technology to share your passion.  There is &lt;em&gt;so much&lt;/em&gt; stuff out there that nobody can be an expert in everything.  Developers that are only able to do a little bit of everything are a commodity resource that can be replaced by cheaper labor at any time.  However, if you have exceptional expertise in one or more select areas, then you become that much more valuable to &lt;em&gt;somebody&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What's the most fun you've ever had ... programming?&lt;br /&gt;
&lt;/strong&gt;I don't know that the actual task of programming has ever been "fun" for me, in the same sense that riding a roller coaster would be.  But, I've done some quirky things while programming just to have fun and be different.  Like implement a &lt;a target="_blank" href="http://www.grokdev.com/Blogs/scott/2005/10/26/ConnectedSystemsDeveloperCompetitionFinalistsAnnounced.aspx"&gt;Z-Machine emulator in SQLCLR&lt;/a&gt; (as my submission to a programming competition) so that I can play Zork using only stored procedure calls...  Or use data from the WoW Armory site to demonstrate how to use SQL Server's XML functionality...  Or write Project Euler solutions in LUA and execute them in-game using WoW.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;/strong&gt; &lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/43.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/06/08/software-development-meme.aspx</guid>
            <pubDate>Sun, 08 Jun 2008 14:56:43 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/43.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/06/08/software-development-meme.aspx#feedback</comments>
            <slash:comments>1</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/43.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/43.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL Server 2008: Spatial Data, Part 7</title>
            <link>http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx</link>
            <description>&lt;p&gt;The &lt;a target="_blank" href="http://www.opengeospatial.org"&gt;Open Geospatial Consortium&lt;/a&gt;'s &lt;a target="_blank" href="http://www.opengeospatial.org/standards/sfa"&gt;Simple Features specification&lt;/a&gt;, which SQL Server 2008's &lt;strong&gt;Geometry&lt;/strong&gt; data type is based upon, defines standards for working with spatial data using a flat-earth (projected planar) model.  Ironically, these standards don't exactly cover the intricacies of using an ellipsoidal model, which is needed to "accurately" represent the world that we live in.  In other words, the OGC standards define how to work with paper maps of the world, but not globes.&lt;/p&gt;
&lt;p&gt;Fortunately, the SQL Server team recognized that that the &lt;strong&gt;Geometry&lt;/strong&gt; type is inadequate for a lot of scenarios, and implemented a second data type just for representing geospatial data using a true ellipsoidal model: &lt;strong&gt;Geography&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this, the seventh part of a series about the SQL Server Spatial Data Type, I'll examine some of the key differences between the &lt;strong&gt;Geometry&lt;/strong&gt; and the &lt;strong&gt;Geography&lt;/strong&gt; type that developers should be aware of.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Latitude and Longitude&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Locations on a flat model are defined in terms of X and Y.  There exists some point known as the Origin where X and Y are both zero.  From there, it is defined that values of X will increase (or decrease, in the case of negatve numbers) if you move horizontally away from the Origin.  Likewise, the values of Y will increase if you move vertically away from the Origin.  &lt;/p&gt;
&lt;p&gt;By convention, both X and Y will grow to infinity, so flat models do not "wrap around" and start approaching the Origin again if you go too far in one direction.  Usually, a coordinate system will be based on some underlying representation of the real-world, so coordinates that are beyond the defined boundaries of that map are logically undefined.&lt;/p&gt;
&lt;p&gt;By contrast, though, an ellipsoidal model &lt;em&gt;does&lt;/em&gt; wrap around.  If you started at a point in the middle and kept traveling in a straight line to the right, you will eventually return to that starting point.  &lt;/p&gt;
&lt;p&gt;So, it turns out not to be very practical to define points on a ball using X and Y.  Instead, points are defined using angles.  Longitude is the horizontal angle (how far East or West from a &lt;a target="_blank" href="http://en.wikipedia.org/wiki/Prime_meridian"&gt;Prime Meridian&lt;/a&gt;) and ranges from -180 degrees to 180 degrees (with -180 and 180 being the same).  Latitude is the vertical angle (how far North or South from the Equator) and ranges from -90 degrees to 90 degrees (with -90 representing the South Pole and 90 representing the North Pole).&lt;/p&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="240" alt="latlon" width="229" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/latlon_3.jpg" /&gt; &lt;/p&gt;
&lt;p&gt;In terms of the &lt;strong&gt;Geography&lt;/strong&gt; data type, just be aware that there is no X and Y.  Instead, you work with Long and Lat.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: All of the SQL Server 2008 CTPs to date that include Spatial support, including the most recent February 2008 version, use Lat-Long ordering within WKT.  This was a design decision based on the fact that the OGC standard did not already define parameter ordering for angular coordinates.  Starting with the first Release Candidate, however, these parameters will be swapped to use Long-Lat ordering.  Doing so will align SQL Server's spatial support with other platforms that have already implemented Long-Lat ordering.  Note also that that this is aligned with the concepts of X and Y, which by convention lists the X value first.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Straight Lines&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
The shortest distance between two points is a straight line.  But, a straight line on a flat-earth model is far different than a straight line on an ellipsoidal model.  To demonstrate, consider the shortest path from Redmond, WA, USA to Cambridge, England, UK: &lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_1_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="290" alt="s7_1" width="380" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_1_thumb.png" /&gt;&lt;/a&gt;  &lt;br /&gt;
On this planar projection, it certainly looks like the shortest path.  Even when examined on a 3D model, it looks correct: &lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_2_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="274" alt="s7_2" width="364" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_2_thumb.png" /&gt;&lt;/a&gt; &lt;br /&gt;
But, if the camera is moved towards the North Pole, then the error becomes apparent:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_3_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="274" alt="s7_3" width="364" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_3_thumb.png" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;In the ellipsoidal model, the shortest path between the points is not the red line, which roughly parallels the lines of Latitude, but rather the black arrow!  Converted back to a planar projection, this actual shortest path appears curved:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_4_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="290" alt="s7_4" width="380" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_4_thumb.png" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;em&gt;(in this view, the black curve was [hastily] plotted by hand)&lt;/em&gt; &lt;br /&gt;
&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
&lt;/p&gt;
&lt;h2&gt;Instance Methods&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
The following slide shows in all of the instance methods that have been implemented for the &lt;strong&gt;Geography&lt;/strong&gt; type as of the February 2008 CTP.  For comparison, instance methods from the &lt;strong&gt;Geometry&lt;/strong&gt; type that do not exist in the &lt;strong&gt;Geography&lt;/strong&gt; type are shown in gray.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/GeographyMethods_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="260" alt="GeographyMethods" width="340" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/GeographyMethods_thumb.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It may be impossible to define some of these equivalent methods for &lt;strong&gt;Geography&lt;/strong&gt;, simply because the rules are different.  For instance, if you define a set of points that make up a Polygon, what is considered to be the interior and exterior of that shape?  Since the world coordinates wrap around in an ellipsoidal model, you might be intending to represent a shape whose interior is the entire world except for the small portion.  There is simply no way to convey your intent using the methods as described by the OGC standard.&lt;/p&gt;
&lt;p&gt;In an attempt to prevent this particular scenario, the SQL Server team has imposed a limit on the size of a &lt;strong&gt;Geography&lt;/strong&gt; in the February 2008 CTP: you cannot define a &lt;strong&gt;Geography&lt;/strong&gt; that is larger than a hemisphere.   &lt;/p&gt;
&lt;p&gt;There may very well be logical solutions for working around some of the issues that prevented the SQL team from implementing all of &lt;strong&gt;Geometry's&lt;/strong&gt; methods in the &lt;strong&gt;Geography&lt;/strong&gt; type.  However, in this case, Microsoft appears to be waiting for the OGC to define certain rules as part of a standard rather than coming up with their own assumptions, which could be invalidated later by the standards group going in a different direction.&lt;/p&gt;
&lt;p&gt;More on the &lt;strong&gt;Geography&lt;/strong&gt; type later!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;SQL Server 2008: Spatial Data, Part 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/27/sql-server-2008-spatial-data-part-2.aspx"&gt;SQL Server 2008: Spatial Data, Part 2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/28/sql-server-2008-spatial-data-part-3.aspx"&gt;SQL Server 2008: Spatial Data, Part 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;SQL Server 2008: Spatial Data, Part 4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx"&gt;SQL Server 2008: Spatial Data, Part 5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx"&gt;SQL Server 2008: Spatial Data, Part 6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx"&gt;(next part) SQL Server 2008: Spatial Data, Part 8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f05%2f16%2fsql-server-2008-spatial-data-part-7.aspx"&gt;&lt;img alt="kick it on DotNetKicks.com" border="0" src="http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f05%2f16%2fsql-server-2008-spatial-data-part-7.aspx" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/42.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx</guid>
            <pubDate>Sat, 17 May 2008 02:53:41 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/42.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx#feedback</comments>
            <slash:comments>3</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/42.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/42.aspx</trackback:ping>
        </item>
        <item>
            <title>Project Euler Comes to Azeroth</title>
            <link>http://jasonfollas.com/blog/archive/2008/04/24/project-euler-comes-to-azeroth.aspx</link>
            <description>&lt;p&gt;It seems that a lot of my friends are doing &lt;a target="_blank" href="http://projecteuler.net/index.php"&gt;Project Euler&lt;/a&gt; (according to my High School math teacher, this is pronounced "Oiler").  For example, &lt;a target="_blank" href="http://srtsolutions.com/blogs/billwagner/default.aspx"&gt;Bill Wagner&lt;/a&gt; has been posting C# solutions, &lt;a target="_blank" href="http://www.darrellhawley.com/"&gt;Darrell Hawley&lt;/a&gt; has ventured into the Python realm, and &lt;a target="_blank" href="http://www.diditwith.net/"&gt;Dustin Campbell&lt;/a&gt; has been working on F# versions.&lt;/p&gt;
&lt;p&gt;I love numbers, and spent a good portion of one summer playing with primes and number fields just for fun (since then, I've discovered WoW, and that takes up all of my time that would otherwise be spent exercising my brain).  Project Euler is actually right up my alley, and while in Seattle, I joked with Dustin that I should post solutions using LUA, and use World of Warcraft as my testbed...  &lt;/p&gt;
&lt;p&gt;Problem 1 is finished.  :-D&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/ProjectEulerComestoAzeroth_13025/WoWScrnShot_042408_205532_2.jpg"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="196" alt="WoWScrnShot_042408_205532" width="244" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/ProjectEulerComestoAzeroth_13025/WoWScrnShot_042408_205532_thumb.jpg" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;My no-frills WoW add-on is a simple ACE2 mod that includes AceConsole (for printing to the chat window in the lower left of the screenshot).  I won't bore you with the framework code, but will list my solutions as individual functions on my &lt;a target="_blank" href="http://jasonfollas.com/wiki/ProjectEulerInLua.ashx"&gt;wiki&lt;/a&gt; (where it can grow without polluting my blog's RSS feed).  &lt;/p&gt;
&lt;p&gt;As a taste, here's the Problem 1 solution written in LUA.  OnEnable() is my add-on's entry point, and it simply calls into the function Problem001(limit).&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;function&lt;/span&gt; ProjectEuler:OnEnable()
    ProjectEuler:Problem001(1000)
&lt;font color="#0000ff"&gt;end&lt;/font&gt;&lt;/pre&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;function&lt;/span&gt; ProjectEuler:Problem001(upperLimit)&lt;br /&gt;
        self:Print(&lt;span class="str"&gt;"Sum of all numbers less than "&lt;/span&gt; 
            .. upperLimit .. &lt;span class="str"&gt;" that are divisible by 3 or 5"&lt;/span&gt;)
        
        &lt;span class="kwrd"&gt;local&lt;/span&gt; f = &lt;span class="kwrd"&gt;function&lt;/span&gt;(factor)
            &lt;span class="kwrd"&gt;local&lt;/span&gt; n = math.ceil(upperLimit / factor)
            &lt;span class="kwrd"&gt;return&lt;/span&gt; n * (n-1) * factor / 2
        &lt;span class="kwrd"&gt;end&lt;/span&gt;
    
        &lt;span class="kwrd"&gt;local&lt;/span&gt; result = f(3) + f(5) - f(15)

        self:Print(result)
&lt;font color="#0000ff"&gt;end&lt;/font&gt;&lt;/pre&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/40.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/04/24/project-euler-comes-to-azeroth.aspx</guid>
            <pubDate>Fri, 25 Apr 2008 01:38:03 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/40.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/04/24/project-euler-comes-to-azeroth.aspx#feedback</comments>
            <slash:comments>1</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/40.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/40.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL Server 2008: Spatial Data, Part 6</title>
            <link>http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx</link>
            <description>&lt;p&gt; &lt;/p&gt;
&lt;p&gt;In the &lt;a target="_blank" href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;Part 4&lt;/a&gt; and &lt;a target="_blank" href="http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx"&gt;Part 5&lt;/a&gt; of the series, I demonstrated some instance methods of the &lt;strong&gt;Geometry&lt;/strong&gt; type that returned a new &lt;strong&gt;Geometry&lt;/strong&gt; based on existing instances.  In this part, I will concentrate on instance methods and properties of the &lt;strong&gt;Geometry&lt;/strong&gt; type that return scalar values and Points. &lt;br /&gt;
&lt;/p&gt;
&lt;h2&gt;STArea, STLength&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Typically, your spatial data will represent something from the real world.  A LineString may be the collection of points gathered from a GPS device, and together they may represent the path that you took from your home to the office.  A Polygon may be the collection of points around the boundary of governmental territory, like a county or a parish within your state.&lt;/p&gt;
&lt;p&gt;In both of these cases, the time will come when you will want to know the length of the LineString (or length of the perimeter of the Polygon) and the area within the Polygon.  OGC standard method &lt;font face="monospace"&gt;STArea()&lt;/font&gt; returns a float indicating the area of the instance in square units (or 0, if the instance is not a Polygon and does not have area).  &lt;font face="monospace"&gt;STLength()&lt;/font&gt; returns a float indicating the length of the instance in units (or 0, if the instance is a Point and does not have length).  &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g GEOMETRY = &lt;span class="str"&gt;'POLYGON((10 10, 10 40, 40 40, 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STArea(), @g.STLength()

Results:

Area       Length&lt;br /&gt;450        102.426406871193&lt;/pre&gt;
&lt;p&gt; &lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="52" alt="Spatial_6_1" width="57" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_1_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STCentroid&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Thinking back to Mr. Bollenbacher's 10th Grade Geometry class, we had to use a compass and straight edge to construct lines bisecting the angles of polygons (primarily triangles).  The point where the angle bisectors met was the exact center, or &lt;a target="_blank" href="http://en.wikipedia.org/wiki/Centroid"&gt;centroid&lt;/a&gt;, of the shape.  Centroids are important because any line that passes through a centroid will divide the Polygon into two parts of equal area.  It should be noted that a Centroid may not actually be on the surface of a Polygon.&lt;/p&gt;
&lt;p&gt;The OGC standard method &lt;font face="monospace"&gt;STCentroid()&lt;/font&gt; returns a Point indicating the centroid of the shape.  If the instance is not a Polygon (or MultiPolygon), then NULL will be returned.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g GEOMETRY = &lt;span class="str"&gt;'POLYGON((10 10, 10 40, 40 40, 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STCentroid().ToString()

Results:

POINT (20 30)&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="52" alt="Spatial_6_2" width="57" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_2_3.png" /&gt;  &lt;br /&gt;
&lt;em&gt;Note: SpatialViewer displays an individual point as an X. &lt;br /&gt;
&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;STWithin, STContains&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Two OGC standard methods returns a 1 or 0 indicating whether all of the points of one instance exist entirely inside of another instance.  &lt;font face="monospace"&gt;STWithin()&lt;/font&gt; tests whether the base instance is inside of the parameter instance, while &lt;font face="monospace"&gt;STContains()&lt;/font&gt; tests whether the parameter instance is inside of the base instance.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g geometry = &lt;span class="str"&gt;'POLYGON ((10 10, 13 30, 30 30, 30 15, &lt;br /&gt;                                 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @h geometry = &lt;span class="str"&gt;'LINESTRING (16 16, 16 24, 25 18)'&lt;br /&gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STContains(@h), @g.STWithin(@h)&lt;br /&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @h.STContains(@g), @h.STWithin(@g) &lt;br /&gt;
Results:

1     0&lt;br /&gt;&lt;br /&gt;0     1&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="41" alt="Spatial_6_3" width="51" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_3_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;ST is {something}?&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
There are a number of OGC standard methods to check whether a given instance meets certain specifications: STIsClosed, STIsEmpty, STIsRing, STIsSimple, STIsValid&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CLOSED:&lt;/strong&gt; An instance is considered to be closed if the start point is the same as the end point.  By definition, a Polygon has to be closed, and a Point is not closed.  That really only leaves LineString.  For a collection of objects to be considered closed, all of its members must be closed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EMPTY:&lt;/strong&gt; A &lt;strong&gt;Geometry&lt;/strong&gt; can be initialized in a special way as to not contain any points.  In SQL terms, this is sort of like having a NULL value, except it really is an instantiated object.  For example, LINESTRING EMPTY is a valid LineString, but it has no points.  Another humorous example is POINT EMPTY, which initializes to a Point without a Point....  so it's kind of Pointless, right?  (thank you, I'm here all week, tip your waitress).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RING:&lt;/strong&gt; An instance is considered to be a ring if it is both Closed and Simple.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SIMPLE:&lt;/strong&gt; An instance is considered to be simple if it does not cross over itself or otherwise touch itself.  For example, a LineString forming the letter 'S' is simple because it never comes in contact with itself.  But, a LineString that forms a Figure-Eight (8) is &lt;u&gt;not&lt;/u&gt; simple because it would have to cross over itself.  Likewise, two circles (MultiPolygon) stacked on top of each other to form a Figure-Eight would &lt;u&gt;not&lt;/u&gt; be simple because they touch each other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VALID:&lt;/strong&gt; A Geometry can cross over itself, but it cannot legally trace over itself.  That is, picture a LineString that backtracks over itself at some point, kind of like how I write my letter "P".  This is not considered to be Valid.&lt;/p&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="41" alt="Spatial_6_4" width="51" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_4_3.png" /&gt; &lt;/p&gt;
&lt;p&gt;Tip: SQL Server will allow an invalid &lt;strong&gt;Geometry&lt;/strong&gt; to be instantiated, and Microsoft has provided an extension method called &lt;font face="monospace"&gt;MakeValid()&lt;/font&gt; that will convert the invalid instance into a valid instance.  In the letter "P" example, instead of the vertical line going down and then back up (as I draw it by hand), the valid form will eliminate the duplication of points simply by start at the bottom and going up (so that the LineString never traces over itself).  If it's not possible to simplify a shape in this way so that there is only one continuous path, then it will be broken up into multiple valid shapes (i.e., a MultiLineString, etc).&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STX, STY, Z, M&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Individual coordinates of a Point can be accessed via the OGC Standard properties STX and STY.  Three-dimensional Points also have a Z coordinate, which can be accessed via Microsoft's extended Z property.  Likewise, four-dimensional Points have a M (for Measure) coordinate, which can be accessed via Microsoft's extended M property.  If Z or M is not defined for a given point, then NULL will be returned.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g geometry = &lt;span class="str"&gt;'POINT(1 2)'&lt;/span&gt;
&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @h geometry = &lt;span class="str"&gt;'POINT(1 2 3 4)'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STX, @g.STY, @g.Z, @g.M
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @h.STX, @h.STY, @h.Z, @h.M

Results:

1    2    &lt;span class="kwrd"&gt;NULL&lt;/span&gt;    &lt;span class="kwrd"&gt;NULL&lt;/span&gt;
1    2    3       4&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STPointOnSurface&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
When working with spatial data, especially without using a viewer, it can be kind of difficult to pick an arbitrary point that is inside of a Polygon (or on a LineString).  Thankfully, the OGC standard method &lt;font face="monospace"&gt;STPointOnSurface()&lt;/font&gt; does just that.  Given a &lt;strong&gt;Geometry&lt;/strong&gt; instance, it will return a somewhat random point that is guaranteed to be located within the interior of that instance.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g geometry = &lt;span class="str"&gt;'POLYGON((10 10, 14 15, 50 12, 45 30, &lt;br /&gt;                                10 30, 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STPointOnSurface().ToString()

Results:

POINT (23 25)&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="141" alt="Spatial_6_5" width="208" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_5_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STSrid&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
All of my examples to this point have used the default Spatial Reference ID of 0 (for the &lt;strong&gt;Geometry&lt;/strong&gt; type) simply because I have not been specifying one.  The SRID is the mechanism that defines one geometry as being based on a different set of parameters than a geometry with a different SRID.  &lt;/p&gt;
&lt;p&gt;For example, you may have a set of shapes defined where each unit represents one meter, while another set of shapes is based on a reference system where each unit represents 1.5 inches.  It's totally legal to mix these shapes together the same column of a table in your database, provided that you assign a different SRID to each.  SQL Server does not need to know what units represent, because it will never permit the interaction of a shape from one SRID with a shape from another SRID.  &lt;/p&gt;
&lt;p&gt;The OGC standard property STSrid will get (or set) the SRID of the Geometry instance.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="rem"&gt;-- @g will have the default SRID = 0&lt;br /&gt;&lt;/span&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g GEOMETRY = &lt;span class="str"&gt;'POLYGON((10 10, 10 40, 40 40, 10 10))'&lt;/span&gt;

&lt;span class="rem"&gt;-- @h is defined with SRID = 123&lt;br /&gt;&lt;/span&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @h GEOMETRY
    = GEOMETRY::STGeomFromText(&lt;span class="str"&gt;'POLYGON((10 10, 40 10, 
                                         40 40, 10 10))'&lt;/span&gt;, 
                               123)

&lt;span class="kwrd"&gt;select&lt;/span&gt; @g.STUnion(@h).ToString()

&lt;span class="rem"&gt;-- Returns NULL because of different SRIDs.  But, let's change&lt;br /&gt;&lt;br /&gt;-- @g to use SRID = 123&lt;/span&gt;

&lt;span class="kwrd"&gt;SET&lt;/span&gt; @g.STSrid = 123

&lt;span class="kwrd"&gt;select&lt;/span&gt; @g.STUnion(@h).ToString()

&lt;span class="rem"&gt;-- Returns POLYGON ((10 10, 40 10, 40 40, 10 40, 10 10))&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Jason, What's Next?&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Enough of this flat Earth stuff!  In the next part, I'll explore the Geography data type.  This is where things really start to get interesting.  Stay tuned!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;SQL Server 2008: Spatial Data, Part 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/27/sql-server-2008-spatial-data-part-2.aspx"&gt;SQL Server 2008: Spatial Data, Part 2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/28/sql-server-2008-spatial-data-part-3.aspx"&gt;SQL Server 2008: Spatial Data, Part 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;SQL Server 2008: Spatial Data, Part 4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx"&gt;SQL Server 2008: Spatial Data, Part 5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx"&gt;(next part) SQL Server 2008: Spatial Data, Part 7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx"&gt;SQL Server 2008: Spatial Data, Part 8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f04%2f11%2fsql-server-2008-spatial-data-part-6.aspx"&gt;&lt;img alt="kick it on DotNetKicks.com" border="0" src="http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f04%2f11%2fsql-server-2008-spatial-data-part-6.aspx" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/39.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx</guid>
            <pubDate>Sat, 12 Apr 2008 00:44:19 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/39.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx#feedback</comments>
            <slash:comments>3</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/39.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/39.aspx</trackback:ping>
        </item>
        <item>
            <title>Using PIVOT and RANK Together</title>
            <link>http://jasonfollas.com/blog/archive/2008/04/10/using-pivot-and-rank-together.aspx</link>
            <description>&lt;p&gt;&lt;a target="_blank" href="http://www.michaeleatonconsulting.com/blog/"&gt;A friend of mine&lt;/a&gt; (name withheld, I didn't actually ask if I could blog this... ;-) asked for advice to what appears to be a simple problem until you try to implement it.  Consider the following somewhat normalized table:&lt;/p&gt;
&lt;table cellspacing="0" cellpadding="2" width="400" border="1"&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;th valign="top" width="133"&gt;
            &lt;p&gt;AccountNum&lt;/p&gt;
            &lt;/th&gt;
            &lt;th valign="top" width="133"&gt;
            &lt;p&gt;Name&lt;/p&gt;
            &lt;/th&gt;
            &lt;th valign="top" width="133"&gt;
            &lt;p&gt;Email&lt;/p&gt;
            &lt;/th&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;0851774002  &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;John Doe   &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;jd@foo.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;0851774003    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;John Doe    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;jd@foo.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;0851774001    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;John Doe    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;jd@foo.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;0851774100    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;John Doe    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;jd@foo.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;0851693000    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;Bob Public    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;bob@bar.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;1138299000    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;Jane Doe    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;JaneD@baz.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;1353452000    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;Jane Doe    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;JaneD@baz.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;1028030000    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;Jane Doe  &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;JaneD@baz.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;0851636000    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;Jane Doe    &lt;/p&gt;
            &lt;/td&gt;
            &lt;td valign="top" width="133"&gt;
            &lt;p&gt;JaneD@baz.com&lt;/p&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;br /&gt;
What he wanted was to collapse the data to one row per person, with a column for each Account Number.  That is, he needed to pivot the table.&lt;/p&gt;
&lt;p&gt;When you pivot a table, unique values in the source column that you pivot on become new columns in the resulting table.  So, in this case, it would not make sense to pivot on the AccountNum column, because the result would be a new column named [0851774002], another one named [0851774003], etc.&lt;/p&gt;
&lt;p&gt;Instead, an intermediate step needed to be performed that introduced a value that &lt;em&gt;could&lt;/em&gt; be pivoted on.  This value needed to be consistent across the individual people (so that the first record for everybody contained the same value in this new column, the second record for everybody contained the same value, etc).&lt;/p&gt;
&lt;p&gt;SQL Server 2005 introduced Ranking functions that provide the ability to rank a record within a partition.  In this case, we can use RANK() to assign a unique number for each record, and partition by the person's name (so that the RANK will reset for each person).  By prefixing some text to the rank number, we end up with something like:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt;    Name,
          Email,
          AccountNum,
          &lt;span class="str"&gt;'AccountNum'&lt;/span&gt; +
          &lt;span class="kwrd"&gt;CAST&lt;/span&gt;(&lt;br /&gt;
               &lt;font size="3"&gt;&lt;strong&gt;RANK() &lt;span class="kwrd"&gt;OVER&lt;/span&gt; 
               ( PARTITION &lt;span class="kwrd"&gt;BY&lt;/span&gt; Name, Email 
                 &lt;span class="kwrd"&gt;ORDER&lt;/span&gt; &lt;span class="kwrd"&gt;BY&lt;/span&gt; AccountNum )&lt;/strong&gt;&lt;/font&gt; &lt;br /&gt;
          &lt;span class="kwrd"&gt;AS&lt;/span&gt; &lt;span class="kwrd"&gt;VARCHAR&lt;/span&gt;(10)) R
          &lt;span class="kwrd"&gt;FROM&lt;/span&gt;     myTable
Results:

Name        Email          AccountNum  R
=========== ============== ==========  ============
Bob &lt;span class="kwrd"&gt;&lt;font color="#000000"&gt;Public&lt;/font&gt;&lt;/span&gt;  bob@bar.com    0851693000  AccountNum1
Jane Doe    JaneD@baz.com  0851636000  AccountNum1
Jane Doe    JaneD@baz.com  1028030000  AccountNum2
Jane Doe    JaneD@baz.com  1138299000  AccountNum3
Jane Doe    JaneD@baz.com  1353452000  AccountNum4
John Doe    jd@foo.com     0851774001  AccountNum1
John Doe    jd@foo.com     0851774002  AccountNum2
John Doe    jd@foo.com     0851774003  AccountNum3
John Doe    jd@foo.com     0851774100  AccountNum4&lt;/pre&gt;
&lt;pre class="csharpcode"&gt; &lt;/pre&gt;
&lt;p&gt;The new column (R) is the concatenation of the literal string "AccountNum" and the string representation of the number that the RANK function returned.  But the bigger point is that now this column can be used for pivoting, and result in a series of new columns called [AccountNum1], [AccountNum2], [AccountNum3], etc.&lt;/p&gt;
&lt;p&gt;Pivoting in SQL Server 2005 requires explicit declaration of values as a column list.  In this case, we can't just say "Pivot on the R column", but rather must say "Pivot on the R column, and make new columns only for these specific values".  This restriction is a little bit of a downside because we need knowledge of the values in the column.  Or, in this case, we need to know how many possible Account Numbers a person could possibly have so that we create enough columns in the result.&lt;/p&gt;
&lt;p&gt;The entire solution is as follows:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt;  *
&lt;span class="kwrd"&gt;FROM&lt;/span&gt;    ( &lt;span class="kwrd"&gt;SELECT&lt;/span&gt;    Name,
                    Email,
                    AccountNum,
                    &lt;span class="str"&gt;'AccountNum'&lt;/span&gt;
                    + &lt;span class="kwrd"&gt;CAST&lt;/span&gt;(RANK() 
          &lt;span class="kwrd"&gt;OVER&lt;/span&gt; ( PARTITION &lt;span class="kwrd"&gt;BY&lt;/span&gt; Name, Email 
                 &lt;span class="kwrd"&gt;ORDER&lt;/span&gt; &lt;span class="kwrd"&gt;BY&lt;/span&gt; AccountNum ) &lt;span class="kwrd"&gt;AS&lt;/span&gt; &lt;span class="kwrd"&gt;VARCHAR&lt;/span&gt;(10)) R
          &lt;span class="kwrd"&gt;FROM&lt;/span&gt;      myTable
        ) &lt;span class="kwrd"&gt;AS&lt;/span&gt; rankedSource 
PIVOT 
( 
    &lt;span class="kwrd"&gt;MAX&lt;/span&gt;(AccountNum) 
    &lt;span class="kwrd"&gt;FOR&lt;/span&gt; R &lt;span class="kwrd"&gt;IN&lt;/span&gt; 
    ( [AccountNum1], [AccountNum2], [AccountNum3],
      [AccountNum4], [AccountNum5], [AccountNum6],
      [AccountNum7], [AccountNum8], [AccountNum9],
      [AccountNum10] ) 
) &lt;span class="kwrd"&gt;AS&lt;/span&gt; pivottable&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;And the results (showing only two of the AccountNum columns, even though there are actually 10)&lt;/p&gt;
&lt;pre class="csharpcode"&gt;Name       Email         AccountNum1  AccountNum2&lt;br /&gt;========== ============= ===========  ===========
Bob &lt;span class="kwrd"&gt;&lt;font color="#000000"&gt;Public&lt;/font&gt;&lt;/span&gt; bob@bar.com   0851693000   &lt;span class="kwrd"&gt;NULL&lt;/span&gt;
Jane Doe   JaneD@baz.com 0851636000   1028030000
John Doe   jd@foo.com    0851774001   0851774002&lt;/pre&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/38.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/04/10/using-pivot-and-rank-together.aspx</guid>
            <pubDate>Thu, 10 Apr 2008 15:03:03 GMT</pubDate>
            <wfw:comment>http://jasonfollas.com/blog/comments/38.aspx</wfw:comment>
            <comments>http://jasonfollas.com/blog/archive/2008/04/10/using-pivot-and-rank-together.aspx#feedback</comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/38.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/38.aspx</trackback:ping>
        </item>
    </channel>
</rss>