<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4834408919706016250</id><updated>2016-10-18T12:15:13.079-04:00</updated><category term="T-SQL"/><category term="Databases General"/><category term="Certification"/><category term="Best Practices"/><category term="Fun"/><category term="Professional Development"/><category term="Business Intelligence"/><category term="Performance"/><category term="Handy Functions"/><category term="SSMS"/><category term="Inventory"/><category term="SQL Server Internals"/><category term="Wiki Articles"/><category term="Disaster Recovery"/><category term="Employment"/><category term="Infectious Diseases"/><category term="MVP"/><category term="Service Packs and CUs"/><category term="Technical Minutiae"/><category term="Training"/><category term="Adios"/><category term="Arrivederci"/><category term="Books"/><category term="Ciao"/><category term="Customer Service"/><category term="Database Engine"/><category term="Happy Dance"/><category term="High-Availability"/><category term="It Sounds Boring But It Isn&#39;t"/><category term="Leadership"/><category term="Presentations"/><category term="Profiler"/><category term="Public Service Announcement"/><category term="Until Next Time"/><category term="Welcome"/><title type='text'>The HOBT</title><subtitle type='html'>or There And Back Again</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>81</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-5208457125063850691</id><published>2009-10-03T23:16:00.001-04:00</published><updated>2009-10-03T23:16:39.058-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Adios"/><category scheme="http://www.blogger.com/atom/ns#" term="Arrivederci"/><category scheme="http://www.blogger.com/atom/ns#" term="Ciao"/><category scheme="http://www.blogger.com/atom/ns#" term="Until Next Time"/><title type='text'>The HOBT is Moving!</title><content type='html'>&lt;p&gt;Some exciting news today – I’ve been invited to blog over at SQLBlog.com!&amp;#160; SQLBlog is the home of some of the brightest minds in the SQL Server world, so I’m still a bit shocked that MVP Adam Machanic thought highly enough of my content to ask me to blog on his site.&amp;#160; I’m going to try to keep a low profile over there so he doesn’t realize the mistake that he’s made ;-)&lt;/p&gt;  &lt;p&gt;My continual aim has been to reach as many people as possible with the content that I publish on The HoBT, and the decision move to SQLBlog was made with that in mind.&amp;#160; The sheer number of talented people that write over there has resulted in a pretty significant following, and I’m of the mind that the more people that I can help or confuse, the better.&amp;#160; I thrive on feedback, corrections, and smart-assed comments, so I hope that you’ll update your RSS subscription and pick up the blog at it’s new home over on SQLBlog.com!&lt;/p&gt;  &lt;p&gt;RSS Feed: &lt;a title=&quot;http://sqlblog.com/blogs/aaron_alton/rss.aspx&quot; href=&quot;http://sqlblog.com/blogs/aaron_alton/rss.aspx&quot;&gt;http://sqlblog.com/blogs/aaron_alton/rss.aspx&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Main Blog: &lt;a href=&quot;http://sqlblog.com/blogs/aaron_alton/&quot;&gt;http://sqlblog.com/blogs/aaron_alton/&lt;/a&gt;&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/5208457125063850691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=5208457125063850691' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5208457125063850691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5208457125063850691'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/10/hobt-is-moving.html' title='The HOBT is Moving!'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-8391526018122014926</id><published>2009-10-02T09:54:00.000-04:00</published><updated>2009-10-02T09:54:00.386-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Business Intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="Certification"/><category scheme="http://www.blogger.com/atom/ns#" term="Professional Development"/><title type='text'>So You Want To Learn Business Intelligence, Eh? Part 5: SSAS Deployment and Administration</title><content type='html'>&lt;p&gt;The fifth installment in this blog series (this one rather belated – apologies if you’ve been following along) is about SSAS Deployment and Administration.&lt;/p&gt;  &lt;p&gt;If you’re from a “core” SQL Server background, you’re in for quite the shock when you try to administer an Analysis Services instance.&amp;#160; Very (and I do mean &lt;em&gt;very&lt;/em&gt;) little of what you know from traditional database administration is applicable in SSAS.&amp;#160; That said, a lot of what we DBA’s spend our time troubleshooting on the relational engine side (locking and blocking, for instance) simply doesn’t exist in Analysis Services.&lt;/p&gt;  &lt;p&gt;The authors do a good job of introducing the main tenets of Analysis Services administration.&amp;#160; In order, the reader is walked through the following:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Partition Design, which is a bit more involved than the partitioned indices and views of the &lt;strike&gt;normal&lt;/strike&gt; relational world. &lt;/li&gt;    &lt;li&gt;Aggregation Design, or the process of finding the balance between query response time and cube processing time/space. &lt;/li&gt;    &lt;li&gt;Cube Deployment, and the various methods available to you to deploy your Analysis Services projects. &lt;/li&gt;    &lt;li&gt;Processing cube objects.&amp;#160; If you’re into that sort of thing. &lt;/li&gt;    &lt;li&gt;Locking down SSAS – roles and security assignment.&amp;#160; Much simpler than relational engine security, so there’s no excuse not to do it right ;) &lt;/li&gt;    &lt;li&gt;High-Availability Strategies and DR.&amp;#160; And NO, they’re not the same thing. &lt;/li&gt;    &lt;li&gt;Tuning and monitoring. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;As is the case in the relational world, administration is a big job and needs to be taken seriously.&amp;#160; The self-paced training kit provides a good introduction into SSAS administration, but you need to supplement it with other sources in order to get a decent understanding of the subject.&amp;#160; Books Online is (as always) very handy in this respect, as are the many BI blogs that you can find on the intertubes.&lt;/p&gt;  &lt;p&gt;Tune in tomorrow for data mining.&amp;#160; I mean…. TUNE IN TOMORROW FOR &lt;u&gt;DATA MINING&lt;/u&gt;!!!&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/8391526018122014926/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=8391526018122014926' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/8391526018122014926'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/8391526018122014926'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/10/so-you-want-to-learn-business.html' title='So You Want To Learn Business Intelligence, Eh? Part 5: SSAS Deployment and Administration'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-2191565030293008094</id><published>2009-09-29T21:57:00.001-04:00</published><updated>2009-09-29T21:57:12.431-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Books"/><category scheme="http://www.blogger.com/atom/ns#" term="MVP"/><title type='text'>SQL Server MVP Deep Dives: One-of-a-Kind Content That Supports a Great Cause</title><content type='html'>&lt;p&gt;About a year ago, &lt;a href=&quot;http://sqlblog.com/blogs/paul_nielsen/archive/2009/09/29/53-mvps-warchild-org-and-sqlservermvpdeepdives-com.aspx&quot; target=&quot;_blank&quot;&gt;Paul Nielsen&lt;/a&gt; proposed that the SQL Server MVPs collaboratively write a book whose proceeds would benefit a globally recognized charity: &lt;a href=&quot;http://www.warchild.org/&quot; target=&quot;_blank&quot;&gt;War Child International&lt;/a&gt;.&amp;#160; Simply put, the MVPs who collaborated to write this book have donated their time, experience, and wisdom to a great cause.&amp;#160; You can see a complete list of the authors involved on the book’s site at &lt;a title=&quot;http://www.sqlservermvpdeepdives.com/&quot; href=&quot;http://www.sqlservermvpdeepdives.com/&quot;&gt;http://www.sqlservermvpdeepdives.com&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;I can’t say enough about the people who were involved in this project.&amp;#160; Writing a book, or chapters of a book, is not an insignificant effort.&amp;#160; It is a very time-consuming task, and the fact that they did it for charity simply makes it that much more amazing.&lt;/p&gt;  &lt;p&gt;I will be picking up a copy, and I hope that you’ll do the same.&amp;#160; The e-Book is expected to be available shortly, whereas the printed version is expected later this year.&amp;#160; If you purchase your copy through &lt;a title=&quot;http://www.sqlservermvpdeepdives.com/&quot; href=&quot;http://www.sqlservermvpdeepdives.com&quot;&gt;http://www.sqlservermvpdeepdives.com&lt;/a&gt;, WarChild will receive an extra 10% of the purchase through the publisher’s affiliate account.&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/2191565030293008094/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=2191565030293008094' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/2191565030293008094'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/2191565030293008094'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/09/sql-server-mvp-deep-dives-one-of-kind.html' title='SQL Server MVP Deep Dives: One-of-a-Kind Content That Supports a Great Cause'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-3301548146656295841</id><published>2009-09-19T13:59:00.000-04:00</published><updated>2009-09-19T13:59:00.447-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Disaster Recovery"/><category scheme="http://www.blogger.com/atom/ns#" term="High-Availability"/><category scheme="http://www.blogger.com/atom/ns#" term="Professional Development"/><title type='text'>Lousy HA is Not Necessarily Better Than No HA</title><content type='html'>&lt;p&gt;In fact, it can be worse.&amp;#160; And no, I’m not just being a retentive purist.&amp;#160; Hear me out.&lt;/p&gt;  &lt;p&gt;High-Availability is the ultimate goal of most corporate IT departments.&amp;#160; It’s annoyingly measured and spouted by counting the “number of nines” that your HA solution delivers.&amp;#160; And there is no shortage of technologies designed to help you obtain your uptime goals: Multipathed SANs, Mirrored Multipathed SANs, Clusters, Geographically-Dispersed Clusters, ESX Farms….the list goes on.&amp;#160; For the most part, the vendors of these technologies aren’t foolish enough to claim that their solutions alone are enough to deliver on the promise of uninterrupted uptime, but somewhere along the line all of the HA-hype has done a number on the collective consciousness of the IT world.&amp;#160; &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;em&gt;“You’re running a cluster?&amp;#160; You’re lucky – you must sleep well at night!”&lt;/em&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Hahahoho…&lt;/p&gt;  &lt;p&gt;At one time, there was an entry barrier into the world of high-availability that stopped most companies from venturing into this space: cost.&amp;#160; Quite simply, it cost a bloody fortune to throw together, for instance, &lt;a href=&quot;http://en.wikipedia.org/wiki/Microsoft_Cluster_Server&quot; target=&quot;_blank&quot;&gt;an MSCS cluster&lt;/a&gt;.&amp;#160; For starters, the entire cluster solution had to be certified for running an MSCS cluster.&amp;#160; Not the individual components, but &lt;em&gt;the combination of components&lt;/em&gt;, right down to your SAN firmware version.&amp;#160; That reduced your choices to a handful of (usually very expensive) solutions.&amp;#160; But this wasn’t so bad – it just weeded out “real” availability requirements from “fake” ones.&amp;#160; Application owners could request five nines of availability, but when they saw the price tag, suddenly they could somehow afford more than five minutes of downtime per year.&amp;#160; That’s not to say that all HA requirements were BS – just that costs and budgets kept requirements in check.&lt;/p&gt;  &lt;p&gt;Thanks to the incessant advancement of tecknowledgee, HA solutions are becoming more commonplace.&amp;#160; For instance, with Windows Server 2008 Failover Clustering, you no longer need a certified cluster solution.&amp;#160; Sure, each component needs to be suitable for a cluster, and the entire cluster needs to pass certain validation tests, but the requirements are significantly relaxed from the days of MSCS clusters.&lt;/p&gt;  &lt;p&gt;This is great news, right?&amp;#160; It’s the modern day equivalent of &lt;a href=&quot;http://kitchenscraps.ca/2009/02/16/chicken-in-every-pot/&quot; target=&quot;_blank&quot;&gt;a chicken in every pot&lt;/a&gt;!&amp;#160; A cluster in every server room!&amp;#160; &lt;em&gt;What’s that?&amp;#160; You don’t have a server room?&amp;#160; Well, a cluster in every supply closet!&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;OK, I might be getting a bit carried away, but you get the picture.&lt;/p&gt;  &lt;p&gt;So where’s the problem with all of this?&amp;#160; I mean…isn’t cheaper better?&amp;#160; And of course it is, I say.&amp;#160; But we can’t get ahead of ourselves.&amp;#160; &lt;/p&gt;  &lt;p&gt;&lt;em&gt;There is no shortcut to high-availability.&amp;#160; &lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Before we start promising to deliver uptime out the wazoo, we have to learn to &lt;em&gt;think&lt;/em&gt; for high-availability.&amp;#160; From a 40,000 foot view, it’s hard to imagine that implementing a high-availability mechanism can actually cause damage, but it can.&amp;#160; Let’s take a look at some of the side-effects of half-assed HA implementations, shall we?&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;1. You can actually &lt;em&gt;reduce&lt;/em&gt; the uptime of your applications      &lt;br /&gt;&lt;/strong&gt;Say what?&amp;#160; How can implementing a HA technology reduce the uptime of your applications?&amp;#160; Quite easily, actually.&amp;#160; Let’s take a typical “dual everything” server.&amp;#160; Dual NICs, dual power supplies, dual RAID controllers, a direct-attached RAID array.&amp;#160; You can survive the failure of most of the “fragile” bits of your server, replace the offending hardware, and be back in business.&amp;#160; But there are a few single points of failure…the OS for one, the motherboard.&amp;#160; This isn’t ideal – we’ll never make five nines like this!&amp;#160; We need more HA!&amp;#160; We need (drumroll please) a cluster!&lt;/p&gt;  &lt;p&gt;So you implement a “simple” active/passive 2 node cluster.&amp;#160; A couple of servers, or more commonly blades.&amp;#160;&amp;#160; An iSCSI or fibre channel SAN.&amp;#160; You roll it into production, and everything is beautiful.&amp;#160; Set it and forget it, right?&lt;/p&gt;  &lt;p&gt;Wrong.&amp;#160; Do you have anyone qualified to manage that SAN?&amp;#160; Do you have anyone who knows a cluster from their elbow?&amp;#160; Are you monitoring your new technological valhalla?&amp;#160; No?&lt;/p&gt;  &lt;p&gt;OK…so let’s say you neglected to enable multipathing on the SAN.&amp;#160; Now you have far more single points of failure than you did with your standalone server.&amp;#160; Or let’s say that, in trying your darndest to create a new LUN, you end up clearing the SAN configuration.&amp;#160; Or a routine firmware flash fails, and your SAN won’t come online.&amp;#160; Or let’s say that you’re not monitoring cluster failover events, so you don’t even notice that node 1 went offline in the middle of the night.&amp;#160; Trust me…you’ll notice when node 2 does the same.&lt;/p&gt;  &lt;p&gt;You see, a wise person once said “complexity is the enemy of security”.&amp;#160; An equally wise person &lt;strike&gt;stole&lt;/strike&gt; borrowed the phrase and applied it to availability – for complexity is indeed the enemy of availability.&amp;#160; Without the skills and infrastructure to back up a HA solution, the complexity that you’re introducing can actually &lt;em&gt;decrease&lt;/em&gt; your availability!&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;2. You can cripple your DR strategy     &lt;br /&gt;&lt;/strong&gt;Half-assed HA can hurt your disaster recovery strategy as well, in two important ways:    &lt;br /&gt;&amp;#160; i. “We have (clustering/SAN mirroring/multipathing)!&amp;#160; We don’t need a DR strategy!”.&amp;#160; &lt;br /&gt;Wrongo.&amp;#160; Nothing replaces backups, and even the best HA solution will call on backups from time to time.&amp;#160; Don’t believe me?&amp;#160; Well, how does your cluster help you when your database is suspect?&amp;#160; Toldja so.    &lt;br /&gt;&amp;#160; ii. “We’re backing up to a network share every five minutes!”.&amp;#160; Great!&amp;#160; Did you check to make sure that said “network share” isn’t using the same storage subsystem that your production servers are hosted on?&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;3. You can create false confidence&lt;/strong&gt;    &lt;br /&gt;A business that has faith in it’s “highly available” infrastructure will learn to lean on it more and more.&amp;#160; And this is a good thing – it means that the technology we all work so hard to implement and maintain is paying dividends.&amp;#160; But to your users, promised HA is the same thing as real HA.&amp;#160; They trust that, when you promise them 99.999% uptime and &amp;lt;5 minutes data loss, you know what you’re talking about.&amp;#160; So much so that they may choose not to develop backup plans should the unthinkable happen.&amp;#160; And when it does happen, the business can be seriously injured (or even destroyed), because they can’t meet their contractual obligations/can’t meet reporting deadlines/can’t ship their product.&amp;#160; Think about it before you promise the sun, the moon, and the stars, because…&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;4. You can be out of work&lt;/strong&gt;    &lt;br /&gt;You can be the CIO’s poster child, but if you promise something you can’t deliver, and the fallout seriously impacts the business, you had better have your CV up to date.&amp;#160; Have you really thought about what it takes to deliver any measure of high-availability?&amp;#160; Let’s take a typical SQL Server database application.&amp;#160; What does it depend upon?&amp;#160; For starters, the obvious: the availability of the database.&amp;#160; Which depends upon the OS and physical hardware being up and functioning.&amp;#160; That’s it, right?&amp;#160; Not&amp;#160; quite.&amp;#160; How about:    &lt;br /&gt;&amp;#160; - The physical network connecting your application to your clients    &lt;br /&gt;&amp;#160; - The application servers and/or terminal servers, and all of their dependencies    &lt;br /&gt;&amp;#160; - DNS, DHCP, and Active Directory (what good is an application if your Windows Authenticated users can’t log in?)    &lt;br /&gt;&amp;#160; - The security of your application infrastructure (an application that is down because of a hacker or a disgruntled employee is no more available than an application that is down because of an infrastructure failure)    &lt;br /&gt;&amp;#160; - The power that runs the whole shebang&lt;/p&gt;  &lt;p&gt;And there may be more, depending upon your environment.&amp;#160; &lt;em&gt;“But wait!” you say, “Those things aren’t my problem!&amp;#160; I’m just a DBA!”&lt;/em&gt;.&amp;#160; True, you may be “just a DBA”, but have you documented your dependencies on items that are outside of your control?&amp;#160; Have &lt;em&gt;you&lt;/em&gt; obtained SLAs from your network admins, your security admins, your SAN admins…..to support the SLA that you delivered to your application owners?&amp;#160; Trust me – the corporate chopping block will be much more sympathetic to your plight if you have already documented your dependencies on external factors, &lt;em&gt;before&lt;/em&gt; the proverbial excrement hits the fan.&amp;#160; Any amount of finger pointing after the fact comes across as just that – finger pointing.&amp;#160; And good managers don’t brook lousy excuses.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Now, let’s not misconstrue the message.&amp;#160; I’m not saying for a second that you shouldn’t try to implement highly available infrastructures in your environment.&amp;#160; Nor am I saying that you’re an idiot if you have rolled out a half-assed infrastructure.&amp;#160; The message I’m trying to impart is that you need to examine your “highly available” solution from every possible angle &lt;em&gt;before&lt;/em&gt; a disaster makes you wish that you had.&lt;/p&gt;  &lt;p&gt;See you next time.&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/3301548146656295841/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=3301548146656295841' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/3301548146656295841'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/3301548146656295841'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/09/lousy-ha-is-not-necessarily-better-than.html' title='Lousy HA is Not Necessarily Better Than No HA'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-585104076526494198</id><published>2009-09-08T19:59:00.001-04:00</published><updated>2009-09-08T19:59:42.827-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="It Sounds Boring But It Isn&#39;t"/><category scheme="http://www.blogger.com/atom/ns#" term="Public Service Announcement"/><title type='text'>Surveys, Get Yer Surveys Heah!</title><content type='html'>&lt;p&gt;I’m sorry – I just didn’t know any other way to make surveys sound exciting.&lt;/p&gt;  &lt;p&gt;Peter Saddow asked the SQL community to put a plug out for a few SQL Express surveys that Microsoft has developed.&amp;#160; The results of these surveys will directly influence the development of the SQL Express installation experience, so if you work with SQL Express, please take the time to fill them out.&amp;#160; Or, as Peter more eloquently said:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Microsoft is committed to making the SQL Server Express installation experience the best in industry. For us to achieve this goal, we need your candid feedback on your experience with installing SQL Server Express.&amp;#160; If you have used several different versions of SQL Server Express, focus on the latest version that you have used.&amp;#160; When SQL Server 2008 Customer Technical Preview 3 is released, feel free to complete this survey again since several installation improvements have been made.&amp;#160; This information will be used to improve our future releases including SQL Server 2008 R2 and SQL Server 11.&amp;#160; This survey will be available until the end of November.&lt;/p&gt;    &lt;p&gt;Please visit the following blogs that contain instructions:&lt;/p&gt;    &lt;p&gt;General survey around installing SQL Server Express&lt;/p&gt;    &lt;p&gt;&lt;a href=&quot;http://blogs.msdn.com/petersad/archive/2009/09/08/general-sql-server-express-survey.aspx&quot;&gt;http://blogs.msdn.com/petersad/archive/2009/09/08/general-sql-server-express-survey.aspx&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;Survey that focuses on embedding SQL Server Express&lt;/p&gt;    &lt;p&gt;&lt;a href=&quot;http://blogs.msdn.com/petersad/archive/2009/09/08/embedding-sql-server-express.aspx&quot;&gt;http://blogs.msdn.com/petersad/archive/2009/09/08/embedding-sql-server-express.aspx&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Seriously.&amp;#160; Fill them out.&amp;#160; And blog about it, or “retweed”, or whatever it is you kids do nowadays.&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/585104076526494198/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=585104076526494198' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/585104076526494198'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/585104076526494198'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/09/surveys-get-yer-surveys-heah.html' title='Surveys, Get Yer Surveys Heah!'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-6875588914459058303</id><published>2009-08-25T09:41:00.001-04:00</published><updated>2009-08-25T09:41:00.043-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>Treating The Results of a Stored Procedure as a Table Expression</title><content type='html'>&lt;p&gt;I stumbled upon a &lt;a href=&quot;http://www.stackoverflow.com/&quot; target=&quot;_blank&quot;&gt;StackOverflow&lt;/a&gt; thread this week, where the poster was asking how they could fetch the results of a stored procedure into a temporary table &lt;em&gt;without knowing the resultset’s composition (column layout) in advance&lt;/em&gt;.  If your first question is “why?”, good for you.  If you’re trying to capture the resultset returned by a stored procedure without knowing it’s composition, how in the world can you possibly hope to use said result set?&lt;/p&gt;  &lt;p&gt;Whatever the case, the OP clearly wanted to do so.  They had gotten as far as concluding that the SELECT…INTO…FROM syntax (which creates the object specified after the INTO keyword) would do the heavy lifting for them, if only they could treat the stored procedure as a table expression (if you have no idea what I’m talking about, check out the examples below).  The normal use of the SELECT INTO syntax looks something like this: &lt;/p&gt;  &lt;pre style=&quot;line-height: 1em; font-size: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;*  &lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Don&#39;t use *.  You didn&#39;t see this here.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INTO &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;#MyTempTable &lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Drop the hash to create a permanent table.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;MyRealTable&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;You can’t substitute a sproc name for your table name, and expect it to work.  What you &lt;em&gt;can&lt;/em&gt; do is use OPENROWSET or OPENQUERY.  Have a look here:&lt;/p&gt;&lt;pre style=&quot;line-height: 1em; font-size: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE PROC &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;MyProc&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;BEGIN&lt;br /&gt;    SELECT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;* &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;sys.databases  &lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Or whatever.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;END&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;* &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INTO &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;#MyTempTable &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM OPENROWSET&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;SQLNCLI&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;br /&gt;                &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Server=(local)\SQL2008;Trusted_Connection=yes;&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,     &lt;br /&gt;                &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;EXEC MyProc&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;* &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;#MyTempTable&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Neat, eh?  Keep in mind that you need to have “Ad Hoc Distributed Queries” enabled: &lt;/p&gt;&lt;pre style=&quot;line-height: 1em; font-size: 12px&quot;&gt;&lt;span style=&quot;color:darkred;&quot;&gt;sp_configure &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Show Advanced Options&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;GO&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;RECONFIGURE&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:darkred;&quot;&gt;sp_configure &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Ad Hoc Distributed Queries&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;RECONFIGURE&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;http://stackoverflow.com/questions/653714/how-to-select-into-temp-table-from-stored-procedure&quot; target=&quot;_blank&quot;&gt;Here’s the original thread&lt;/a&gt; – there are a number of very good contributions on the thread.&lt;/p&gt;&lt;p&gt;Thanks to &lt;a href=&quot;http://msmvps.com/blogs/robfarley/&quot; target=&quot;_blank&quot;&gt;SQL MVP Rob Farley&lt;/a&gt; for putting a name to what the OP was asking for – the title of this article was a direct “yoink” from his comment on my post.  I would have called it “How to bring back the results of a stored procedure into a temporary table even though you don’t know what the results look like at design time”.  I ran short of breath just writing that one.&lt;/p&gt;&lt;p&gt;See you next time.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/6875588914459058303/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=6875588914459058303' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/6875588914459058303'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/6875588914459058303'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/08/treating-results-of-stored-procedure-as.html' title='Treating The Results of a Stored Procedure as a Table Expression'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-860766028654472663</id><published>2009-08-23T20:45:00.001-04:00</published><updated>2009-08-23T20:54:10.106-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Business Intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="Certification"/><category scheme="http://www.blogger.com/atom/ns#" term="Professional Development"/><title type='text'>So You Want To Learn Business Intelligence, Eh? Part 4 – Extending SSAS Cubes</title><content type='html'>&lt;p&gt;Time for more multi-dimensional fun.  If you read &lt;a href=&quot;http://thehobt.blogspot.com/2009/07/so-you-want-to-learn-business_31.html&quot; target=&quot;_blank&quot;&gt;the previous article in this series&lt;/a&gt;, we finally started digging into the cubey goodness that is SSAS.  Today we’re going to look at &lt;strong&gt;Chapter 6&lt;/strong&gt; in our trusty guide (MCTS Self-Paced Training Kit (Exam 70-448): Business Intelligence Development and Maintenance).  In this chapter, we start pimping our SSAS cubes; implementing hierarchies, creating KPIs, Actions, Translations, and Perspectives, and creating calculations and queries in (gasp!) MDX.&lt;/p&gt;  &lt;p&gt;I introduced hierarchies in the last blog post, but for those who &lt;strike&gt;were asleep&lt;/strike&gt; didn’t catch that post, a hierarchy is….well…a hierarchy.  An example is probably better than my weak, recursive definition.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;A Time Hierarchy&lt;/strong&gt;    &lt;br /&gt;Year    &lt;br /&gt;  Quarter    &lt;br /&gt;    Month    &lt;br /&gt;      Week    &lt;br /&gt;        Day    &lt;br /&gt;          Hour    &lt;br /&gt;            Minute    &lt;br /&gt;              Second&lt;/p&gt;  &lt;p&gt;Establishing a hierarchy serves two purposes: a usability benefit for the end user (they can browse data at any level in the hierarchy, without having to search for disparate time dimensions), and a performance benefit for SSAS.  How’s that?  Well, much of the reason why SSAS is able to present aggregated data to the end user so quickly is that it pre-aggregates said data.  Usually, the most frequently queried aggregations are stored, and the balance are calculated upon request.  By implementing a hierarchy, SSAS can use a lower level aggregation to “roll up” to a higher level aggregation.  For instance, if we already have an aggregation at the Day level, a Month aggregation can be calculated very efficiently.  As usual, the book did an excellent job of presenting the concept of hierarchies, and of explaining how to implement them.&lt;/p&gt;  &lt;p&gt;KPIs are one of the biggest buzzwords associated with modern Business Intelligence.  For the initiate, a KPI is a quick indication of the degree of success with which a business is meeting it’s goals.  What those goals are depends upon the organization and the KPI consumer, but some easy examples are Customer Profitability, Inventory Turnover, Expenses as they relate to Revenue, etc.  In defining a KPI, you provide SSAS with instructions on how to calculate the current value of your metric, your target value (goal), your KPI’s status (value as it relates to the goal – are we well under the target?  Approaching the target?  Have we exceeded the target?), and the trend (are you trending up, or trending down).  There are, of course, more properties available, but the four I just mentioned are the key properties.  A KPI is what powers those lovely gauges and stoplights we see so frequently on BI dashboards.&lt;/p&gt;  &lt;p&gt;Actions simply allow you to perform additional activities within your cubes, provided that your client software supports said activities.  Good examples of cube activities include linking out to a website, drilling into detail data, and linking out to an SSRS report.  Chapter 6 walks you through the process of creating a drill-through action in detail.&lt;/p&gt;  &lt;p&gt;Translations are…just that.  They translate the data – for instance, presenting data in languages other than the cube data’s primary language to international users.  Of course, SSAS doesn’t actually perform the translation – your datawarehouse needs to contain translated dimension attributes, but a translation allows the end user to select one of your defined languages and see all of their cube data in the translated language.&lt;/p&gt;  &lt;p&gt;You can think of a perspective as a subset of a cube.  The book’s authors are quick to note that perspectives are not security mechanisms – they simply exist to allow you to present a relevant subset of a (potentially huge) cube to a business user.&lt;/p&gt;  &lt;p&gt;Finally, in lesson 3, we took a run at the dreaded MDX.  I won’t even try to explain MDX here, save to say that it is indeed deceptively similar to TSQL.  My one beef about the book to date is that the coverage of MDX was sparse and rushed.  The authors jumped right into multidimensional syntax without really walking us through the basics of the language.  As a result, the reader is left to wonder why a member is expressed as [Product].[Product].[Mountain-100] (the AdventureWorks Mountain Bike 100).  Why [Product].[Product]?  Why does the member name have two parts?  Is this always the case?  In their defense, you could probably work with cubes for years without needing to write very much MDX at all: the designers built into BIDS generally “write” all of the MDX for you.  And though the exam doesn’t place much weight on MDX, I still believe that you need to understand it in order to really understand SSAS.  Unfortunately, you’ll need to spend a bit of time on MSDN before you can really wrap your head around MDX.&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;And that’s it for this week.  See you next time, when we’ll talk about SSAS deployment and administration.  Stop yawning.  After that, it’s data mining time, baby.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/860766028654472663/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=860766028654472663' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/860766028654472663'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/860766028654472663'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/08/so-you-want-to-learn-business.html' title='So You Want To Learn Business Intelligence, Eh? Part 4 – Extending SSAS Cubes'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-8935353641750980324</id><published>2009-08-07T11:26:00.001-04:00</published><updated>2009-08-07T11:27:46.194-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>The Transact-SQL OVER Clause: Not Just For Ranking Functions</title><content type='html'>&lt;p&gt;If you’ve been following this blog for a bit, you may have seen the post I made &lt;a href=&quot;http://thehobt.blogspot.com/2009/02/rownumber-rank-and-denserank.html&quot; target=&quot;_blank&quot;&gt;back in March&lt;/a&gt; on Ranking Functions.  In it I introduced ranking functions: RANK(), DENSE_RANK(), and ROW_NUMBER().  I also introduced the OVER clause, and how it is used to order and partition the ranking logic.&lt;/p&gt;  &lt;p&gt;Until now, I didn’t realize that the OVER clause could be applied to &lt;em&gt;aggregate&lt;/em&gt; functions as well.  Earlier this week, I dug up &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms189461.aspx&quot; target=&quot;_blank&quot;&gt;this little nugget&lt;/a&gt; from BOL. &lt;/p&gt;  &lt;pre style=&quot;line-height: 1em; font-size: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;SalesOrderID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProductID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;OrderQty&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;SUM&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;OrderQty&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PARTITION &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;SalesOrderID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Total&#39;&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;AVG&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;OrderQty&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PARTITION &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;SalesOrderID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Avg&#39;&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;OrderQty&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PARTITION &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;SalesOrderID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Count&#39;&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;MIN&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;OrderQty&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PARTITION &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;SalesOrderID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Min&#39;&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;MAX&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;OrderQty&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PARTITION &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;SalesOrderID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Max&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderDetail &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;SalesOrderID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;IN&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;43659&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;43664&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;);&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Holy cow, Batman!  How did I miss that?  The partitioned aggregates very efficiently perform subtotals based upon the OVER clause that you define.  Prior to the OVER clause, we would have needed to create a derived table which GROUPed the query by our partition columns, then joined said table back to our parent query.  This method is much cleaner, and much more efficient, as you aren’t running through your results twice (one for the aggregate, and again to fetch back the “rows” themselves).&lt;/p&gt;&lt;p&gt;This opens up a lot of possibilities, so it’s a good tool to have in your belt, so to speak.&lt;/p&gt;&lt;p&gt;Tune in later this week, as we return to our regularly scheduled BI series.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/8935353641750980324/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=8935353641750980324' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/8935353641750980324'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/8935353641750980324'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/08/transact-sql-over-clause-not-just-for.html' title='The Transact-SQL OVER Clause: Not Just For Ranking Functions'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-1873927164135309640</id><published>2009-07-31T10:37:00.000-04:00</published><updated>2009-07-31T10:40:03.397-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Business Intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="Certification"/><category scheme="http://www.blogger.com/atom/ns#" term="Professional Development"/><title type='text'>So You Want To Learn Business Intelligence, Eh? Part 3 – Developing SSAS Cubes</title><content type='html'>&lt;p&gt;Here comes the fun. Once again, if you’re just joining the party, this blog series walks through the Microsoft Press Training Kit for Exam &lt;a href=&quot;http://www.microsoft.com/learning/en/us/exam.aspx?ID=70-448&quot; target=&quot;_blank&quot;&gt;70-448: Business Intelligence Development and Maintenance&lt;/a&gt;. In previous posts, we’ve worked through SSIS as it pertains to loading data warehouses.&lt;/p&gt;&lt;p&gt;Now we’ve arrived at the fun bit – SQL Server Analysis Services. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;Chapter 5&lt;/strong&gt; in the self-paced training kit, introduces the concept of an SSAS cube. The whole multi-dimensional concept stymies some people a bit, so let me break it down as well as I can. Picture a load of sales data in a traditional, OLTP database; say, for instance, an order detail table. Such a table contains one line for each item on each sales order. It’s useful for transacting, and for &lt;em&gt;simple&lt;/em&gt; analysis queries (for instance, total sales per product), but once we start to get into trickier analysis requirements, an OLTP structure begins to fall down. For instance, let’s say we want to see how sales for a given product is trending with respect to time. Well, to solve that kind of a problem using traditional TSQL, we’d need to do something like this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Define the time interval that we’re using to “bucket” our sales. For example, we could compare sales from one month to the next. &lt;/li&gt;&lt;li&gt;Write a query to GROUP our order lines by that date interval, by product (GROUP BY YEAR(MyDate), MONTH(MyDate), MyProductID). In said query, summarize order quantity by product (SUM(OrderQuantity)) &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Fairly straightforward, right? If this was all that we needed to do, and we didn’t care a hoot about performance, a TSQL solution would be perfectly acceptable. Dump the results into Excel, chart it, and voila. Unfortunately, reality is seldom that straightforward. More often than not, business users want a bit of flexibility in their reporting and analysis. Perhaps they might want to drill in on an interesting item, and see how the sales for said item are trending on a week-by-week basis. Or perhaps they want to view sales by &lt;em&gt;region&lt;/em&gt; by product over time. With a TSQL analysis solution, you’re going to end up with an awful lot of specialized queries to write and maintain. Add to that the fact that aggregations and grouping in a relational engine are generally anything but quick, and you’ll start to see the business case for OLAP.&lt;/p&gt;&lt;p&gt;Picture a cube as a combination of data values (quantity sold, total purchases, and the like) and their attributes (customer, order date, product, etc). The data values are collectively referred to as &lt;em&gt;measures&lt;/em&gt;, and are derived from tables in our data warehouse called “fact tables”. The attributes are termed “dimensions”, and are derived from tables in our data warehouse called “dimension tables”. Dimensions can be organized into hierarchies, further facilitating data analysis. The most common hierarchy is of course time (Year-Quarter-Month-Week….), but many other hierarchies will likely exist in any given set of dimensions (ex. Sales Region; Country-State/Province-City-Zip/Postal). The performance boon kicks in when you factor in aggregations, which are pre-calculated summaries of measures across one or more dimensions.&lt;/p&gt;&lt;p&gt;The training kit digs into these concepts and much more in chapter 5. There’s quite a lot of information in there, so I went slowly and made sure that I internalized the information as I went. Given that it’s a pretty complex topic to tackle, I feel that the chapter did a very good job introducing the reader, and bringing them up to speed on the basics of SSAS cubes.&lt;/p&gt;&lt;p&gt;Next time, we’ll be looking at some of the more advanced aspects of cube design: KPIs, Actions, Translations, and the like.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/1873927164135309640/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=1873927164135309640' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1873927164135309640'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1873927164135309640'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/07/so-you-want-to-learn-business_31.html' title='So You Want To Learn Business Intelligence, Eh? Part 3 – Developing SSAS Cubes'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-1143017574439640152</id><published>2009-07-30T17:18:00.001-04:00</published><updated>2009-07-30T17:18:00.056-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Business Intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="Certification"/><category scheme="http://www.blogger.com/atom/ns#" term="Professional Development"/><title type='text'>So You Want To Learn Business Intelligence, Eh? Part 2 – Deploying and Administering SSIS Packages</title><content type='html'>&lt;p&gt;In case you’ve missed the first two posts, this series is about working through the curriculum for exam 70-448: Microsoft SQL Server 2008 Business Intelligence and Maintenance.  You can find the introduction &lt;a href=&quot;http://thehobt.blogspot.com/2009/06/so-you-want-to-learn-sql-server.html&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.  The first two chapters of the MSPress Training Kit are covered &lt;a href=&quot;http://thehobt.blogspot.com/2009/07/so-you-want-to-learn-business.html&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In this post, we’re going to cover off on chapters 3 and 4, which cover the deployment and administration of your completed SSIS packages.  Now if you’re anything like me, you’re probably asking yourself…where’s the BIeef?  I didn’t think this was an SSIS curriculum.  And if you are anything like me, you probably need to chillax a bit too.  &lt;a href=&quot;http://en.wikipedia.org/wiki/Extract,_transform,_load&quot; target=&quot;_blank&quot;&gt;ETL&lt;/a&gt; is a necessary part of any Business Intelligence project, and SSIS is a particularly slick ETL toolset.  That said, the next chapter is the start of the SSAS portion of the curriculum, so the coolness is right around the corner.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Chapter 3&lt;/strong&gt; focused on the deployment options available in SSIS.  Though it may be tempting (especially in smaller environments) to write off formal package deployments as “unnecessarily complex”, this would be a mistake.  Deployment via the Deployment Wizard or by using DTUtil allows you to standardize the deployment process, thus reducing the possibility for operator-induced error when deploying a package to multiple environments (say, test and production).  It also allows for a more streamlined deployment process should you need to need to recreate your packages for any reason (think DR).  As with the previous chapters, I found Chapter 3 to be very well written and comprehensible.  The exercises are particularly well-written, so make sure that you take the time to go through them.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Chapter 4&lt;/strong&gt; discussed the administration of SSIS packages.  It introduced the SSIS service, as well as the key folders and files required for SSIS to properly function.  It then moved on to securing SSIS packages (sadly, an often-overlooked aspect of SSIS administration), and encrypting sensitive data (for example, connection strings).   The authors then introduced DTExec and DTExecUI, and the various parameters and switches that we can use to control package execution.&lt;/p&gt;&lt;p&gt;Overall, the SSIS section was very well written, and almost completely error-free.  This isn’t always the case with technical publications, so this resource kit is a refreshing change so far.&lt;/p&gt;&lt;p&gt;Come back tomorrow where we’re going to start to tackle the fun bit: SQL Server Analysis Services.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/1143017574439640152/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=1143017574439640152' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1143017574439640152'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1143017574439640152'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/07/so-you-want-to-learn-business_30.html' title='So You Want To Learn Business Intelligence, Eh? Part 2 – Deploying and Administering SSIS Packages'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-341389774756625352</id><published>2009-07-14T20:55:00.000-04:00</published><updated>2009-07-14T20:55:00.074-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Presentations"/><title type='text'>Simple Change Data Tracking in SQL Server – Presentation Tomorrow!</title><content type='html'>&lt;p&gt;In&amp;#160; case you aren’t following along on Twitter, &lt;a href=&quot;http://www.facility9.com/&quot; target=&quot;_blank&quot;&gt;Jeremiah Peschka&lt;/a&gt; somehow managed to twist my arm into delivering a presentation for the AppDev Virtual Chapter tomorrow, from 2 to 3 PM.&amp;#160; The subject: change data tracking in SQL Server.&amp;#160; I’ll be going over two of the most common methods for tracking data changes – DML triggers and SQL Server Change Data Capture (CDC).&lt;/p&gt;  &lt;p&gt;It will be the event of the &lt;strike&gt;year&lt;/strike&gt; &lt;strike&gt;week&lt;/strike&gt; day, so don’t be left out.&amp;#160; I’ve been told &lt;font size=&quot;1&quot;&gt;&lt;em&gt;by a liar&lt;/em&gt;&lt;/font&gt; that I sound like Barry White – you wouldn’t want to miss that, would you?&lt;/p&gt;  &lt;p&gt;Registration is not required, so you don’t need to worry about being spammed – just follow the link below to join the fun.&lt;/p&gt;  &lt;p&gt;&amp;gt;&amp;gt;&lt;a href=&quot;http://www.sqlpass.org/Events/ctl/ViewEvent/mid/521.aspx?ID=176&quot; target=&quot;_blank&quot;&gt;Where You Want To Be Spending a Wednesday Afternoon&lt;/a&gt;&amp;lt;&amp;lt;&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/341389774756625352/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=341389774756625352' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/341389774756625352'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/341389774756625352'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/07/simple-change-data-tracking-in-sql.html' title='Simple Change Data Tracking in SQL Server – Presentation Tomorrow!'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-1947349996227778296</id><published>2009-07-09T20:48:00.001-04:00</published><updated>2009-07-09T20:50:06.094-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Business Intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="Certification"/><category scheme="http://www.blogger.com/atom/ns#" term="Professional Development"/><title type='text'>So You Want To Learn Business Intelligence, Eh?  Part 1 – SSIS Development, Error Handling, and Logging</title><content type='html'>&lt;p&gt;In case you missed &lt;a href=&quot;http://thehobt.blogspot.com/2009/06/so-you-want-to-learn-sql-server.html&quot; target=&quot;_blank&quot;&gt;my post last week&lt;/a&gt;, I have begun to work on preparing for exam 70-448 (SQL Server 2008 – Business Intelligence Development and Maintenance).  This all came about because a number of people in the SQL Server community expressed a desire to “learn BI”, myself included.  Being of not entirely sound mind and judgment, I volunteered to &lt;a href=&quot;http://en.wikipedia.org/wiki/Take_point&quot; target=&quot;_blank&quot;&gt;take point&lt;/a&gt;.  Hopefully I don’t get shot in the process.&lt;/p&gt;  &lt;p&gt;Our guidebook for this adventure is the &lt;a href=&quot;http://www.microsoft.com/learning/en/us/Book.aspx?ID=13112&amp;amp;locale=en-us&quot; target=&quot;_blank&quot;&gt;MSPress Self-Paced Training Kit&lt;/a&gt;, and I hope you’ll follow along.  The book is broken down into thirteen chapters, and I’ll be covering one or two per week.  Probably two.  I’m impatient that way.&lt;/p&gt;  &lt;p&gt;Anyhow, without further ado – let’s dig into the material.  If you’re not familiar with MSPress training kits, they’re a pretty sweet deal.  For $55 CAD (roughly $8.95 USD) you get the manual itself, a suite of MeasureUp practice questions, and a discount code for 15% off your exam registration fee.  15% off of $125 USD is a discount of just under twenty bucks, so the $55 purchase price is really a no-brainer.&lt;/p&gt;  &lt;p&gt;Of course, the value really depends upon the quality of the material.  Fortunately, this book’s authors (Erik Veerman, Teo Lachev, and Dejan Sarka) really have it together.  Chapters 1 and 2 were very well written – they’re logically structured, and written in very plan, easy to understand language.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Chapter 1&lt;/strong&gt; introduces SQL Server Integration Services.  There won’t be any surprises here for the seasoned SSIS user, but if you haven’t had the chance to use SSIS yet, this chapter will serve as a very nice introduction.  It starts out with an introduction to Business Intelligence Development Studio, and shows you how to create a new SSIS package within BIDS.  The chapter then moves on to the creation of Project Data Sources, which are a method of centralizing connection information for the data sources and destinations used in the package.  At the end of the first lesson, the author covers off on creating Package Connections from your Project Data Sources.  If you think of a package data source as an object, and a package connection as an instance of said object, you won’t be too far off the beaten track.  You &lt;em&gt;can &lt;/em&gt;have package connections that don’t reference Project Data Sources though, so make sure that you understand the difference.&lt;/p&gt;  &lt;p&gt;The second lesson in chapter 1 is about creating and editing control flow objects.  If your most recent ETL experience is with DTS, you might start cursing at this point.  Simmer down.  SSIS is a completely different animal.  I cursed the first time that I opened up SSMS in SQL Server 2005 and couldn’t find my precioussss DTS.  In fact, I cursed the first few times.  Of course, the SQL Server engineers didn’t make SSIS complex for kicks – it’s complex because it allows for immense flexibility.  Control flow objects perform actions in your SSIS packages – anything from sending an email to transferring a file via FTP.  Control flows also provide for the use of control flow containers, which allow you to control transactional consistency and implement looping structures.  Control flows also contain data flow tasks, which are themselves containers for all of the elements that make up a data flow – for instance, connecting to a data source, extracting information, manipulating it, and loading it into a destination.  Data Flow adapters and transformations are covered in chapter 3.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Chapter 2&lt;/strong&gt; discusses the methods available to you for debugging and error handling in SSIS.  Error handling is very robust, with a full array of logging and data redirection options available to you.  One of the most useful error handling features in SSIS is the ability to use package checkpoints.  Checkpoints save your package’s state should an error occur, allowing it to “pick up where it left off” once you correct the error.  This chapter also goes over the ins and outs of transactions, which (like their TSQL cousins) allow for atomic behaviour in your SSIS packages.  One thing that I have found to be particularly useful about SSIS transaction handling is the ability to exempt portions of your package from a transaction rollback – for instance, the tasks that are responsible for logging our the reasons why your package is rolling back in the first place.&lt;/p&gt;  &lt;p&gt;…&lt;/p&gt;  &lt;p&gt;Altogether, the self-paced training kit has been a treat to read so far.  I’m anxious to get to data mining and cube design, which is what really motivated me to work towards this certification, but it’s good to flesh out your knowledge in all of the supporting technologies first.&lt;/p&gt;  &lt;p&gt;Stay tuned for more BI goodness next week.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/1947349996227778296/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=1947349996227778296' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1947349996227778296'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1947349996227778296'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/07/so-you-want-to-learn-business.html' title='So You Want To Learn Business Intelligence, Eh?  Part 1 – SSIS Development, Error Handling, and Logging'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-7851206591330116304</id><published>2009-06-25T01:27:00.001-04:00</published><updated>2009-06-25T01:27:03.892-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Business Intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="Certification"/><title type='text'>So You Want To Learn SQL Server Business Intelligence, Eh?</title><content type='html'>&lt;p&gt;So do I.  In fact, in Tim Ford’s recent chain blog “Give Me a Coconut and Six Months” (&lt;a href=&quot;http://www.ford-it.com/sqlagentman/?p=268&quot; target=&quot;_blank&quot;&gt;Tim’s post&lt;/a&gt;, &lt;a href=&quot;http://thehobt.blogspot.com/2009/06/so-apparently-im-on-deserted-island.html&quot; target=&quot;_blank&quot;&gt;my response&lt;/a&gt;), almost all of the bloggers that participated indicated that they’re interested in learning about SQL Server Business Intelligence.&lt;/p&gt;&lt;p&gt;Why is that?  Well, Business Intelligence carries with it the promise of unlocking tremendous amounts of value in the data that we gather and maintain every day.  As database administrators, we are naturally very interested in anything that promises to squeeze additional benefit out of the information that we work so hard to protect.  Plus, all of those BI guys keep telling us about the fame, fortune, and delicious food that the magic of Business Intelligence brings.&lt;/p&gt;&lt;p&gt;I just placed my order for the &lt;a href=&quot;http://www.microsoft.com/learning/en/us/Book.aspx?ID=13112&amp;amp;locale=en-us&quot; target=&quot;_blank&quot;&gt;MCTS Self-Paced Training Kit (Exam 70-448): Microsoft SQL Server 2008 – Business Intelligence Development and Maintenance&lt;/a&gt;.  Why do I all of a sudden feel a little nauseous?&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/_GyTlY8-ysTw/SkJiIomi_VI/AAAAAAAAAJI/Dc7YOpoFCXc/s1600-h/9780735626362x3.gif&quot;&gt;&lt;img style=&quot;BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px&quot; title=&quot;9780735626362x&quot; border=&quot;0&quot; alt=&quot;9780735626362x&quot; src=&quot;http://lh5.ggpht.com/_GyTlY8-ysTw/SkJiI6BvYmI/AAAAAAAAAJM/57XasaAvZC8/9780735626362x_thumb1.gif?imgmax=800&quot; width=&quot;277&quot; height=&quot;277&quot; /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Maybe it’s the thought of all that denormalizing going on in the analysis services cubes, or the concept of learning MDX (&lt;strong&gt;M&lt;/strong&gt;ulti-&lt;strong&gt;D&lt;/strong&gt;imensional E&lt;strong&gt;x&lt;/strong&gt;pressions), which I’m given to understand is an unholy marriage between my beloved Transact-SQL and a Escher illusion.&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot;font-size:180%;&quot;&gt;&lt;strong&gt;MDX&lt;br /&gt;=&lt;br /&gt;T-SQL&lt;br /&gt;&lt;/strong&gt;&lt;/span&gt;&lt;span style=&quot;font-size:180%;&quot;&gt;&lt;strong&gt;+&lt;/strong&gt;&lt;br /&gt;&lt;/span&gt; &lt;img src=&quot;http://upload.wikimedia.org/wikipedia/en/thumb/a/a3/Escher%27s_Relativity.jpg/300px-Escher%27s_Relativity.jpg&quot; width=&quot;493&quot; height=&quot;475&quot; /&gt;&lt;/p&gt;&lt;p&gt;I’m kidding, mostly.  I do have some experience with SSRS, and I regularly maintain SSAS installations, so those parts of the curriculum at least won’t be entirely new.  I’m looking to dig into the nitty-gritty details surrounding BI design and implementation, and this certification should be a good start.&lt;/p&gt;&lt;p&gt;Anyway, the reason why I wrote this post &lt;em&gt;isn’t&lt;/em&gt; because I was bored and thought I’d share my latest purchase with you.  It was actually to invite you along for the ride; to whatever extent you would like to go.  I’m going to blog every week about my experiences with the curriculum so far – I’ll include a high-level overview of the chapter(s) that I’ve covered, any difficulties or inaccuracies that I noted, as well as links to other resources that I found useful.  If you have the time and inclination, go ahead and grab a copy of the book – it’s always better to have somebody to bounce your observations or problems off of, and I’m happy to be that someone.  If you aren’t quite ready to dive headfirst into SQL Server Business Intelligence, feel free to follow along anyway.  While I won’t be regurgitating the curriculum word-for-word, I hope to provide enough insight into BI that it’ll take some of the mystery away for those who would like to dig into BI at some point in the future.&lt;/p&gt;&lt;p&gt;Wish me luck!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/7851206591330116304/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=7851206591330116304' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/7851206591330116304'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/7851206591330116304'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/06/so-you-want-to-learn-sql-server.html' title='So You Want To Learn SQL Server Business Intelligence, Eh?'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh5.ggpht.com/_GyTlY8-ysTw/SkJiI6BvYmI/AAAAAAAAAJM/57XasaAvZC8/s72-c/9780735626362x_thumb1.gif?imgmax=800" height="72" width="72"/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-3320202930739672182</id><published>2009-06-24T14:23:00.001-04:00</published><updated>2009-06-24T14:23:04.194-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Technical Minutiae"/><title type='text'>A Publisher’s Responsibility</title><content type='html'>&lt;p&gt;Over the last few weeks, I have closely followed a couple of conversations on Twitter, wherein various people were debating the responsibility of a publisher when it comes to ensuring that the content that they put out is correct and conforms to best practices.  On each occasion, the conversation started because a major technical resource site (two different sites, actually) published an article that advocated behaviour that doesn’t correspond to established SQL Server best practices.&lt;/p&gt;&lt;p&gt;Two interesting (and very incompatible) perspectives were raised.  In each case, the site’s representative believed that it was within the community’s power to judge the validity of an article by using the voting tools supplied on the site.  They conjectured that, by down-voting a poor article, said article could do no harm as anyone reading it would take the advice with a few grains of salt.  Also in each case, the people questioning the content believed that it was the publisher’s responsibility – not the community’s – to validate that the advice being provided on their site was sound and accurate.  Both sides stuck to their guns, and not much seemed to come out of the whole debate.&lt;/p&gt;&lt;p&gt;Without getting into details or naming names, the first site’s representative has since gone on to state that they are now screening articles more critically than they did before.  The second site’s representative said…not much…so we’ll have to see whether or not they take steps to keep out the riffraff.&lt;/p&gt;&lt;p&gt;The whole issue has been consuming idle threads at the back of my mind ever since, so I thought I’d blog my thoughts and offer a chance for others to comment.&lt;/p&gt;&lt;p&gt;I believe that the responsibility for ensuring that published content is accurate and valid belongs to &lt;strong&gt;the publisher&lt;/strong&gt;.  Here’s why I believe so strongly that this is the case:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The publisher has built a reputation by providing sound content.  By virtue of the fact that an article is published on SomeBigSQLSite.com, the unsuspecting reader may very well assume that said content has already been vetted.  While the seasoned SQL professional may be able to separate fact from fiction, a novice likely would not. &lt;/li&gt;&lt;li&gt;Comments and ratings are easily glossed over.  If I’m looking for a quick bit of information, I’m likely to miss the ratings altogether.  If, on the other hand, an article that consistently got poor ratings was surrounded by a very prominent warning, indicating to the reader that they should proceed with caution, one could almost argue that the “community moderation” theory holds water.  Except for… &lt;/li&gt;&lt;li&gt;…those poor few keeners who read the article before somebody in the know has the chance to downvote it.  I believe that published advice should be safe for anyone to follow from the minute it is published, not just after the hours or days required to accumulate enough downvotes for the content to be considered “suspsect”. &lt;/li&gt;&lt;li&gt;The community doesn’t get paid to edit or moderate a site’s content – the revenue stays with the site owner, and so should the responsibility.  If it doesn’t, I think I’ll register SomeBigSQLSite.com and start accepting submissions from anyone with a heartbeat and a cheap asking price ;-) &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So what do you think?  I’m interested in hearing your perspective on the issue, so please feel free to share it below.  No references to actual sites or people, though – I hope that publishers will listen to common consensus (whatever that may be) without having their noses rubbed in it.&lt;/p&gt;&lt;p&gt;Speak up!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/3320202930739672182/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=3320202930739672182' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/3320202930739672182'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/3320202930739672182'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/06/publishers-responsibility.html' title='A Publisher’s Responsibility'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-5623023288728456516</id><published>2009-06-22T12:32:00.001-04:00</published><updated>2009-06-22T12:32:01.255-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Best Practices"/><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>Tricky Updates: Weird, Wacky, and Wonderful UPDATE Syntax Variations in SQL Server</title><content type='html'>&lt;p&gt;If you’ve been working with SQL Server for any length of time, you’ve probably seen a number of different variations of the UPDATE command.  From the miraculous to the mundane, the UPDATE command has as many variants in TSQL as….something with a lot of varieties of stuff has things.  Damn, it’s late.&lt;/p&gt;&lt;p&gt;Anyway, today’s blog post is more like a public service announcement.  It seems that all too many developers are still dangerously unaware of the ability to create very ambiguous UPDATE statements in TSQL.&lt;/p&gt;&lt;p&gt;What am I blithering on about?  Well, here’s a couple of simple update statements (using AdventureWorks2008): &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--Update the comment field on ALL orders&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderHeader&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Comment &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Unqualified Update&#39;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Update the comment field on all orders worth more than 10k&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderHeader&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Comment &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Qualified Update - High Dollar Orders&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TotalDue &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;10000.00&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Straightforward, right?  The first one sets the “Comment” field to “Unqualified Update” for all rows.  That’s a bit scary in and of itself, and more than one person has suggested a compiler option to warn about performing unqualified updates.  Unqualified updates are today’s topic though.  The second script performs a qualified update, setting Comment to a new value for all orders worth more than 10k.&lt;/p&gt;&lt;p&gt;Now how about this? &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--Update the comment for orders without a PO Number&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderHeader&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Comment &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;This order is for Territory 1&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderHeader&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.Customer &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderHeader.CustomerID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.Customer.CustomerID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.Customer.TerritoryID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Now we’ve added in a FROM clause, and we’re joining in another table (Sales.Customer) to facilitate updating the comment for customers from a single territory only.  Still no rocket science, right?  Good.&lt;/p&gt;&lt;p&gt;Howsabout this one? &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--Update the comment for orders that contain red products&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderHeader&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Comment &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;This order contains red products!&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderDetail sod&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Production.Product prod &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;sod.ProductID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;prod.ProductID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;prod.Color &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Red&#39;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Same thing, right?  We’re joining to the SalesOrderDetail table, to update the comments for orders containing red products.  Or are we?  If you’re following along at home, try running the update.  Note how many rows were updated (I get 31465 rows affected).  Now see how many records are in Sales.SalesOrderHeader.  Same number, right?  Maybe red is really popular.&lt;/p&gt;&lt;p&gt;Maybe not.  I have about 6k orders with red products on them.  So what happened?  Notice that I didn’t reference Sales.SalesOrderHeader in the FROM clause?  Ruh-roh.  We effectively coded an all-or-nothing update – if ANY of the order had red products on them, they ALL got updated.  And that’s where we get into trouble.  An UPDATE doesn’t really demand anything of the FROM clause that a SELECT statement doesn’t demand.  Technically, you don’t need to specify the table being updated in the FROM clause, as I just demonstrated.&lt;/p&gt;&lt;p&gt;There are a great many examples of this kind of ambiguous UPDATE syntax.  Sometimes the unrestricted UPDATE syntax can be a boon, but all too often it’s a source of error, when developers don’t realize that they’ve made an omission.  MVPs &lt;a href=&quot;http://sqlblog.com/blogs/adam_machanic/&quot; target=&quot;_blank&quot;&gt;Adam Machanic&lt;/a&gt;, &lt;a href=&quot;http://stevekass.com/&quot; target=&quot;_blank&quot;&gt;Steve Kass&lt;/a&gt;, &lt;a href=&quot;http://www.sql.co.il/&quot; target=&quot;_blank&quot;&gt;Itzik Ben-Gan&lt;/a&gt;, and &lt;a href=&quot;http://www.sommarskog.se/&quot; target=&quot;_blank&quot;&gt;Erland Sommarskog&lt;/a&gt; seem to be particularly skilled at finding these kinds of disasters in the making – each of them has blogged or commented about them at great length in the past.&lt;/p&gt;&lt;p&gt;One of the aforementioned fine gentlemen (and I honestly can’t remember who – I think it was Adam) suggested a method for preventing this kind of issue – to always use aliases in the update statement.  Here’s an example: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--Update the comment for orders that contain red products&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;soh&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;soh.Comment &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;This order contains red products!&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderHeader soh&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Sales.SalesOrderDetail sod &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;soh.SalesOrderID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;sod.SalesOrderID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Production.Product prod &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;sod.ProductID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;prod.ProductID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;prod.Color &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Red&#39;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;By aliasing Sales.SalesOrderHeader, I can “update the alias”, rather than “updating the table”.  In “updating the alias”, I make it impossible to omit the table being updated from the FROM clause.&lt;/p&gt;&lt;p&gt;I now use the alias syntax exclusively, when coding an UPDATE with a FROM clause.  Unless there is a very good reason not to do so, I recommend that you do the same – unless of course, you really like red products ;-)&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/5623023288728456516/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=5623023288728456516' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5623023288728456516'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5623023288728456516'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/06/tricky-updates-weird-wacky-and.html' title='Tricky Updates: Weird, Wacky, and Wonderful UPDATE Syntax Variations in SQL Server'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-1134796724223160034</id><published>2009-06-17T09:53:00.000-04:00</published><updated>2009-06-17T09:53:01.061-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Databases General"/><category scheme="http://www.blogger.com/atom/ns#" term="Fun"/><category scheme="http://www.blogger.com/atom/ns#" term="Training"/><title type='text'>So Apparently I’m On a Deserted Island…</title><content type='html'>&lt;p&gt;I’ve never really met &lt;a href=&quot;http://www.ford-it.com/sqlagentman&quot; target=&quot;_blank&quot;&gt;Tim Ford&lt;/a&gt;, but apparently he’s an ingrate.&amp;#160; About a week or so ago, he ended up on &lt;a href=&quot;http://www.ford-it.com/sqlagentman/?p=268&quot; target=&quot;_blank&quot;&gt;a deserted island&lt;/a&gt;.&amp;#160; Something about moonlighting for FedEx and running out of fuel over the Atlantic….&lt;/p&gt;  &lt;p&gt;&lt;img src=&quot;http://tbn0.google.com/images?q=tbn:kfXIckbHgwOdQM:http://www.imagebee.net/images/kmm64y1a28javh4ky2b.png&quot; width=&quot;464&quot; height=&quot;264&quot; /&gt;     &lt;br /&gt;&lt;em&gt;Tim After a Week on the Island&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Now, being upstanding members of the SQL community, we all set out in scout planes looking for him.&amp;#160; Bad idea.&lt;/p&gt;  &lt;p&gt;You see, like I said before, Tim is a bit of an ingrate.&amp;#160; If a bunch of people got together and tried to save me, I’d be pretty grateful.&amp;#160; Not Tim…he started shooting us down.&amp;#160; I have no idea how he got a catapult thrown together that quickly, but he did.&lt;/p&gt;  &lt;p&gt;Anyway, in the ensuing melee, at least a dozen good SQL Server people have crash-landed on the island.&amp;#160; And then there’s me.&lt;/p&gt;  &lt;p&gt;I know that I’ve seen the following people here, and I’m sure there are more (my RSS reader is currently sitting at 89 unread):&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href=&quot;http://thomaslarock.com/2009/06/a-lovely-bunch-of-coconuts/&quot; target=&quot;_blank&quot;&gt;Thomas LaRock&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://www.brentozar.com/archive/2009/06/give-me-a-coconut-and-six-months/&quot; target=&quot;_blank&quot;&gt;Brent Ozar&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://sqlfool.com/2009/06/chainblogging-deserted-islands-have-wifi/&quot; target=&quot;_blank&quot;&gt;Michelle Ufford&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://sqlchicken.blogspot.com/2009/06/look-boss-execution-plan-plan.html&quot; target=&quot;_blank&quot;&gt;Jorge Segarra&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://www.sqlservercentral.com/blogs/tim_mitchell/archive/2009/06/11/tagged-the-desert-island-scenario.aspx&quot; target=&quot;_blank&quot;&gt;Tim Mitchell&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://sqlinthewild.co.za/index.php/2009/06/13/lost-the-database-tales/&quot; target=&quot;_blank&quot;&gt;Gail Shaw&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://facility9.com/2009/06/05/this-is-my-island/&quot; target=&quot;_blank&quot;&gt;Jeremiah Peschka&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://www.sqlservercentral.com/blogs/cybersql/archive/2009/06/12/the-coconut-initiative.aspx&quot; target=&quot;_blank&quot;&gt;Ken Simmons&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://blog.statisticsio.com/archive/2009/06/here-on-gilligans-isle/&quot; target=&quot;_blank&quot;&gt;Jason Massie&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://robpaller.com/archive/2009/06/sabatically-speaking/&quot; target=&quot;_blank&quot;&gt;Rob Paller&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;OK, not quite a dozen.&amp;#160; Like I said, I’m sure there are more.&amp;#160; Be quiet.&lt;/p&gt;  &lt;p&gt;Anyway, every night on the island we sit around the fire and talk shop.&amp;#160; Tonight Brent Ozar kicked off a discussion on how to keep sand out of your SAN…it was riveting, to say the least.&amp;#160; Tim has this little game he likes to play after the SQL gossip dies down where he hands you a &lt;a href=&quot;http://wiki.answers.com/Q/What_does_the_conch_smybolize_in_lord_of_the_flies&quot; target=&quot;_blank&quot;&gt;conch&lt;/a&gt; and asks what you’d spend your time learning if you were on this island for the next six months.&amp;#160; When he asked me, I didn’t even have to think about it:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;How to disassemble a catapult &lt;/li&gt;    &lt;li&gt;How to swim back to the shore &lt;/li&gt;    &lt;li&gt;How to avoid sharks while swimming back to the shore &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Yeah…that didn’t go over so well.&amp;#160; Once I regained consciousness, he handed me the conch again and asked for a list of SQL SERVER topics that I’d spend my time on.&amp;#160; Oh, fine….&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Business Intelligence      &lt;ul&gt;       &lt;li&gt;Surprise, surprise.&amp;#160; Everyone else said this too.&amp;#160; In fact, I’m not waiting for a desert island – I’m starting to work on it now.&amp;#160; I’ve decided that sleep is largely discretionary. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;SQL Server Internals      &lt;ul&gt;       &lt;li&gt;Yes, I’ve read (and own) Kalen Delaney’s unparalelled “&lt;a href=&quot;http://www.microsoft.com/learning/en/us/books/7436.aspx&quot; target=&quot;_blank&quot;&gt;Inside SQL Server 2005: The Storage Engine&lt;/a&gt;”, but I’ve yet to read the &lt;a href=&quot;https://www.microsoft.com/learning/en/us/book.aspx?ID=12967&amp;amp;locale=en-us&quot; target=&quot;_blank&quot;&gt;2008 edition&lt;/a&gt;, which brings in the talents of Paul Randal, Kimberly L. Tripp, Adam Machanic, Conor Cunningham, and Ben Navarez.&amp;#160; I’m sure that the 2008 edition is even better than 2005. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;CLR      &lt;ul&gt;       &lt;li&gt;I haven’t yet had much cause to delve deeply into the CLR (Common Language Runtime), but I am continually amazed by the performance of some CLR solutions, particularly where regular expressions are involved. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Like I said, it seems like everyone is on the island now, so I’m afraid I can’t “tag” anyone.&amp;#160; Unless you’re not on the island – in which case, consider yourself tagged.&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/1134796724223160034/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=1134796724223160034' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1134796724223160034'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1134796724223160034'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/06/so-apparently-im-on-deserted-island.html' title='So Apparently I’m On a Deserted Island…'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-5518280278846796334</id><published>2009-06-10T13:13:00.001-04:00</published><updated>2009-06-10T13:13:01.035-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Best Practices"/><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>Defensive Programming – Assumptions Must be Guaranteed or Tested</title><content type='html'>&lt;p&gt;Every &lt;strike&gt;couple of weeks&lt;/strike&gt; so often, I come across a well-intentioned piece of Transact-SQL code that simply wasn’t designed with 100% reliability in mind.  I won’t go into detail about what makes a piece of TSQL well-intentioned – let’s just say that it doesn’t look malicious or anything.  Anyway, the type of code I’m talking about is the sort of thing that can easily go undetected through the application’s design, all through QC and acceptance testing, and look deceptively innocent for months – even years – until one day it breaks.  &lt;/p&gt;&lt;p&gt;Confused yet?  Good.  Here’s an example of what I’m talking about: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;DECLARE    &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@BatchToProcess &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT&lt;br /&gt;&lt;br /&gt;SET        &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@BatchToProcess &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;=    &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;/*&lt;br /&gt;&lt;br /&gt;                           Select the next batch to process.&lt;br /&gt;&lt;br /&gt;                           Only one batch can ever have (ProcessState=2)&lt;br /&gt;&lt;br /&gt;                           at a time, so this will always return a single&lt;br /&gt;&lt;br /&gt;                           BatchNumber&lt;br /&gt;&lt;br /&gt;                           */&lt;br /&gt;&lt;br /&gt;                           &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;BatchNumber &lt;br /&gt;                           &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;QueuedBatches&lt;br /&gt;&lt;br /&gt;                           &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProcessState &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;br /&gt;&lt;br /&gt;                           &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Does this look familiar?  For the more junior coders in the room, this code will run fine and dandy as long as there genuinely is never more than one batch with ProcessState = 2.  The first time there is more than one however, your application will fail, and will generally not do so in a very nice way.  As database professionals, there are a good many tools available to us to maintain the integrity of our applications and our application data – constraints, triggers, and the like.  When you use schema objects such as these to guarantee the validity of assumptions made in your application code, you’re coding defensively.  When you test any assumptions that aren’t otherwise guaranteed, you’re also coding defensively.  When you do neither of the above, you’re coding very, very optimistically, and you’ll end up soaked on the first rainy day.&lt;/p&gt;&lt;p&gt;I liken this type code to a bomb ticking on a time-delayed fuse – it may not go off today, and it may not go off tomorrow.    Heck, it may never go off.  But if and when it does, it’s almost certainly going to be a royal pain in the Heineken to clean up.  Clean up enough of them, and you’ll understand that it’s easier to prevent this kind of occurrence than it is to clean up the mess after the fact.&lt;/p&gt;&lt;p&gt;In order to avoid errors in the code written above, I need to &lt;em&gt;guarantee&lt;/em&gt; that my assumption is valid, or &lt;em&gt;test&lt;/em&gt; to make sure that it is correct.  Hence the title of this article.  I know, it was inspired.&lt;/p&gt;&lt;p&gt;Now how can I guarantee that the condition I have assumed is valid?  Off hand, I can think of two different ways – one works on any recent version of SQL Server, and the other will work on 2008+ only.  The first involves creating a trigger to roll back any transactions that try to insert a row with ProcessState = 2, or update a row’s ProcessState to 2, when a row already exists in this state.  The code for such a trigger might look something like this: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TRIGGER &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;QueuedBatches_RestrictProcessState&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;QueuedBatches&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FOR INSERT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE&lt;br /&gt;&lt;br /&gt;AS&lt;br /&gt;&lt;br /&gt;BEGIN&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Check for inserts or updates that set more than 1 row&#39;s ProcessState to 2&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;IF &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1 &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt; (&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(*)&lt;br /&gt;&lt;br /&gt;           &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;INSERTED&lt;br /&gt;&lt;br /&gt;           &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProcessState &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BEGIN&lt;br /&gt;&lt;br /&gt;       PRINT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;You cannot insert more than one record with ProcessState=2&#39;&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ROLLBACK TRAN&lt;br /&gt;&lt;br /&gt;   END&lt;br /&gt;&lt;br /&gt;   &lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Check for inserts or updates that set a row&#39;s ProcessState to 2, when&lt;br /&gt;&lt;br /&gt;   --a row already exists in that state&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;IF &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1 &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt; (   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT  &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(*) &lt;br /&gt;               &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;QueuedBatches&lt;br /&gt;&lt;br /&gt;               &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProcessState &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BEGIN&lt;br /&gt;&lt;br /&gt;       PRINT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;QueuedBatches may only ever have a single record where ProcessState=2.&lt;br /&gt;&lt;br /&gt;    A record already exists in this state.&#39; &lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ROLLBACK TRAN&lt;br /&gt;&lt;br /&gt;   END&lt;br /&gt;&lt;br /&gt;END&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Of course, you would replace the PRINT statements with whatever error handling mechanism you’re employing.&lt;/p&gt;&lt;p&gt;The second method, applicable on SQL 2008 and greater, would employ a unique filtered index to guarantee that there is never more than one record with ProcessStatus = 2.  Said index would look like this: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE UNIQUE INDEX &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;QueuedBatches_ProcessState_Unique_IX&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;QueuedBatches &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProcessState&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProcessState &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;The former method is a bit more flexible, but the latter is almost certainly more efficient.&lt;/p&gt;&lt;p&gt;If (for some reason) we are unable to implement constraints in the schema to guarantee the state of our QueuedBatches data, we can still handle the condition in code.  Rather than creating a variable and blindly selecting in (hopefully one) batch number, we could either check for duplicate batches before doing the variable assignment: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;IF &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1 &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt; (&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(*)&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;QueuedBatches&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProcessState &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BEGIN&lt;br /&gt;&lt;br /&gt;     PRINT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;QueuedBatches data is inconsistent - more than one record with BatchState = 2&#39;&lt;br /&gt;&lt;br /&gt;     &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ROLLBACK TRAN&lt;br /&gt;&lt;br /&gt;   END&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;or we could fetch the TOP BatchNumber into our scalar variable (provided that doing so will not adversely affect our application): &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;&lt;br /&gt;SET        &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@BatchToProcess &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;=    &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;&lt;br /&gt;                           &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT  TOP 1 &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;BatchNumber &lt;br /&gt;                           &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;QueuedBatches&lt;br /&gt;&lt;br /&gt;                           &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProcessState &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;br /&gt;&lt;br /&gt;                           &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;At the risk of sounding like a broken record, whatever the method you choose, you need to either guarantee or test your assumptions.  Any of the above methods is better than the “wing and a prayer” strategy that seems to have become so prevalent nowadays, and only you can decide which method is right for your applications.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Remember – that poor schmuck who gets dumped on by the user community when the app breaks and business grinds to a halt – well, that poor schmuck just may be you.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/5518280278846796334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=5518280278846796334' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5518280278846796334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5518280278846796334'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/06/defensive-programming-assumptions-must.html' title='Defensive Programming – Assumptions Must be Guaranteed or Tested'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-5671090853411165792</id><published>2009-06-09T13:45:00.001-04:00</published><updated>2009-06-09T13:45:00.473-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Performance"/><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>Full-Text Search vs Denormalized Tables vs Link Tables: Handling Tags in SQL Server</title><content type='html'>&lt;p&gt;“Tags” seem to be a very popular way of categorizing content nowadays.  And by “nowadays” I mean “for the last 20 years or so”.  But they’re especially popular today, with apps like Flickr and Picasa, sites like the &lt;a href=&quot;http://social.msdn.microsoft.com/Forums/en-US/categories/&quot; target=&quot;_blank&quot;&gt;MSDN Forums&lt;/a&gt; and &lt;a href=&quot;http://www.stackoverflow.com/&quot; target=&quot;_blank&quot;&gt;StackOverflow&lt;/a&gt;, and even the blog post that you’re reading, all using tags to categorize content.&lt;/p&gt;&lt;p&gt;So how can we handle tags efficiently in SQL Server?  Well, let’s take a look at the problem first:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Tags will fit into a conventional datatype definition.  In fact, most tags generally consume 50 characters or less. &lt;/li&gt;&lt;li&gt;The relationship between an item and the number of tags that item has is &lt;em&gt;not&lt;/em&gt; consistent.  In most applications,  an item can have as few as zero, and as may as ten different tags. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The first attribute is OK, but the second one presents a bit of a storage dilemma, doesn’t it?  I can think of three (reasonable) solutions to handle this, because ultimately a tag/item relationship is not too different from a parent/child relationship.  Given that assumption, we could use:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A classic parent/child relationship, supported with a link table:&lt;br /&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--&quot;Tags&quot; table used for the &quot;link table&quot; tag reference method.&lt;br /&gt;&lt;br /&gt;--We&#39;ll also use it to populate the full text table (see next method)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TABLE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;IDENTITY&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;PRIMARY KEY CLUSTERED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   )&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Table for use with Link Table&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TABLE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithoutTags&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;IDENTITY&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;PRIMARY KEY CLUSTERED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemText &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   )&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Link Table&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TABLE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemTagXRef&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NOT NULL &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FOREIGN KEY REFERENCES &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithoutTags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NOT NULL &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FOREIGN KEY REFERENCES &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CONSTRAINT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PK_ItemTagXRef_ItemID_TagID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;PRIMARY KEY NONCLUSTERED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   )&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE INDEX &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CX_ItemTagXRef_ItemID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemTagXRef&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;This solution stores items in one table (pictures, blog posts, forum entries, etc), tags in a second table, and uses a link table to store item/tag relationships.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;A single column with a full-text index: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TABLE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithFTSTags&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;IDENTITY&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;PRIMARY KEY NONCLUSTERED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemText &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   )&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE UNIQUE CLUSTERED INDEX &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PK_ItemsWithFTSTags_ItemID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithFTSTags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;);&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;FULLTEXT CATALOG FTCatDemo &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS DEFAULT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;FULLTEXT &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INDEX ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithFTSTags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;KEY INDEX &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PK_ItemsWithFTSTags_ItemID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;To employ this type of solution, we would need to delimit the tags, for instance: &amp;lt;Tag1&amp;gt;&amp;lt;Tag2&amp;gt;&amp;lt;Tag3&amp;gt;&lt;/p&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;A denormalized items table with one column for each possible tag: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TABLE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithInlineTags&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;IDENTITY&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;PRIMARY KEY CLUSTERED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemText &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tag1 &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tag2 &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tag3 &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tag4 &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   )&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;p&gt;There are probably more, but these three are what 90% of designers will come out with.  Each solution has it’s strengths and weaknesses.  The Item/Tag/Link solution allows for a true 0-n relationship, without necessitating schema modification as the number of tags increases.  This may not be a requirement for every application, but it’s good to be able to increase the number of permitted tags without having to modify the schema.  The Full Text Search solution uses only one table (remember, joins aren’t free), and allows us the flexibility of the full-text operators to do our tag search (though that flexibility may not be required in this situation).  The denormalized table requires only one table, and doesn’t have a dependency on full-text search.&lt;/p&gt;&lt;p&gt;The next logical question would be: how do they perform?  While I was designing a test to answer this very question, it struck me that the denormalized table solution was…well…stupid.  I was sitting trying to figure out the best way to index four columns, each of which is potentially nullable, each of which could be the target of a given search (WHERE Tag1 = ‘Java’ OR Tag2 = ‘Java’ OR ….).  The solution started to feel like a stretch.  One of our challenges as developers and designers is to learn when an idea, no matter how immaculate it seemed when it was conceived, needs to be killed off.  When the only thing providing a solution with legitimacy is my desire for it to be a good solution, it&#39;s time to kick it to the curb.  And that’s just what I did here.&lt;/p&gt;&lt;p&gt;&lt;em&gt;And then there were two…&lt;/em&gt;&lt;/p&gt;&lt;p&gt;To create a decent test, I decided to load about 500 tags and 10,000 items.  I “borrowed” the tags from a list of programming languages on Wikipedia (truncated so the post wouldn’t turn into a miniseries, but feel free to contact me or leave a comment if you&#39;d like the script): &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--First, the Tags table.  I need a decent dataset, so I&#39;m passing a list&lt;br /&gt;&lt;br /&gt;--of programming languages that I &quot;borrowed&quot; from &lt;br /&gt;--Wikipedia (http://en.wikipedia.org/wiki/List_of_programming_languages)&lt;br /&gt;&lt;br /&gt;--567 rows in all.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;A# (Axiom)&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;A# .NET&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;A+&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;A++&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;A-0&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;ABAP&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;ABC&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;ABC ALGOL&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;.....&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;XQuery&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;XSLT - See XPath&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Y&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;YACC&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Yorick&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Z&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Z notation - like UML.&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Zonnon&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;ZOPL&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;ZPL&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;ZZT-oop&#39;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;I then populated ItemsWithoutTags, ItemTagXRef and ItemsWithFTSTags using a bit of loop logic and RAND().  I know, every time you loop in SQL Server, an angel loses it’s wings.  I’m sorry, but RAND() only evaluates once per statement, which necessarily meant that I had to run the statement 10,000 times.  Here’s the script: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--Now for the items being tagged.  10k for each table,&lt;br /&gt;&lt;br /&gt;--with 1-4 tags per item.&lt;br /&gt;&lt;br /&gt;--2500 items each with 1,2,3 and 4 tags.  (Hence the modulo logic below)&lt;br /&gt;&lt;br /&gt;--I&#39;m using a standard numbers table for enumeration&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithoutTags &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemText&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;REPLICATE&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;A&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Numbers&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Number &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;10000&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Crude, but effective tagging script&lt;br /&gt;&lt;br /&gt;--You&#39;ll get a few errors for duplicate tags&lt;br /&gt;&lt;br /&gt;--on a given item, but only a few - statistically&lt;br /&gt;&lt;br /&gt;--1/567 * the number of tags for a given item,&lt;br /&gt;&lt;br /&gt;--* the number of items&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;DECLARE    &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT&lt;br /&gt;&lt;br /&gt;DECLARE    &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@NumberOfTags &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT&lt;br /&gt;&lt;br /&gt;DECLARE    &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SET        &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@NumberOfTags &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(*) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET        &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET        &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHILE  &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;10000&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BEGIN&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   WHILE   &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt;= (&lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BEGIN&lt;br /&gt;&lt;br /&gt;       INSERT  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemTagXRef&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;TagID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT  &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;br /&gt;               &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;CAST&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;RAND&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;() * &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@NumberOfTags &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS INT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;END&lt;br /&gt;&lt;br /&gt;   &lt;br /&gt;   SET &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1  &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;END&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Now to populate the full-text table.&lt;br /&gt;&lt;br /&gt;--Again, 2500ish each with 1, 2, 3, and 4 tags.&lt;br /&gt;&lt;br /&gt;--We&#39;ll delimit the tags as such: ztagz&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET        &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@NumberOfTags &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(*) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;DECLARE    &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@TagString &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;250&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET        &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@TagString &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;&#39;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHILE  &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;10000&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BEGIN&lt;br /&gt;&lt;br /&gt;   WHILE   &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt;= (&lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BEGIN&lt;br /&gt;&lt;br /&gt;       SET &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@TagString &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@TagString &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;br /&gt;                       (&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;z&#39; &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;t.TagName &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;z&#39;&lt;br /&gt;&lt;br /&gt;                       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;t&lt;br /&gt;&lt;br /&gt;                       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;t.TagID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;CAST&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;RAND&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;() * &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@NumberOfTags &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS INT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;))&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;END&lt;br /&gt;&lt;br /&gt;   INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithFTSTags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemText&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT  &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;REPLICATE&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;a&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@TagString&lt;br /&gt;&lt;br /&gt;   &lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@LoopCounter &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@TagString &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;&#39;&lt;br /&gt;&lt;br /&gt;   &lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@CurrentTagItem &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;END&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Still with me?  OK, nobody has dozed off – must be doing something right.&lt;/p&gt;&lt;p&gt;Notice the bit of a hack required to store the tags in the full text table?  I’ve delimited the tag with a “z” on either end.  By default, querying full text search via the CONTAINS operator will find any block of text containing a given word, so a search for “Java” will find not only the “Java” tag, but also “General Java”, “Join Java”, and the like.  “Z” isn’t the best choice of delimiters, however you’ll find that SQL Server strips out most symbols when it indexes a column, so (for instance) storing the tags separated by vertical pipes won’t do you any good.  Score 1 for the ItemXRef solution – we don’t need to muck around with it in order to get it to work, which leaves far more time for intelligent development and optimization.&lt;/p&gt;&lt;p&gt;Now let’s try querying both tables, for items tagged with the tag “Java”:&lt;br /&gt;&lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;i.&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;*&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithoutTags i&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemTagXRef itx &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;i.ItemID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;itx.ItemID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Tags t &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;itx.TagID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;t.TagID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;t.TagName &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Java&#39;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;i.&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;*&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ItemsWithFTSTags i&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  CONTAINS&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;i.Tags&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;&quot;zJavaz&quot;&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;The full-text syntax is a little painful, but I can live with it.  &lt;/p&gt;&lt;p&gt;Of course, a key factor in the suitability of any solution is it’s performance, so I wrapped each query in a loop, and executed them 100x apiece for five iterations each.  Here’s what I found:&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_GyTlY8-ysTw/Si6PEjpXBLI/AAAAAAAAAJA/ZkHoPpzSnT4/s1600-h/image3.png&quot;&gt;&lt;img style=&quot;BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_GyTlY8-ysTw/Si6PFH-PHAI/AAAAAAAAAJE/w1u-E2LcyiE/image_thumb1.png?imgmax=800&quot; width=&quot;284&quot; height=&quot;218&quot; /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Pretty comparable, in the end.  Of course, YMMV when two solutions are this close to each other, so if you’re considering applying one of these techniques, I recommend testing it in your environment with realistic sample data.  If the difference is negligible, the XRef solution will most likely tip the scales as a result of it’s flexibility and ease of coding.&lt;/p&gt;&lt;p&gt;Until next time, &lt;a href=&quot;http://www.youtube.com/watch?v=w7CclVneVpw&quot;&gt;that&#39;s all folks&lt;/a&gt;.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/5671090853411165792/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=5671090853411165792' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5671090853411165792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5671090853411165792'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/06/full-text-search-vs-denormalized-tables.html' title='Full-Text Search vs Denormalized Tables vs Link Tables: Handling Tags in SQL Server'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh6.ggpht.com/_GyTlY8-ysTw/Si6PFH-PHAI/AAAAAAAAAJE/w1u-E2LcyiE/s72-c/image_thumb1.png?imgmax=800" height="72" width="72"/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-243054712774271931</id><published>2009-06-04T10:45:00.000-04:00</published><updated>2009-06-04T10:45:00.287-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Training"/><title type='text'>Microsoft DPE Releases SQL Server 2008 Training Kit</title><content type='html'>&lt;p&gt;One of the best things about working with Microsoft technologies is the absolute wealth of information provided, in many cases for free, to developers and IT professionals.&amp;#160; A great example of this is the &lt;a href=&quot;http://www.microsoft.com/downloads/details.aspx?displaylang=en&amp;amp;FamilyID=e9c68e1b-1e0e-4299-b498-6ab3ca72a6d7&quot; target=&quot;_blank&quot;&gt;SQL Server 2008 Training Kit&lt;/a&gt;, which was released by Microsoft DPE (Developer and Platform Evangelism) just a couple of weeks ago.&lt;/p&gt;  &lt;p&gt;The training kit is a set of code samples, presentations, and demo scripts designed to facilitate quick and easy presentations on some of the fun new features in SQL Server 2008.&lt;/p&gt;  &lt;p&gt;The download is about 50 megs, and installs within a matter of minutes.&amp;#160; The content is accessed via an html page which is included in the installer.&amp;#160; It breaks the content down into a number of sections, each accessible via the menu bar at the top of the screen:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_GyTlY8-ysTw/SifQKC_FW4I/AAAAAAAAAIs/QFKaeZ6lPcI/s1600-h/image3.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/_GyTlY8-ysTw/SifQKtmNr5I/AAAAAAAAAIw/0T3saKSzxqQ/image_thumb1.png?imgmax=800&quot; width=&quot;595&quot; height=&quot;211&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Drill into a topic, such as T-SQL, and you’re faced with a listing of all of the content in that subject matter area:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/_GyTlY8-ysTw/SifQLsuZsuI/AAAAAAAAAI0/JAcQqHTVU20/s1600-h/image7.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/_GyTlY8-ysTw/SifQMMSBkqI/AAAAAAAAAI4/3l9R4ghV9lo/image_thumb3.png?imgmax=800&quot; width=&quot;461&quot; height=&quot;341&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;The presentations are provided as PowerPoint files (Office 2007 – pptx), and the demos include both a demo script (in html or Word format) and a source code listing (for the TSQL examples, the source code is actually provided as a .SQL file).&lt;/p&gt;  &lt;p&gt;The demo scripts take you through an entire presentation on the subject at hand.&amp;#160; For smaller topics, such as Row Constructors, you’re basically doing through the entire subject, from start to finish.&amp;#160; Larger topics (SQL Server Spatial, for instance) obviously aren’t covered in their entirety, but a good introduction is provided, and it is enough to pique a group’s interest, or to provide an introduction into some of the cooler features in SQL Server 2008.&lt;/p&gt;  &lt;p&gt;These presentations and demos are great for user groups, internal training events, or even for personal use as a quick introduction to a SQL 2008 feature.&amp;#160; The download is very well presented and clearly represents a lot of effort on the part of DPE.&lt;/p&gt;  &lt;p&gt;Support your own geekiness – download the training kit and have fun!&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/243054712774271931/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=243054712774271931' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/243054712774271931'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/243054712774271931'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/06/microsoft-dpe-releases-sql-server-2008.html' title='Microsoft DPE Releases SQL Server 2008 Training Kit'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh3.ggpht.com/_GyTlY8-ysTw/SifQKtmNr5I/AAAAAAAAAIw/0T3saKSzxqQ/s72-c/image_thumb1.png?imgmax=800" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-9181918115067472003</id><published>2009-05-26T10:00:00.001-04:00</published><updated>2009-05-26T20:58:49.949-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Database Engine"/><category scheme="http://www.blogger.com/atom/ns#" term="Performance"/><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>Easy On The Updates There, Sparky</title><content type='html'>&lt;p&gt;Ahh, the UPDATE statement. Do you ever stop to think of where we would be without the UPDATE statement? Can you imagine writing a DELETE followed by an INSERT every time we needed to modify a data row? I thought not. The very word “update” rings of timeliness, relevance, and light. Unfortunately, as is the case with all things, it’s all too easy to go overboard, and you can have too much of a good thing.&lt;/p&gt;&lt;p&gt;You see, to the unsuspecting database developer, it may seem that some operations in SQL Server are more or less “free”. Let’s clear the air on that one – nothing is free, ever. Or if it is, it usually has a 30 day limit. It’s easy to forget this, because when you’re working with something like SQL Server, it’s hard to imagine that a sub-second response time can hide anything of significant concern.&lt;/p&gt;&lt;p&gt;A SQL Server update is actually a relatively “expensive” operation, and as such needs to be done only when necessary. Wondering what I’m blathering on about? OK, let me ask you a question – which of the following two statements is more expensive? &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em;font-size:12px;&quot; &gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Customer&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AccountStatus &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;LastPurchaseDate &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;20090101&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;AND    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CurrentBalance &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em;font-size:12px;&quot; &gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Customer&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AccountStatus &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;LastPurchaseDate &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;20090101&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;AND    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CurrentBalance &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;AND    &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AccountStatus &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;(For those of you who haven’t yet had your coffee today, the second statement has an extra line).&lt;/p&gt;&lt;p&gt;Your choices are:&lt;br /&gt;- 1&lt;br /&gt;- 2&lt;br /&gt;- It Depends&lt;/p&gt;&lt;p&gt;How many of you chose “it depends”? It’s the safe answer, so I’ll wager that most of you did. And I’m sure you’re right, because there are &lt;a href=&quot;http://en.wikipedia.org/wiki/Edge_case&quot; target=&quot;_blank&quot;&gt;edge cases&lt;/a&gt; for everything. But let’s get real and talk about the 99.99% of cases that you should concern yourself with.&lt;/p&gt;&lt;p&gt;The correct answer is “1”.&lt;/p&gt;&lt;p&gt;Don’t believe me? OK, let’s dig a bit deeper. The first batch is, in plain English, setting the “status” flag for all customers who haven’t purchased in this calendar year, and don’t owe us anything, to “0” (0 = Inactive, which you’ll see when I post the sample data script). The second batch is setting the “status” flag for all customers who haven’t purchased in this calendar year, and don’t owe us anything, &lt;em&gt;and who aren’t already inactive&lt;/em&gt;, to zero.&lt;/p&gt;&lt;p&gt;So what’s the big deal? If it’s already set to “0”, SQL Server will just pass it by, won’t it?&lt;/p&gt;&lt;p&gt;No, no it won’t. SQL Server performs every update in exactly the same manner, regardless of whether or not the “new” value is any different from the “old” one. The following is an oversimplification of what happens:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;SQL Server acquires an Intent Exclusive (IX) lock on the rows or pages being modified. Note that an IX lock is NOT compatible with a shared (S) lock, so &lt;strong&gt;your update will block readers, even if it isn’t “changing” the value&lt;/strong&gt;. This also means that your update can be blocked by readers, even if it doesn’t really need to update those rows. When SQL Server is ready to perform the update, it converts the IX lock into an Exclusive (X) lock.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The actual update occurs. SQL Server implements an update internally as a “delete” followed by an “insert”. ALL rows covered by the UPDATE statement are deleted and inserted, regardless of whether or not the value has actually changed. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Any update triggers which are defined on the table fire. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;What’s that? You don’t believe that SQL Server actually updates the rows, even if the value hasn’t changed? Check it out for yourself. In the following example, we create a table called “Customer”. No comments on the schema – this isn’t a database design post, and it’s an easy example to work with. On said table, we create a FOR UPDATE trigger, which will fire any time &lt;strong&gt;an update is made&lt;/strong&gt;. Have a look at the results:&lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em;font-size:12px;&quot; &gt;&lt;span style=&quot;color:green;&quot;&gt;--Create a table for testing&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TABLE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Customer&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CustomerID          &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;IDENTITY&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) NOT NULL &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;PRIMARY KEY CLUSTERED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CustomerName        &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;LastPurchaseDate    datetime &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NULL,&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CurrentBalance      decimal&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;18&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AccountStatus       bit &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NOT NULL &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;DEFAULT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0  &lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;-- 0 = Inactive, 1 = Active&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Create a trigger on our test table. &lt;br /&gt;--This trigger fires whenever an update is actually performed&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TRIGGER &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Customer_Update&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON     &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Customer&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FOR    UPDATE&lt;br /&gt;&lt;br /&gt;AS&lt;br /&gt;&lt;br /&gt;BEGIN&lt;br /&gt;&lt;br /&gt;       DECLARE &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@RowsDeleted &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@RowsInserted &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT&lt;br /&gt;&lt;br /&gt;       SET     &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@RowsDeleted &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(*) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DELETED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET     &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@RowsInserted &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(*) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;INSERTED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;      &lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;PRINT   &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Rows Deleted: &#39; &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;CAST&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@RowsDeleted &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)) + &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;13&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) +&lt;br /&gt;&lt;br /&gt;               &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Rows Inserted: &#39; &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;CAST&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@RowsInserted &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;))&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;END&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Insert a test row.  Note that the AccountStatus is 0&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Customer &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CustomerName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;LastPurchaseDate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CurrentBalance&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AccountStatus&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;Trapper Sam&#39;&#39;s House of Peameal Bacon&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;20081215&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--Perform an update&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UPDATE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Customer&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SET        &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AccountStatus &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;LastPurchaseDate &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;&amp;lt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;20090101&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;AND        &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;CurrentBalance &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;/*&lt;br /&gt;&lt;br /&gt;Zut Alors!&lt;br /&gt;&lt;br /&gt;Rows Deleted: 1&lt;br /&gt;&lt;br /&gt;Rows Inserted: 1&lt;br /&gt;&lt;br /&gt;(1 row(s) affected)&lt;br /&gt;&lt;br /&gt;*/&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;I hate to say I told you so…. ;-)&lt;br /&gt;&lt;br /&gt;Now if you’re following along at home, don’t drop that table quite yet – we’re not done with it.&lt;/p&gt;&lt;p&gt;Now note that, in order to prove to you that the rows were updated, we also demonstrated that any triggers defined on the table are fired. In the event of an update that actually changes 0 rows, but updates &amp;gt;0 rows, the trigger will fire unnecessarily.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;strong&gt;Lock Escalation&lt;br /&gt;&lt;/strong&gt;Remember &lt;a href=&quot;http://blogs.msdn.com/sqlserverstorageengine/archive/2006/05/17/Lock-escalation.aspx&quot; target=&quot;_blank&quot;&gt;lock escalation&lt;/a&gt;? Basically, SQL Server consumes resources in order to place and track locks. For a large query, the resources (memory and CPU, in specific) required to maintain a significant number of locks is not insignificant. Ideally, row locks are most conducive to concurrency, as the chances of one request blocking another is reduced. Unnecessarily updating rows can cause the lock manager to select page locks over row locks, or to escalate your row/page locks to table locks!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Transactional and Merge Replication&lt;/strong&gt;&lt;br /&gt;Although they employ different mechanisms, both transactional and merge replication will replicate all updates on published articles – with no regard to whether or not the row’s data was actually updated. This isn’t a failing on the part of the replication agents – it’s simply the most efficient way for them to work. If you’re “updating” rows unnecessarily, you’re replicating useless updates – you didn’t “change” anything, but it’s published anyway!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Database Mirroring&lt;br /&gt;&lt;/strong&gt;Do you use database mirroring? If you do, I’m sure you know that mirroring uses the SQL Server transaction log to keep the mirror database in sync with the primary. Just as with replication, if you’re performing unnecessary updates, you’re now adding to your mirror load as well.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Database Snapshots&lt;/strong&gt;&lt;br /&gt;Database Snapshots use a copy-on-write mechanism to maintain a point-in-time record of a database’s pages. When a page in the source database is written to, the page is first copied to the snapshot. As a result of this copy-on-write mechanism, you’re pushing rows that weren’t really changed into the snapshot! (Thanks to &lt;a href=&quot;http://sqlskills.com/blogs/paul/&quot; target=&quot;_blank&quot;&gt;Paul Randal&lt;/a&gt; for confirming this).&lt;/p&gt;&lt;br /&gt;&lt;p&gt;So how do we prevent this kind of carnage? Well, as the code example above shows, all we need to do is add the column(s) being updated to the WHERE clause. Keep in mind that application code often doesn’t need to deal with this, because the data access components only pass back updates for rows that were actually changed. When in doubt it’s best to make sure that the application &lt;em&gt;is &lt;/em&gt;doing this kind of “intelligent updating”, as it can have a significant impact on your application’s throughput.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Ladies and Gentlemen, that’s all for today. Please sign the guestbook on your way out.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/9181918115067472003/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=9181918115067472003' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/9181918115067472003'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/9181918115067472003'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/05/easy-on-updates-there-sparky.html' title='Easy On The Updates There, Sparky'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-5047257500422514996</id><published>2009-05-26T09:40:00.000-04:00</published><updated>2009-05-26T09:40:00.041-04:00</updated><title type='text'>Memorial Day Book Review: Juno Beach (And Making a Habit of Thinking Every Day)</title><content type='html'>&lt;p&gt;I don’t normally do book reviews – more than anything because I’m not entirely sure people will want to read them.  I’m making an exception this time though, in honor of Memorial Day.  Memorial Day is an American holiday, and I’m Canadian – you’ll see in a bit why that doesn’t much matter.&lt;/p&gt;&lt;p&gt;I happened to be at one of the local University libraries a few weeks ago and noticed a book called &lt;a href=&quot;http://www.zuehlke.ca/index.php/Canadian-Battle-Series/juno-beach.html&quot; target=&quot;_blank&quot;&gt;Juno Beach: Canada’s D-Day Victory: June 6, 1944&lt;/a&gt;.  Normally I would have just kept browsing, but something appealed to me about this book.  I picked it up and looked it over, and the most remarkable thing was that this 414 page paperback was written entirely about a single day, the day of the allied landings in Normandy.&lt;/p&gt;&lt;p&gt;For those who aren’t aware, the allied assault was divided into five areas, and the beaches that they landed on were named accordingly: the Americans took Omaha and Utah, the British had Sword and Gold, and finally the Canadians were assigned to Juno beach.&lt;/p&gt;&lt;p&gt;I won’t go into exhaustive detail about the book, but one thing should be mentioned:  the author (Mark Zuehlke) combined official record with veteran interviews to compile his work.  Where multiple, independent veterans agreed on a detail that did not agree with the official record, the author favoured the veterans’ accounts.  This is an important detail, for nobody knows what happened better than the ones who lived through it, and official record is often coloured by the desire to protect the reputations of the people involved.  As these veterans disappear, accounts like this will be all we have to remember what they went through.&lt;/p&gt;&lt;p&gt;Now just to give you an idea of what the men who were assigned the task of taking Juno (and the other D-Day beaches) faced, let me relay just two points.&lt;br /&gt;&lt;br /&gt;First, in order to make it safe(r) for the assault on the beach to proceed, a fleet of minesweeping vessels was assigned to clear the water leading up to said beach.  These minesweepers were to steam toward the beach in a staggered line, and each ship cleared the path in front of the ship behind it.  This meant that the leading ship had no cover whatsoever – it was sailing directly into a heavy minefield.  Once the ships approached the beach, they were to turn at 90 degrees and sweep laterally across the water, a manoeuvre which exposed the broad side of the ships to the formidable German batteries onshore.  Prior to setting out across the English channel, the men crewing the ships were given an assessment of their chances.  Headquarters had forecasted that 75% of the ships (and their entire crew) involved in the clearing operation would be lost as a result of enemy mines or land-based batteries.  The leading ship’s crew was told that there was very little chance that they would survive the clearing operation.  Each ship was informed that if they were disabled or damaged, the Navy destroyers waiting behind would be forced to blast the ship out of the water, so that it didn’t impede the landing crafts’ progress.  After the odds were communicated to the men, they were each given the opportunity to stand aside, and be assigned to a safer detail in England.  Not_a_single_one_of_them chose to stand aside.&lt;br /&gt;&lt;br /&gt;The second detail that I’d like to convey is about the landing itself.  In the first hour of the assault, the Canadians who waded ashore onto Juno faced a full 50% casualty rate.  That is to say, if you and the person standing beside you stepped off the landing craft, one of you would more than likely not make it through the next three or four minutes.  These people had trained together for years in England, and all the while had been away from their families and their homes.  They had become like family, and yet they had to keep going, even when they watched their fellow solders get “cut to pieces” as they waded ashore.  In the end they succeeded in their efforts and won the day, but they could never be the same after living through what they did.&lt;/p&gt;&lt;p&gt; &lt;/p&gt;&lt;p&gt;Now why did I feel it was necessary to bring those points up?  Well, about a week ago I was driving to work along a semi-residential road.  The speed limit was 50km/h, and of course traffic was moving at closer to 65 km/h.  The road narrowed from two lanes in either direction to one, and I found myself behind a vehicle that was going about 40km/h.  I was just starting to get annoyed when I looked down at the license plate – it was the Ontario Veteran Graphic License Plate:&lt;/p&gt;&lt;p&gt;&lt;img border=&quot;0&quot; alt=&quot;Veteran&#39;s Licence Plate&quot; src=&quot;http://www.culture.gov.on.ca/seniors/images/programs/veterans/veteransweek/veteran.LicensePlate.gif&quot; width=&quot;276&quot; height=&quot;143&quot; /&gt;&lt;/p&gt;&lt;p&gt;It struck me as kind of sad that my initial reaction was annoyance, before I even took the time to really look at what was in front of me.  I know I’m not alone, because while I gave the gentleman space and followed behind him patiently, a line of cars behind me was angrily jostling and tailgating, trying to get me to speed up.  In a few moments, the veteran pulled off into the parking lot of one of our municipal greenhouses – likely one of the few things he really looks forward to at this point in his life.  I wondered whether the people that were pushing behind me would have given him the distance and respect that I did.  Then I wondered whether or not I would have if I hadn’t been reading about Juno Beach.  Neither of those are very comfortable thoughts, are they?&lt;/p&gt;&lt;p&gt;I hope that this doesn’t come across as preachy, as that’s the furthest thing from my mind, but how often do you really think about the lives that were lost, &lt;em&gt;and continue to be lost&lt;/em&gt;, by people who believe in something greater than themselves, and are willing to give their lives to protect it?  I know that for me it has always meant nothing more than a poppy on my collar and a few minutes of silence once a year.  Sadly, Canadian schools focus the majority of their time and effort in history class on the settling of Canada, and precious little time on the events that are close enough and still real enough to honestly give us pause.  I believe that everyone should be taught about the sacrifices that went into making our world what it is today.  If they did, maybe people would have a little more respect for the place that they call home, and the people that they meet as they go through their lives.  After all, if our world was worth dying for, isn’t it worth at least pausing to think about, just once in a while?&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/5047257500422514996/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=5047257500422514996' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5047257500422514996'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5047257500422514996'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/05/memorial-day-book-review-juno-beach-and.html' title='Memorial Day Book Review: Juno Beach (And Making a Habit of Thinking Every Day)'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-1966822571083066894</id><published>2009-05-21T12:21:00.001-04:00</published><updated>2009-05-21T12:21:00.322-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Fun"/><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>Identifying Items with Matching Join Sets In SQL Server</title><content type='html'>&lt;p&gt;Here’s a neat problem: Identify the records in Table A which join to the same set of records in Table B.  That is to say, produce a listing of records from TableA, grouped by &lt;em&gt;the set of records that they join to&lt;/em&gt; in Table B.&lt;/p&gt;&lt;p&gt;This problem came up on StackOverflow the other day, and it was actually quite a lot of fun to resolve.  Let’s see if I can somehow relay the convoluted thinking that led me to my conclusion:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;What a weird requirement.  I wonder if this is a homework question.&lt;/li&gt;&lt;li&gt;A homework question would have better table names.&lt;/li&gt;&lt;li&gt;Or at least I would hope so.&lt;/li&gt;&lt;li&gt;But I’d likely be wrong.&lt;/li&gt;&lt;li&gt;Geez..in the time I’ve spent debating whether or not to solve this question, I could have solved this question.&lt;/li&gt;&lt;li&gt;Time to don the Serious Hat.&lt;/li&gt;&lt;li&gt;If I can somehow summarize the joins that an item makes into a single, discrete value, I can compare that item with the balance of the items to identify matches. &lt;/li&gt;&lt;li&gt;If I can then consolidate like items into a single data value, I can produce a listing to show “items by join set”. &lt;/li&gt;&lt;li&gt;But how do I consolidate items and joins? &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I thought about this for a minute, and then I remembered &lt;a href=&quot;http://sqlblog.com/blogs/arnie_rowland/&quot; target=&quot;_blank&quot;&gt;SQL Server MVP Arnie Rowland’s&lt;/a&gt; excellent article on the Transact-SQL Wiki: &lt;a href=&quot;http://code.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=CreateACommaDelimitedList&amp;amp;referringTitle=Home&quot; target=&quot;_blank&quot;&gt;Create a Comma-Delimited List From a Column in a Table&lt;/a&gt;.  I’ve actually used his solution in so many different situations now that I’ve reserved a special place in my (limited) memory bank for it.  If you haven’t read it, take a few minutes to do so.&lt;/p&gt;&lt;p&gt;So here was my solution.  First, let’s have a look at the sample data I used.  The poster had two tables – Area, and AreaToProduct.  The former contained the Area master record, while the latter contained relationships between areas and products. &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TABLE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Area&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NOT NULL &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;PRIMARY KEY CLUSTERED&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Name &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   )&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE TABLE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaToProduct&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NOT NULL &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FOREIGN KEY REFERENCES &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Area&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProductID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NOT NULL&lt;br /&gt;&lt;br /&gt;   )&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Area&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Name&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;SectionA&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;SectionB&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;SectionC&#39;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;SectionD&#39;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INSERT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaToProduct&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProductID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;br /&gt;&lt;br /&gt;GO&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;So based upon this sample data, Sections A and C should have “matching” join sets, while Sections B and D should stand alone.  Here’s what I worked out: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:gray;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WITH &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaProductSets&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--This CTE aggregates the joins&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;a.AreaID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;a.Name&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,                         &lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;SUBSTRING&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;                   (&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;, &#39; &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;CAST&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ap.ProductID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)))&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaToProduct ap&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ap.AreaID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;a.AreaID&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ORDER BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ap.ProductID&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FOR &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;XML PATH &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)), &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProductArray&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM            &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Area &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--The main query uses the aggregated joins to produce an aggregation&lt;br /&gt;&lt;br /&gt;--of the Area names&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT  &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;SUBSTRING&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;                   (&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT     &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;, &#39; &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:magenta;&quot;&gt;CAST&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;aps2.Name &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)))&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM        &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaProductSets aps2&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE       &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;aps2.ProductArray &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;aps.ProductArray&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ORDER BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;aps2.AreaID&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FOR &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;XML PATH&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)), &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;MatchingAreas&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,                        &lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;aps.ProductArray&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM               &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT DISTINCT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ProductArray &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AreaProductSets&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;aps&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;So, as the code comments say, the Common Table Expression creates a listing of Product Set arrays, using Arnie’s pattern.  The main query then produces a listing of areas for each distinct product set.  The results are just what I was aiming for:&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://lh4.ggpht.com/_GyTlY8-ysTw/ShVxuh4gxxI/AAAAAAAAAIk/CPz1URoyAA4/s1600-h/image%5B3%5D.png&quot;&gt;&lt;img style=&quot;BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/_GyTlY8-ysTw/ShVxvLNWiZI/AAAAAAAAAIo/fNuBeYIHXME/image_thumb%5B1%5D.png?imgmax=800&quot; width=&quot;428&quot; height=&quot;205&quot; /&gt;&lt;/a&gt; &lt;/p&gt;&lt;br /&gt;&lt;p&gt;You can use this kind of logic in data cleansing as well, to identify duplicate elements based upon their relationships.&lt;/p&gt;&lt;p&gt;So there you have it - a neat solution for matching sets, and a whirlwind tour through the strange and sometimes frightening mind of a SQL Server Geek ;-)&lt;/p&gt;&lt;p&gt;I&#39;d love to see what kind of solutions you can drum up to address this problem...&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/1966822571083066894/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=1966822571083066894' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1966822571083066894'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/1966822571083066894'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/05/identifying-items-with-matching-join.html' title='Identifying Items with Matching Join Sets In SQL Server'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh4.ggpht.com/_GyTlY8-ysTw/ShVxvLNWiZI/AAAAAAAAAIo/fNuBeYIHXME/s72-c/image_thumb%5B1%5D.png?imgmax=800" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-2115257753950283895</id><published>2009-05-19T22:01:00.001-04:00</published><updated>2009-05-19T22:01:00.298-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Technical Minutiae"/><title type='text'>Wolfram|Alpha: Move Over Google? I Think We’re Missing The Point.</title><content type='html'>&lt;p&gt;In case you’ve been living under a rock for the past week (or offline – same thing in this day and age), the highly anticipated WolframAlpha was released on Friday.  If it sounds like Ich spreche Deutsch, have a look &lt;a href=&quot;http://www.wolframalpha.com/screencast/introducingwolframalpha.html&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt; for the excellent introductory screencast by Stephen Wolfram.&lt;/p&gt;&lt;p&gt;Soon after it’s release, the “blogosphere” lit up with articles both proclaiming, and disclaiming, WolframAlpha as the next Google killer.  It’s a reasonable comparison – after all, Google is our current standard for knowledge acquisition on the internet.  If we want to see a trailer for the new Star Trek movie, we start on Google.  If we want to find out the syntax, pros, cons, and performance characteristics for a SQL construct, we start on Google.  If we want to find out the molecular composition, basic properties, and thermodynamic characteristics of Carbon Monoxide, we start on Google.  In fact, if you’re looking for just about anything on the internet, you’re almost certainly going to start on Google.  And so, with this broad brush of generalization, we pitch WolframAlpha into the ring with Google.  With the same unfair broad brush of generalization, I’ll summarize the majority of the blog posts that were written on this modern-day David vs Goliath matchup:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Pro-Google Perspective&lt;/strong&gt;&lt;br /&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Google is the internet standard &lt;/li&gt;&lt;li&gt;WolframAlpha doesn’t work for most common search topics &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;The Pro-WolframAlpha Perspective&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;WolframAlpha synthesizes, aggregates, and presents relevant information on a search topic, saving time and “making sense” of related information. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Sure, they were more long-winded than that, but you get the idea.&lt;/p&gt;&lt;p&gt;Which brings me to the point of this article – comparing Google to WolframAlpha is like comparing Apples to Ostriches, and bears roughly the same chance of producing intelligent insights.&lt;/p&gt;&lt;p&gt;WolframAlpha isn’t designed to be a “Google”, so calling it a “Google Killer” is doing it a grave disservice.  WolframAlpha is billed as a “computational knowledge engine”, which is geek for “thingamajig that makes sense of data and facts”.  This isn’t what Google does.  Google’s main purpose is to find relevant content.  It doesn’t (at this point) aim to “slice and dice” data or produce pretty charts and extrapolations – indeed, it’s only purpose of note is to find the most relevant content for a given input string, a purpose which it fulfills very competently.&lt;/p&gt;&lt;p&gt;W0lframAlpha also fulfills it’s purpose very competently.  For instance, take a look at that &lt;a href=&quot;http://www37.wolframalpha.com/input/?i=Carbon+Monoxide&quot; target=&quot;_blank&quot;&gt;Carbon Monoxide search&lt;/a&gt; I mentioned before.  In order to get that information via the conventional (Google) method, I’d need to visit at least a half a dozen sites, and I still wouldn’t get the diagrams that WolframAlpha produces.  Concise, quick, and palatable access to &lt;em&gt;knowledge&lt;/em&gt; is what WolframAlpha is designed to do.  Look up Carbon Monoxide on Wikipedia, and you’re going to waste an awful lot of time just trying to extract the relevant bits that WolframAlpha produced within a few seconds.&lt;/p&gt;&lt;p&gt;So why won’t WolframAlpha ever kill the mighty Google?  Well, according to The Unofficial Book of Random Speculation, the vast majority of internet searches are trivial.  See if you can find stats to prove me wrong, but I don’t think I am.  Unless Stephen Wolfram has perfected Artificial Intelligence (and if he has, let me be the first to say “All Hail the Machines”), his engine requires actual people to aggregate, validate, and present subject matter.  He may have figured out a smarter and more automated way of doing it, but it still requires human intervention.  Now using the oh-so-handy &lt;a href=&quot;http://www.blogger.com/www.google.com/trends&quot; target=&quot;_blank&quot;&gt;Google Trends&lt;/a&gt;, let’s have a look at the top search topics for today:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;harvard shooting &lt;/li&gt;&lt;li&gt;beverly center shooting &lt;/li&gt;&lt;li&gt;harvard crimson &lt;/li&gt;&lt;li&gt;bacherlorette &lt;/li&gt;&lt;li&gt;note to god lyrics &lt;/li&gt;&lt;li&gt;pooch hall &lt;/li&gt;&lt;li&gt;if i wrote a note to god &lt;/li&gt;&lt;li&gt;warped tour &lt;/li&gt;&lt;li&gt;24 season finale &lt;/li&gt;&lt;li&gt;hosea chanchez &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Notice anything?  1 through 3 are current events.  4 and 9 revolve around TV shows.  6 and 10 revolve around a TV show that I’ve never even  heard of.  5 through 8 relate to music or music events that I probably want nothing to do with.&lt;br /&gt;The point is, it’s all current, fleeting, and very….pop-ish.  All that matters for the people who are searching for those terms is current, relevant search results.  No human required, no aggregation possible…or at least, any aggregation would likely miss a large part of what the searching public is looking for.  What is the specific heat of combustion of The Bachelorette?  No..seriously, I think we’d all be better off if that kind of show would just hurry up and combust.&lt;/p&gt;&lt;p&gt;So will anybody ever kill Google?  Probably – nothing lasts forever.  But it won’t be WolframAlpha.  I wouldn’t rule out Microsoft.  Live Search is still miles away from Google IMO, but Microsoft has a history of taking on titans (&amp;lt;ahem&amp;gt;Oracle&amp;lt;/ahem&amp;gt;) and winning.  And who knows what Kumo has in store for us.&lt;/p&gt;&lt;p&gt;Anyway, time to go figure out who Pooch Hall is.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/2115257753950283895/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=2115257753950283895' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/2115257753950283895'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/2115257753950283895'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/05/wolframalpha-move-over-google-i-think.html' title='Wolfram|Alpha: Move Over Google? I Think We’re Missing The Point.'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-2801410681360675231</id><published>2009-05-12T09:24:00.002-04:00</published><updated>2009-05-12T09:24:00.866-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>sp_ExecuteSQL Doesn’t Validate Parameter Names</title><content type='html'>&lt;p&gt;I came across something interesting on the MSDN Forums a few weeks ago, and I thought I’d share it here. This is about as close to SQL Server trivia as I like to get, so I’ll keep it short ;-)&lt;/p&gt;&lt;p&gt;If you aren’t familiar with sp_ExecuteSQL, it is an &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms164653.aspx&quot; target=&quot;_blank&quot;&gt;extended stored procedure&lt;/a&gt; that can be used to parameterize and execute dynamic SQL. According to BOL, the general syntax for using sp_ExecuteSQL is as follows: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em;font-size:12px;&quot; &gt;&lt;span style=&quot;color:blue;&quot;&gt;DECLARE        &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@MySQLStatement &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;NVARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;               &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@MyParams &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;NVARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;              &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT         &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@MySQLStatement &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;SELECT * FROM sys.databases WHERE [name] = @name&#39;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;               &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@MyParams &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;@name sysname&#39;&lt;br /&gt;&lt;br /&gt;              &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;EXEC &lt;/span&gt;&lt;span style=&quot;color:darkred;&quot;&gt;sp_ExecuteSQL &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@stmt &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@MySQLStatement&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@params &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@MyParams&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;                   &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@name &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;msdb&#39;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;So bit by bit, the first parameter (@stmt) accepts the SQL statement. It has to be a Unicode type (ntext/nchar/nvarchar), so you need to either use a variable (as I did), or you need to prefix your constant string with a capital “N”. The second parameter accepts a comma-delimited listing of the dynamic SQL’s parameters, and also needs to be Unicode. The parameters following the second parameter should correspond to the parameters that you listed in the second parameter. Can I possibly say parameter one more time? I just did!&lt;/p&gt;&lt;p&gt;Now if you should happen to use the wrong type for one of the first two params, SQL Server will whine loudly:&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color:#ff0000;&quot;&gt;Msg 214, Level 16, State 2, Procedure sp_executesql, Line 1&lt;br /&gt;&lt;br /&gt;Procedure expects parameter &#39;@statement&#39; of type &#39;ntext/nchar/nvarchar&#39;.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Notice anything odd? In the original sp_ExecuteSQL call above, I called the first param “@stmt”. And it worked. But the error message gripes about a parameter called @statement. Is the error message wrong? Try the first example again, but substitute @statement for @stmt. It still works, doesn’t it? That isn’t standard stored procedure behaviour: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em;font-size:12px;&quot; &gt;&lt;span style=&quot;color:blue;&quot;&gt;CREATE PROC &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;MyTestProc&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@SQLServer &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;   )&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;&lt;br /&gt;BEGIN&lt;br /&gt;&lt;br /&gt;   SELECT &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@SQLServer&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;END&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--This works&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;EXEC &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;MyTestProc &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@SQLServer &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;I am SQL - hear me ROAR!&#39;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;--This doesn&#39;t.  No matter how many people say it does.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;EXEC &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;MyTestProc &lt;/span&gt;&lt;span style=&quot;color:#434343;&quot;&gt;@MySQL &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:red;&quot;&gt;&#39;I am free.  That should count for something, no?&#39;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;In fact, you can use anything you want for &lt;strong&gt;the first two&lt;/strong&gt; parameter names when using sp_ExecuteSQL – it runs on ordinal position, not variable name. Everything after the second parameter is validated. Weird, eh?&lt;/p&gt;&lt;p&gt;The change from standard SQL Server behaviour is a result of the fact that this sp is implemented as an extended stored procedure, using the XP API. Thanks to &lt;a href=&quot;http://sqlblog.com/blogs/michael_coles/default.aspx&quot; target=&quot;_blank&quot;&gt;Michael Coles&lt;/a&gt; and &lt;a href=&quot;http://stevekass.com/&quot; target=&quot;_blank&quot;&gt;Steve Kass&lt;/a&gt; for helping me crack the riddle.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/2801410681360675231/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=2801410681360675231' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/2801410681360675231'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/2801410681360675231'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/05/spexecutesql-doesnt-validate-parameter.html' title='sp_ExecuteSQL Doesn’t Validate Parameter Names'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4834408919706016250.post-5203022462641638905</id><published>2009-05-06T20:32:00.001-04:00</published><updated>2009-05-06T20:32:00.878-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><title type='text'>The Many Flavours of Common Table Expressions in SQL Server</title><content type='html'>&lt;p&gt;One of the coolest features introduced in SQL Server 2005 was the Common Table Expression, or CTE for short.  In the simplest sense, a CTE is a “virtual result set” that is defined within a given SQL statement, and used throughout that statement as if it were a real object.&lt;/p&gt;&lt;p&gt;En Anglais?  Here’s an example, using AdventureWorks2008: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--A regular CTE&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WITH &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeeSeniority &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.LoginID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.FirstName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.LastName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.HireDate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DENSE_RANK&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ORDER BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.HireDate &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ASC&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Seniority&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.Employee emp&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Person.Person p &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.BusinessEntityID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.BusinessEntityID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;LoginID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;FirstName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;LastName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HireDate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Seniority&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeeSeniority&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;As you can see, we have effectively “named” a result set by using the syntax:&lt;br /&gt;&lt;br /&gt;&lt;em&gt;WITH &amp;lt;ResultSetName&amp;gt; AS (MyQuery)&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Using the simple example above, I also demonstrated the ability of a CTE to encapsulate a column from the SELECT list, so we can treat aliased columns as “real” ones.  I built a column called “Seniority” inside the CTE itself, and then used said derived column in the main query’s SELECT statement.  Coolio.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Let’s take it one step further.  By placing a comma after the CTE’s defining query, we can add in another query: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--Two CTE&#39;s.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WITH &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeeSeniority &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.LoginID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.FirstName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.LastName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.HireDate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.BusinessEntityID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DENSE_RANK&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ORDER BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.HireDate &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ASC&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Seniority&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.Employee emp&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Person.Person p &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.BusinessEntityID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.BusinessEntityID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeePay &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.BusinessEntityID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.Rate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.PayFrequency&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ROW_NUMBER&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PARTITION &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.BusinessEntityID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ORDER BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ModifiedDate &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;IsCurrent&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.EmployeePayHistory eph&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.LoginID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.FirstName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.LastName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.HireDate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.PayFrequency&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.Rate&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeeSeniority es&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeePay ep   &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.BusinessEntityID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.BusinessEntityID&lt;br /&gt;&lt;br /&gt;                       &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;AND &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.IsCurrent &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Here, we’ve defined two “virtual result sets”, and in the main query we join the two together, just as we would with regular tables or views.  Now the real power of a CTE starts to reveal itself when you understand that one CTE can reference another.  For instance, I can rewrite the above query as: &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--The previous two CTE&#39;s, rewritten so the second references the first.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WITH &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeeSeniority &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.LoginID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.FirstName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.LastName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.HireDate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.BusinessEntityID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DENSE_RANK&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ORDER BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.HireDate &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ASC&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Seniority&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.Employee emp&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Person.Person p &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;emp.BusinessEntityID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;p.BusinessEntityID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;),&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeePay &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.BusinessEntityID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.Rate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.PayFrequency&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ROW_NUMBER&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;OVER &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;PARTITION &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.BusinessEntityID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ORDER BY &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ModifiedDate &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;IsCurrent&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.LoginID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.FirstName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.LastName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.HireDate&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.EmployeePayHistory eph&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;JOIN   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeeSeniority es &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;eph.BusinessEntityID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;es.BusinessEntityID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.LoginID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.FirstName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.LastName&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.HireDate&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.PayFrequency&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.Rate&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM   &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeePay ep&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE  &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ep.IsCurrent &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;We’ve actually joined the first CTE to regular tables, within the second CTE’s definition.  Now the “main” query only has to reference one CTE!&lt;/p&gt;&lt;br /&gt;&lt;p&gt;OK, let’s kick it up one more notch (please don’t sue me, Emeril).  The recursive CTE.  This one was the trickiest for me to understand, but now that I do understand it I’ll try to make it easier for you.  Have a look at this query (stolen, with attribution, from MSDN): &lt;/p&gt;&lt;pre style=&quot;LINE-HEIGHT: 1em; FONT-SIZE: 12px&quot;&gt;&lt;span style=&quot;color:green;&quot;&gt;--A recursive CTE&lt;br /&gt;&lt;br /&gt;--Example from http://msdn.microsoft.com/en-us/library/ms186243.aspx&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;USE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;AdventureWorks&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;GO&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WITH &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DirectReports &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ManagerID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeeID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Title&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DeptID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;Level&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;(&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;-- Anchor member definition&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.ManagerID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.EmployeeID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.Title&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;edh.DepartmentID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;0 &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS Level&lt;br /&gt;&lt;br /&gt;    FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.Employee &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INNER JOIN &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.EmployeeDepartmentHistory &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;edh&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.EmployeeID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;edh.EmployeeID &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;AND &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;edh.EndDate &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;IS &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NULL&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;WHERE &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ManagerID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;IS &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NULL&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;UNION &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;ALL&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;-- Recursive member definition&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.ManagerID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.EmployeeID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.Title&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;edh.DepartmentID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;,&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;LEVEL &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;+ &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;1&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.Employee &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INNER JOIN &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.EmployeeDepartmentHistory &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;edh&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.EmployeeID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;edh.EmployeeID &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;AND &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;edh.EndDate &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;IS &lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;NULL&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INNER JOIN &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DirectReports &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;d&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;e.ManagerID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;d.EmployeeID&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:green;&quot;&gt;-- Statement that executes the CTE&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;SELECT &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;ManagerID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;EmployeeID&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;Title&lt;/span&gt;&lt;span style=&quot;color:gray;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;Level&lt;br /&gt;&lt;br /&gt;FROM &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DirectReports&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;INNER JOIN &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;HumanResources.Department &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;AS &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;dp&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;ON &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;DirectReports.DeptID &lt;/span&gt;&lt;span style=&quot;color:blue;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:black;&quot;&gt;dp.DepartmentID&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Now we’re starting to get into weird science.  First, we’ve added column name definitions between WITH CTEName and AS.  This essentially defines the name and number of the columns to be returned by the CTE.  It is an optional construct, so you don’t need to use it if you’re explicitly aliasing columns in your CTE.&lt;br /&gt;&lt;br /&gt;Second, have a look a the query itself.  What we’re trying to do here is print a listing of employees, but indicate for each employee which level of the company’s hierarchy they reside in.  The first part of the query (labeled the “anchor definition”) returns nothing more than a listing of the “uppermost parent” in the hierarchy.  In our example, this is one record – the CEO – but it could very well entail a number of records.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The second part of the query (the “recursive member definition”) is brought into the same result set by the use of a UNION ALL statement.  Have a closer look at the recursive member though – notice that it actually refers back to &lt;em&gt;the CTE that it is a part of&lt;/em&gt;?  Cool, eh?  For the first iteration, it will join back to the CEO’s record, and return both the CEO and any of his or her direct reports.  For the second iteration, it will find the “third level” employees, or the ones who report to the ones who report to the CEO (say that 5 times fast…).  It will continue on and on until either there are no more “child” records, or the MAXRECURSION level for the CTE is hit.  Since we didn’t specify a MAXRECURSION level, it will default to 100.  The largest setting for MAXRECURSION that you use is 32,767 – anything even close to that and your server will likely be crawling anyway.  If we want to specify a MAXRECURSION hint, we can do so immediately after the CTE name.  For instance, in the query above, we would change&lt;br /&gt;&lt;br /&gt;FROM DirectReports&lt;br /&gt;into&lt;br /&gt;FROM DirectReports OPTION (MAXRECURSION 1000)&lt;/p&gt;&lt;p&gt;Now one last thing you should know about recursive CTEs is that there are certain restrictions on the DML that they can contain.  For further detail on these restrictions, have a look &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms175972.aspx&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Until next time, &lt;a href=&quot;http://en.wikipedia.org/wiki/The_Red_Green_Show&quot; target=&quot;_blank&quot;&gt;Keep Your Stick on the Ice&lt;/a&gt;, and have fun.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://thehobt.blogspot.com/feeds/5203022462641638905/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4834408919706016250&amp;postID=5203022462641638905' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5203022462641638905'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4834408919706016250/posts/default/5203022462641638905'/><link rel='alternate' type='text/html' href='http://thehobt.blogspot.com/2009/05/many-flavours-of-common-table.html' title='The Many Flavours of Common Table Expressions in SQL Server'/><author><name>Aaron Alton</name><uri>http://www.blogger.com/profile/10631672028280946920</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://2.bp.blogspot.com/_GyTlY8-ysTw/Seqbk-wSk5I/AAAAAAAAAE8/27XCD_y2kK0/S220/P1010022+MVP+Profile+Small.jpg'/></author><thr:total>3</thr:total></entry></feed>