<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Key2 Consulting Blogs &#187; Audrey Hammonds</title>
	<atom:link href="http://key2consulting.com/Blogs/author/audrey-hammonds/feed/" rel="self" type="application/rss+xml" />
	<link>http://key2consulting.com/Blogs</link>
	<description>BI, Data Warehousing, SharePoint and .NET</description>
	<lastBuildDate>Mon, 30 Apr 2012 17:47:28 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Five Places to Document Before You Win the Lottery</title>
		<link>http://key2consulting.com/Blogs/2011/04/05/five-places-to-document-before-you-win-the-lottery/</link>
		<comments>http://key2consulting.com/Blogs/2011/04/05/five-places-to-document-before-you-win-the-lottery/#comments</comments>
		<pubDate>Tue, 05 Apr 2011 13:02:09 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[Database Development]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[documentation]]></category>

		<guid isPermaLink="false">http://key2consulting.com/Blogs/?p=292</guid>
		<description><![CDATA[When a project starts to go sideways and you&#8217;re cursing Naive Past You who signed off on the &#8220;aggressive&#8221; time estimates as you feed quarters into the vending machine in hopes that powdered donuts and honey buns won’t give you a heart attack before you hit the deadline, the last thing you want to talk [...]]]></description>
			<content:encoded><![CDATA[<p>When a project starts to go sideways and you&#8217;re cursing Naive Past You who signed off on the &#8220;aggressive&#8221; time estimates as you feed quarters into the vending machine in hopes that powdered donuts and honey buns won’t give you a heart attack before you hit the deadline, the last thing you want to talk about is documentation. Non-developers, if you value your life, observe the developer and her surroundings before you ask whether the documentation is updated. If you see any of the following&#8230;</p>
<ul>
<li>Pyramid of Diet Coke or Mountain Dew cans</li>
<li>Pile of takeout containers (half-eaten because there’s no time)</li>
<li>Wild, bloodshot eyes</li>
<li>Caffeine-induced shakes</li>
<li>Sleeping bag and/or pillow</li>
</ul>
<p>&#8230;Back away slowly. DO NOT, under any circumstances, ask the developer if the documentation is up to date. You may lose a valuable body part before you can wedge yourself behind the file cabinet.</p>
<p>I love the beginning of a project. It’s full of resolutions and promises about how this time it’s going to be different. A quick run-down of statements I’ve heard (and said):</p>
<ol>
<li>&#8220;We’re building time into the schedule to keep the documentation up to date&#8221;</li>
<li>&#8220;All of my time estimates will include some buffer for documentation&#8221;</li>
<li>&#8220;QA will not sign off without documentation&#8221;</li>
<li>&#8220;We’ve made accurate documentation part of the critical path&#8221;</li>
<li>&#8220;We’ll document as we go&#8221;</li>
</ol>
<p>What ends up happening? I can sum it up in 4 words: “We’ll fix it later”. But we never do. Then we leave and the next poor soul comes in blind and realizes that the documentation is either non-existent or woefully out of date. He gets to spend the next few weeks being indignant and self-righteous because the documentation is bad. He quickly forgets the undocumented tangle of code he left behind at his last job.</p>
<p>So, what exactly is documentation? Is it a necessary evil, a sacrifice to the development lifecycle gods? Is it yet another bit of unproductive administration work you have to do to check all the boxes on the project plan? Is it some antiquated legacy of waterfall methods? No. It is none of these things. Documentation, when done well, is a conversation between you and the next guy who comes along and looks at your work. You might not be there to explain why you had to do that weird thing in that stored procedure. You might not be able to tell the story behind the funky flat table you wish your name wasn’t associated with. You might not be able to sit down over lunch and talk about the wacky business rules you’re trying to enforce. But, your documentation will be there. More than anything else, this is your legacy.</p>
<p>Am I proposing that you create exhaustive documentation explaining every decision and line of code? No. That’s totally unrealistic, and I believe that it’s what gets us into trouble. Overwhelmed by the goal of perfect documentation, we end up not writing any documentation at all. I fall into the good enough camp in my approach. Even then, it can be a struggle to keep up. In that spirit, I have a few must-haves and guidelines for what and when to document. Also, I’m sort of a function over form kind of girl. If it works, do it. If it’s pretty, well, that’s nice too. Of course, I’m a developer. If you’re a DBA, your list will probably look different. But, I recommend the same basic approach. What are my must-haves and what are my triggers for a bit of explanatory prose? Or, you know, poetry… but if you have time to write documentation in iambic pentameter, you probably need a more challenging job.</p>
<p><span style="text-decoration: underline"><strong>Must-Haves </strong></span></p>
<p><strong>Data Dictionary </strong>- If nothing else, get the basics down:</p>
<ol>
<li>Entities and their purpose</li>
<li>Attributes and their definition</li>
<li>Business names for entities and attributes</li>
<li>Explanation or examples of what data can end up in a column</li>
</ol>
<p><strong>Source to Target Mapping </strong>– if you’re moving data around, make a map</p>
<ol>
<li>Where does the data come from?</li>
<li>Where is it going?</li>
<li>What are the stops along the way?</li>
</ol>
<p><strong>Anything Manual </strong>- if you have to do anything out of process or manually, write it down</p>
<ol>
<li>Bob from Accounting asked you to run a “quick little report” for him in 2009. Somehow, you’re still running it every Monday</li>
<li>You manually kick off that pesky job every Thursday evening instead of scheduling it</li>
<li>You had to (gasp!) manually update data in the database</li>
</ol>
<p><span style="text-decoration: underline"><strong>Other Guidelines </strong></span></p>
<p><strong>In-Line Documentation</strong> &#8211; (Stored Procedures/Functions/Scripts/etc.)</p>
<ol>
<li>Set up a nice little template inside any procedure/function/script that tells: Author/Date/Purpose/Major Revisions</li>
<li>If you did something unconventional inside some encapsulated code, make a note of why you went this direction</li>
<li>If some piece of code started out pretty and then grew horns and a tail and bought a pitchfork from Evil Processes R Us, explain what happened</li>
<li>If you’re working around some limitation of the application/database/etc., say how and why</li>
</ol>
<p><strong>In Case I Win the Lottery Documentation</strong> (aka, In Case I Get Hit by a Bus)</p>
<ul>Imagine that you walk out the door this afternoon, and for some wonderful (not getting hit by a bus) reason, you never step foot inside your office again. You never log into the network again, and no one knows how to get in touch with you. What do only you know? Trust me, if you’ve been with an organization for any length of time, I can guarantee that you have some proprietary knowledge. This can be the least formal of all. Just make a list of things you hope someone else knows how to do when you’re sitting in Tahiti sipping fruity drinks, ogling the very cute cabana boy as you ask him to bring you yet another extra towel.</ul>
<ul>In our little SQL Server community, we talk a lot about being part of the community. (I know… totally Meta) Documentation is a great way to be a good citizen inside our world. Chances are someone you know, or someone who knows someone you know, will be coming into an organization after you leave. Helping the next guy ramp up more quickly and avoid your early stumbles is just about the best you can do to support your fellow database professional.</ul>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/2011/04/05/five-places-to-document-before-you-win-the-lottery/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>What Are We Doing Here, Exactly?</title>
		<link>http://key2consulting.com/Blogs/ahammonds/2011/02/15/what-are-we-doing-here-exactly/</link>
		<comments>http://key2consulting.com/Blogs/ahammonds/2011/02/15/what-are-we-doing-here-exactly/#comments</comments>
		<pubDate>Tue, 15 Feb 2011 18:03:32 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[Database Development]]></category>

		<guid isPermaLink="false">http://6.48</guid>
		<description><![CDATA[There’s a secret about being a developer that I forget all the time. I know it, and I should remember it, but in the daily drama of life, I tend to forget it. Here it is: Knowing how to do something is the easy part. Knowing what to do… that’s hard. Technical skills allow me [...]]]></description>
			<content:encoded><![CDATA[<p>There’s a secret about being a developer that I forget all the time.  I know it, and I should remember it, but in the daily drama of life, I tend to forget it.  Here it is:  <strong>Knowing how to do something is the easy part.  Knowing what to do… that’s hard. </strong></p>
<p>Technical skills allow me to execute on a plan.  The good news is that if I don’t know how to do something, there is a wealth of resources out there to help me out.  I can probably pilfer a bit of code from a blog, find a checklist, or even call a friend.  Knowing the plan?  That’s the hard part. </p>
<p>Brent Ozar (<a href="http://www.brentozar.com/" target="_blank">Blog</a>|<a href="http://twitter.com/#!/BrentO" target="_blank">Twitter</a>) wrote a <a href="http://www.brentozar.com/archive/2011/02/consulting-lines-would-you-mind-driving/" target="_blank">brilliant post </a>about being a consultant.  It was so brilliant and apropos that I e-mailed him to ask for advice on a few things I’m dealing with.  He was awesome, and thoughtful, and gave me some great ideas.  He even recommended a book, <a href="http://www.amazon.com/Secrets-Consulting-Giving-Getting-Successfully/dp/0932633013/ref=sr_1_1?ie=UTF8&amp;qid=1297782287&amp;sr=8-1" target="_blank">The Secrets of Consulting by Gerald Weinberg</a>.  I read it, and I immediately felt better.  Everyone should read it.  It validated something that had been creeping around in the recesses of my brain:  I didn’t have a good plan.</p>
<p>Why not?  Well, I’d been so busy executing that I’d forgotten to take a step back and think.  It happens to the best of us.  I tend to be an intuitive person.  I feel like something’s not right long before I can put my finger on it.  It makes me crazy.  It’s like the intelligent part of my brain is whispering, “Audrey… Audrey… Pay attention.  This isn’t working”, while the rest of my brain is totally focused on crossing things off the to-do list.  The problem?  Maybe the to-do list is dead wrong. </p>
<p>So that’s what development managers and project managers are for, right?  They put the plan together. They figure out the what, we figure out the how.  Right.  Right?  Hogwash!  Yeah, I said it.  Hogwash. </p>
<p>Here’s what I believe.  Every person involved in a project, from the college intern to the CTO needs to do a personal assessment of the project they’re on.  I’d flat forgotten this personal belief of mine.  In the rush to deliver, I’d jumped headfirst into a project without first grounding myself.  So, after talking with Brent and reading Weinberg’s book, I assigned myself a task:  Assess the Project. </p>
<p>Now, I’m not the first person to do this, and I’m certainly not the first person to talk about this.  But I constantly have to remind myself to actually do it.  It is a liberating exercise.  Putting onto paper what’s worrying me about a project makes it real.  If it’s real I can do something about it, or at least see it coming before it smacks me in the face. </p>
<p>I have a few personal rules for my assessments:<br />
1)      It is a personal document.  For my benefit.  I might use it as a reference for later communication, but for now, it’s just me and my stream of consciousness.  I don’t worry about sounding negative or hurting feelings.  If I think it’s going to be really bad, I might even write it at home and on the personal computer.</p>
<p>2)      It is mostly a problem-defining exercise, not a problem-solving exercise. </p>
<p>3)      No edits till I’m done.  None.  No tweaking, rewording, or rethinking.  This one is hardest for me.  I can’t help myself sometimes, and the urge to soften a harsh word or begin in-line rationalizing is tough to resist. </p>
<p>4)      This is not a technical document.  It is an emotional document.  Everything from “I don’t know how to do X” to “Mr. End User refuses to cooperate” is fair game. </p>
<p>Anyway, here’s my process.  I ask myself some questions, and answer them.  Really, it’s just a set of lists. </p>
<p>1)      <strong>What is the current state?</strong>  &#8211; What’s going on in the business that prompted this project in the first place?  What needs to be improved/created/maintained?  Is the system too slow?  Are users complaining?  Are we losing customers? </p>
<p>2)      <strong>What is the desired state?</strong>  &#8211; What does everyone want the world to look like when this project is done?  Is capacity higher?  Turnaround faster?  Errors reduced?  Is there a totally new process?  Is there a shiny new system? </p>
<p>3)      <strong>What are the problems?</strong> – (Remember, we can use the politically incorrect “problem” because it’s a personal document) What’s keeping us from getting to the desired state?  What issues do we keep tripping over?  Who’s being difficult or unrealistic?  Is the schedule reasonable?  What am I awake at 4:00 AM worrying about? </p>
<p>4)      <strong>What can I fix?</strong> – Here, I sort of break one of my rules.  I try to identify what I can fix that’s broken.  Key point:  What, not how.</p>
<p>a.       <strong>Right Now</strong> – What can I do right now without anything else happening first?  I don’t worry about time or resources; I just list everything I could theoretically fix.<br />
b.       <strong>Right After</strong> – If I fix the things I could fix right now, what’s next?<br />
c.       <strong>And Then?</strong>  &#8211; If I can theoretically get through the “Right Now” and “Right After”, what could I do? </p>
<p>TANGENT 1:  It’s interesting to see if putting together these three lists naturally gets me to the desired state I defined in List 2.  BUT… I resist the urge to force it.  Be honest.</p>
<p>5)      <strong>What is my conclusion?</strong> – This is the part where I get to rant.  I just start writing about where I think this project is headed.  Hopefully, the first 4 lists I’ve put together have helped me get my head on straight.  If not, well, that tells me something too.  Seriously, I rant.  I tell it like I think it is.  No one is going to read what I say except for me.  Do I believe the project is going to fail?  I say it.  Then say why.  Do I think we need to go in a different direction?  I put it down.  Do I think I’m failing to deliver?  Why?  It’s the most liberating part of this process.  Feels like confession.</p>
<p>6)      <strong>What questions do I have?</strong> – I read back over my first 5 lists, and start writing down any questions I can’t answer.  Doesn’t matter how big or stupid or rude.  In the past, I’ve written thing like, “Does anyone care if this project succeeds?”, and “Can we hit deadline if [Name Redacted] keeps screwing up?”  I might never ask these questions out loud, but it’s therapeutic to ask myself.  I might even come up with a few that need real answers that I can ask in public and look proactive and smart. </p>
<p>TANGENT 2:  If I can’t put the 6 lists together off the top of my head, this is a giant, flaming red flag.  If I can’t define where we are, or where we want to go, or what I can do to help get us there, I’ve got real problems. </p>
<p>So, I’ve poured my thoughts and worries and soul into answering 6 basic questions.  What now?  I put it away.  I leave it alone to marinate for a day.  Then, I open it back up again.  I read it and try to see what the basic feel is.  Is it optimism or despair?  Was I overly negative, or did I apply false optimism to my lists?  And, most important, do I see the beginnings of a plan? </p>
<p>Ninety-nine times out of a hundred, I see things I could be doing differently.  I can begin to filter out the things I can improve versus the things I have no control over.  I usually see a plan emerge.  I see a way out of whatever hole I’ve dug for myself (or been thrown into).  Most importantly, I have a lodestone in this assessment next time the manager asks me what I think.  I’ve already thought about it and put it on paper, and I’m not fumbling around trying to describe some general feeling of “Not Rightness”.</p>
<p>I don’t believe that there are impossible projects, but I do believe that there are impossible plans.  More than there should be, actually.  I know that Development Managers get sick of hearing us whine about the plan.  But, if I can say, “I have a few specific questions and ideas about the plan”, they usually sit up and listen.  See, there’s another dirty little secret that my dear friend Josh Lane told me once:  We all think there are these experts out there that have all the answers.  Guess what?  There isn’t.  It’s us.  We’re it.</p>
<p>Scary as hell?  Yes.  But it also means that it’s on us as developers to not just solve problems, but to help define them as well.  Ask anyone in our business… it really is harder than it looks.</p>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/ahammonds/2011/02/15/what-are-we-doing-here-exactly/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Noir SQL&#8230; Or, a Hardboiled Approach to Getting the Job Done</title>
		<link>http://key2consulting.com/Blogs/2011/01/19/noir-sql-or-a-hardboiled-apprach-to-getting-the-job-done/</link>
		<comments>http://key2consulting.com/Blogs/2011/01/19/noir-sql-or-a-hardboiled-apprach-to-getting-the-job-done/#comments</comments>
		<pubDate>Wed, 19 Jan 2011 20:22:18 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[fixed-width output]]></category>
		<category><![CDATA[format string]]></category>
		<category><![CDATA[replace]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://key2consulting.com/Blogs/?p=214</guid>
		<description><![CDATA[You can tell a lot about my state of mind by the books I’m reading. Lately, it’s Urban Fantasy with a Noir feel to it. Specifically, I’m reading Mike Carey’s Felix Castor series, and I just finished a book by Richard Kadrey called Sandman Slim: A Novel. I love the anti-hero. The protagonist who is [...]]]></description>
			<content:encoded><![CDATA[<p>You can tell a lot about my state of mind by the books I’m reading. Lately, it’s Urban Fantasy with a <a href="http://en.wikipedia.org/wiki/Hardboiled" target="_blank">Noir </a>feel to it. Specifically, I’m reading <a href="http://www.amazon.com/Devil-You-Know-Felix-Castor/dp/0446618705/ref=sr_1_1?ie=UTF8&amp;qid=1295458684&amp;sr=8-1" target="_blank">Mike Carey’s Felix Castor </a>series, and I just finished a book by Richard Kadrey called <a href="http://www.amazon.com/Sandman-Slim-Richard-Kadrey/dp/0061976261/ref=sr_1_1?s=books&amp;ie=UTF8&amp;qid=1295458730&amp;sr=1-1" target="_blank">Sandman Slim: A Novel</a>. I love the anti-hero. The protagonist who is gritty and dirty and has a few great scars is my kind of guy. He unapologetically breaks the rules and isn’t all, “it’s more about the journey than the destination.” For him, destination is what matters, no matter now you got there.</p>
<p>Lately, I feel a bit like the scarred anti-hero. I’m doing some things in a production environment that I’m not totally thrilled about, and I wish I could stop the line and do things the “right” way. I want to use SSIS to transform data. I want to encapsulate processes into neat, repeatable, parameterized modules. But, you know what? When there’s a same-day turnaround on a request, you make do. You go a little Noir on your T-SQL, know what I mean?</p>
<p>I want to show you two things that I’ve actually done in the past few weeks. No, given a nice, neat environment, this SQL might never have been written. Am I proud of it? Well, yes. Yes I am. At the end of the day, I got the customer what he needed. Was it pretty? No. I’m cool with that. Being the anti-hero is kind of fun every once in a while.</p>
<p><span style="text-decoration:underline"><strong>Fixed-Width Output</strong></span></p>
<p>I needed to give a guy a text file in fixed-width format. I had a process from my predecessor that just wasn’t working. The file was already late. So here’s what I did. I’m using the AdventureWorks database to show an example.</p>
<pre>SELECT
	LEFT((ISNULL(Title,'')+SPACE(50)), 8)+
	LEFT((ISNULL(FirstName,'')+SPACE(100)), 20)+
	LEFT((ISNULL(LastName,'')+SPACE(100)), 30)+
	LEFT((ISNULL(MiddleName,'')+SPACE(100)), 5)+
	LEFT((ISNULL(EmailAddress,'')+SPACE(100)), 35)+
	LEFT((ISNULL(Phone,'')+SPACE(100)), 25)
FROM AdventureWorks.Person.Contact	;</pre>
<p>The result:</p>
<p><a href="http://datachix.files.wordpress.com/2011/01/fixedwidthssms.jpg"><img class="alignnone size-full wp-image-931" src="http://datachix.files.wordpress.com/2011/01/fixedwidthssms.jpg" alt="" width="620" height="254" /></a><a href="http://datachix.files.wordpress.com/2011/01/fixedwidthnotepad.jpg"></a></p>
<p>Paste it into Notepad and see how it looks:</p>
<p><a href="http://datachix.files.wordpress.com/2011/01/fixedwidthnotepad2.jpg"><img class="alignnone size-full wp-image-934" src="http://datachix.files.wordpress.com/2011/01/fixedwidthnotepad2.jpg" alt="" width="590" height="299" /></a><a href="http://datachix.files.wordpress.com/2011/01/fixedwidthnotepad1.jpg"></a></p>
<p>I save the text file and send it on. Pour myself a whiskey, neat, and light up an unfiltered Lucky Strike.  Okay, not really, but you know what I mean. </p>
<p>A quick run-down:</p>
<p><strong><a href="http://msdn.microsoft.com/en-us/library/aa933210(SQL.80).aspx" target="_blank">ISNULL</a></strong>: If any of the values I’m concatenating are NULL, then the entire string will come back as NULL. I wrap all of my columns in ISNULL like so:<strong> </strong></p>
<p><strong>ISNULL(Title, ‘’)</strong></p>
<p>This sets the value to an empty string if the value is NULL.</p>
<p><strong><a href="http://msdn.microsoft.com/en-us/library/ms187950.aspx" target="_blank">SPACE</a></strong>: This handy little string function will pad the given number of spaces onto the result you return. I want to make sure I end up with enough padded spaces to fill out the fixed-width portion of that column. So, I pad the output:</p>
<p><strong>ISNULL(Title, ‘’)+SPACE(50)</strong></p>
<p>This will give me the output from the Title column, plus 50 spaces.</p>
<p><strong><a href="http://msdn.microsoft.com/en-us/library/ms177601.aspx" target="_blank">LEFT</a></strong>: Now, not every value coming out of the database is going to have the exact same number of columns. So, I use the LEFT function to trim it down to the exact length I want. LEFT will take the left-most number of characters you tell it to. If I say,</p>
<p><strong>LEFT((ISNULL(Title,&#8221;)+SPACE(50)), 8 )</strong></p>
<p>I’m telling it to give me characters 1-8 that are returned. Since I’ve padded my output with spaces, it’ll be the result from the column, plus as many spaces as I need to pad the output to 8.</p>
<p>Pretty? No. Functional? Yes. Noir SQL? Absolutely.</p>
<p><span style="text-decoration:underline"><strong>Remove Unwanted Characters</strong></span></p>
<p>Next up, I have a source file I use from another department. It comes in Excel format, and includes a phone number. I’m supposed to get something that looks like this: 1112223333. Nice, neat, simple. What do I get? A hodge-podge of phone number formats. I’m looking at something like this:</p>
<pre>CREATE TABLE PhoneNumber
(
	PhoneNumber varchar(50)
); 

INSERT INTO PhoneNumber(PhoneNumber)
VALUES
	('1112223333'), ('(111) 222-3333'), ('111-222-3333'), ('111 222 3333'); 	

SELECT PhoneNumber
FROM PhoneNumber</pre>
<p><a href="http://datachix.files.wordpress.com/2011/01/phonenumbersunformatted.jpg"><img class="alignnone size-full wp-image-935" src="http://datachix.files.wordpress.com/2011/01/phonenumbersunformatted.jpg" alt="" width="205" height="128" /></a></p>
<p>Okay. So I need to clean these numbers up quickly. Destination, not journey, my friends. I’m the anti-hero. I import the data into SQL Server using the Import/Export utility so I can manipulate the data. Then, I run this nifty little REPLACE statement:</p>
<pre>SELECT PhoneNumber,
	CASE
	WHEN ISNUMERIC(PhoneNumber) = 0
		THEN REPLACE(
			REPLACE(
				REPLACE(
					REPLACE(PhoneNumber, '-', ''),			--Strip out dashes
				' ', ''),							--Strip out spaces
			')', ''),								--Strip out close parenthesis
		'(', '')									--Strip out open parenthesis
		ELSE PhoneNumber
	END as FormattedPhoneNumber
FROM dbo.PhoneNumber</pre>
<p>Check out the results:</p>
<p><a href="http://datachix.files.wordpress.com/2011/01/phonenumberformatted.jpg"><img class="alignnone size-full wp-image-936" src="http://datachix.files.wordpress.com/2011/01/phonenumberformatted.jpg" alt="" width="333" height="133" /></a></p>
<p>Sweet. It’s quick, it’s dirty, and it saved me having to wait on the source data provider to clean things up on his end. I turn the query into an UPDATE statement, and I&#8217;ve got clean data to import.  Again, a run-down of the functions:</p>
<p><strong><a href="http://msdn.microsoft.com/en-us/library/ms186272.aspx" target="_blank">ISNUMERIC</a></strong>: Tells me whether the value I’m passing is qualifies as a number or not. NOTE: It recognizes hexadecimal as a number, so use carefully. I set up a CASE statement that asks if the value is numeric. If it is, that means I don’t have any characters like “(“, “)”, or “-“ in there. If not, I apply a nested REPLACE to the value.</p>
<p><strong><a href="http://msdn.microsoft.com/en-us/library/ms186862.aspx" target="_blank">REPLACE</a></strong>: Replace is awesome. I can say something like this: REPLACE(PhoneNumber, ‘-‘, ‘’). This is saying that if I find a dash, I want to replace it with an empty string. What’s really cool is that I can nest them. So, I can tell it to remove the dashes, then the spaces, then the open parenthesis, and finally the close parenthesis in one statement.</p>
<p>Bottom line: Sometimes things just have to get done. The difference between an anti-hero and a true antagonist is that we anti-heroes know to go back and do things the right way as soon as we get a moment to breathe. In the meantime, don’t apologize for leaving behind a few unmarked graves when you need to get the job done. We’re anti-heroes. We have the scars to prove it.</p>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/2011/01/19/noir-sql-or-a-hardboiled-apprach-to-getting-the-job-done/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Open Invitation: Atlanta SQL Server Writer&#8217;s Workshop</title>
		<link>http://key2consulting.com/Blogs/2011/01/06/open-invitation-atlanta-sql-server-writers-workshop/</link>
		<comments>http://key2consulting.com/Blogs/2011/01/06/open-invitation-atlanta-sql-server-writers-workshop/#comments</comments>
		<pubDate>Thu, 06 Jan 2011 16:14:22 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[Atlanta]]></category>

		<guid isPermaLink="false">http://key2consulting.com/Blogs/?p=201</guid>
		<description><![CDATA[The Short &#38; Sweet: WHAT: Atlanta SQL Server Writer&#8217;s Workshop WHO: Anyone who writes, presents, or is thinking of getting started in either WHEN: Thursday, January 6, beginning at 6:30 PM (First Thursday of the month going forward) WHERE: 12000 Findley Rd, Ste 150, John&#8217;s Creek, GA 30097* WHY: To write! To socialize! To finish [...]]]></description>
			<content:encoded><![CDATA[<p><strong>The Short &amp; Sweet: </strong></p>
<p><strong>WHAT: Atlanta SQL Server Writer&#8217;s Workshop</strong><br />
<strong>WHO: Anyone who writes, presents, or is thinking of getting started in either</strong><br />
<strong>WHEN: Thursday, January 6, beginning at 6:30 PM (First Thursday of the month going forward)</strong><br />
<strong>WHERE: <a href="http://www.bing.com/maps/default.aspx?encType=1&amp;where1=12000+Findley+Rd%2c+Johns+Creek%2c+GA+30097-1412&amp;FORM=MIRE&amp;qpvt=12000+findley+rd%2c+john's+creek%2c+ga" target="_blank">12000 Findley Rd, Ste 150, John&#8217;s Creek, GA 30097</a>*</strong><br />
<strong>WHY: To write! To socialize! To finish that abstract that&#8217;s been calling your name for the past two weeks!</strong></p>
<p>* More detailed instructions: This is the John&#8217;s Creek City Hall building right off Peachtree Pkwy (Hwy 141). If you go into the front entrance of the building, hang a right and go down the hallway. We&#8217;re the last door on the left. If you come in the side entrance, we&#8217;re the first door on your right. The door is locked by default, but if you know the secret knock, we&#8217;ll let you in. <img src='http://key2consulting.com/Blogs/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p><strong>The Explanation: </strong></p>
<p>Writing and Presenting is fun, satisfying, and good for your career. It&#8217;s also intimidating, nerve-wracking, and hard. If you&#8217;re just getting into the game, it&#8217;s a scary proposition. You&#8217;re not sure how to get started, what to write or present about, or how to get your brilliant ideas to the masses. If you&#8217;ve been doing it for a while&#8230; well, all of the above probably applies to you too.</p>
<p>Some days, you feel like this guy:</p>
<p><a href="http://datachix.files.wordpress.com/2010/12/joseph-fiennes-shakespeare-in-love.jpg"><img class="alignnone size-medium wp-image-841" src="http://datachix.files.wordpress.com/2010/12/joseph-fiennes-shakespeare-in-love.jpg?w=300" alt="" width="300" height="230" /></a></p>
<p>The words and ideas flow. You amuse yourself with your witty prose, and it comes naturally. Gwyneth Paltrow falls madly in love with you and you live happily ever after.</p>
<p>But some days, you&#8217;re this guy:</p>
<p><a href="http://datachix.files.wordpress.com/2010/12/jackshining.jpg"><img class="alignnone size-medium wp-image-842" src="http://datachix.files.wordpress.com/2010/12/jackshining.jpg?w=300" alt="" width="300" height="186" /></a></p>
<p>All work and no play&#8230; Need I say more?</p>
<p>A few weeks ago my fellow Datachix, Julie Smith (<a href="http://www.datachix.com" target="_blank">Blog</a>|<a href="http://twitter.com/#!/Datachix1" target="_blank">Twitter</a>) made a comment that we should have an abstract writing party. I think that everyone went, &#8220;Huh, that actually sounds fun.&#8221; (Go ahead, insert geek joke here. I&#8217;ll wait.) Then, Aaron Nelson (<a href="http://sqlvariant.com" target="_blank">Blog</a>|<a href="http://twitter.com/#!/SQLvariant" target="_blank">Twitter</a>) and I had lunch last week and talked about it again. The idea that Like-Minded Individuals within the SQL Server community would get together to socialize and write all at the same time sounded pretty cool.</p>
<p>So here we are, a week later. Logistics have been worked out, permission has been granted, and the invitation is being put forward. We&#8217;re starting a monthly Writer&#8217;s Workshop for anyone who writes or presents about SQL Server in the Atlanta area (or elsewhere if you want to travel and/or happen to be in town). We need a cooler name than Writer&#8217;s Workshop, but I&#8217;ll leave that open up for discussion. For now, SQL Server Writer&#8217;s Workshop it is. Even given the name, if you want to write or present about any technical topic, come on. We are an inclusive bunch.</p>
<p>Here&#8217;s the setup: Bring your laptop, ideas, half-finished blog posts, nearly-due abstracts, writer&#8217;s block, and your happy self. This being a working/social gathering, it&#8217;s BYOB (or soda/coffee/whatever &#8211; we don&#8217;t judge). We&#8217;ll probably order take-out or make a food run, but feel free to bring some grub. Settle in and work on whatever it is you&#8217;re trying to start or finish, knowing that the room has people willing to answer a shouted question, do a bit of quick copy-editing, or make suggestions about your work. That&#8217;s right, fellow writers. This is a community gathering; be prepared to listen and offer suggestions. If someone wants to get a second set of eyes, they might be yours. On the other hand, if you need a look over your shoulder, there&#8217;s someone there. Also, with a bit of advance notice, I can make sure we have a projector on site for presentation practice.</p>
<p>If you&#8217;re thinking of starting a blog or submitting your first presentation abstract, we especially want you to come out. I would argue that Atlanta has one of the best SQL Server communities out there, and we want to reinforce that reputation. Getting new speakers and writers up and running is a win for everyone. So seriously, join us. We promise to be nice.</p>
<p>My boss (and the President of <a href="http://www.key2consulting.com" target="_blank">Key2 Consulting</a>) Brian Thomas (<a href="http://www.linkedin.com/pub/brian-thomas/0/829/148" target="_blank">LinkedIn</a>), has been gracious enough to allow us to use the Key2 offices in John&#8217;s Creek after-hours for this. We have open space, good wifi, whiteboards, an as-needed projector, and a fridge. It&#8217;s not fancy (we are a small company after all), but it&#8217;s got a good setup and it&#8217;s free. We have a decent-sized room with tables, chairs, and a couch for those of you who like to get comfy while writing.</p>
<p>If you have questions feel free to e-mail me at audrey.hammonds@key2consulting.com. I&#8217;m also on Twitter as Datachix2.</p>
<p>And now, the official disclaimers:</p>
<p>1) Please be respectful of the offices we&#8217;re using. They&#8217;re being offered up free of charge, and my boss is doing our community a solid by letting us use the space. Don&#8217;t take what&#8217;s not yours, and clean up after yourself.<br />
2) It&#8217;s BYOB. You are a grown-up. Be responsible and know your limits. Key2 Consulting nor the people you&#8217;re surrounded by are responsible for your actions or behavior during or after the gathering.<br />
3) You are solely responsible for what you publish or present. Do your homework.<br />
4) That being said, if someone gives you a great idea or really helps you out, give them some credit. A name-drop is a good thing in this context.</p>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/2011/01/06/open-invitation-atlanta-sql-server-writers-workshop/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Adventures in MDX &#8211; Sets</title>
		<link>http://key2consulting.com/Blogs/2010/12/22/adventures-in-mdx-sets/</link>
		<comments>http://key2consulting.com/Blogs/2010/12/22/adventures-in-mdx-sets/#comments</comments>
		<pubDate>Wed, 22 Dec 2010 13:01:41 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[Database Development]]></category>
		<category><![CDATA[SSAS]]></category>
		<category><![CDATA[MDX]]></category>
		<category><![CDATA[SET]]></category>

		<guid isPermaLink="false">http://key2consulting.com/Blogs/?p=182</guid>
		<description><![CDATA[As I’ve mentioned before, I’m just not good at MDX. No excuses… I’m just not. Rather than jumping in and memorizing functions and complex structures, I’m trying to train my over-saturated brain to comprehend how the data is structured, and subsequently, how to get said data out of the cube and into a result set. [...]]]></description>
			<content:encoded><![CDATA[<p>As I’ve mentioned before, I’m just not good at MDX. No excuses… I’m just not. Rather than jumping in and memorizing functions and complex structures, I’m trying to train my over-saturated brain to comprehend how the data is structured, and subsequently, how to get said data out of the cube and into a result set. The first step was to understand Tuples. A quick recap: A Tuple is a data point – the intersection of all of the dimensions at a certain place. Imagine you have a nice, big, freshly baked cake. Maybe a chocolate layer cake with chocolate ganache. I’m just spit-balling… choose any flavor you like. This is our cube proxy. Anyway, stick a toothpick into that cake. The spot where the point of the toothpick stops: Tuple. It’s a single point in our cake, the intersection of eggs, flour, chocolate, sugar, butter, etc.</p>
<p>Now, take a knife and cut into the cake. You’ve defined a set. It’s a collection of Tuples. Cool, huh? Now, stop cutting! I have to get through a basic set before we cut (SELECT) a whole slice of data out of that decadent, delicious cake. As I mentioned before, I’m using the <a href="http://www.amazon.com/Microsoft-SQL-Server-2008-Step/dp/0735626189/ref=sr_1_1?ie=UTF8&amp;s=books&amp;qid=1292170353&amp;sr=8-1" target="_blank">Microsoft SQL Server 2008 MDX Step-by-Step</a> book as my primary resource. Much credit to these guys for their excellent tome.</p>
<p>The best way to illustrate a SET is to build up a SELECT statement in MDX. So, that’s just what we’re going to do.</p>
<p>Defining a set gives you a lot of power over the way that your result is presented to you as well as what’s included in it. In most cases, you’re going to define what’s on COLUMNS and ROWS, the first two of 128 possible axes you can define. I’d love to talk to the sadistic you-know-what at Microsoft that thought it would be funny to try to make my brain fry by encouraging me to even attempt to visualize how a result set would be presented on 128 different axes. It’s okay, though, somebody probably failed to tell him that SSMS only allows you to return two axes in a result set. If you try to define a third, PAGE, for those of you keeping track at home, you’ll get an error message instead of results. Ha! Take that, Mr. Microsoft Over-Achiever!</p>
<p>Anyway, let’s start building us a SELECT statement in Management Studio. First, make it as basic as possible:</p>
<pre>SELECT
FROM [Adventure Works];</pre>
<p>You get this:</p>
<p><a href="http://datachix.files.wordpress.com/2010/12/mdx2_result1.png"><img class="alignnone size-full wp-image-763" src="http://datachix.files.wordpress.com/2010/12/mdx2_result1.png" alt="" width="209" height="57" /></a></p>
<p>Um, okay, that’s nice. 80 million dollars. That tells me… nothing useful. But, we have a syntactically correct MDX query, so I’m not complaining.</p>
<p>TANGENT:</p>
<p>By the way, what does that ~80 million represent? Reseller Sales Amount. Why? Because it’s the default measure for the Adventure Works cube. How do we know? Open up BIDS. Open the Analysis Services Database, Adventure Works. Open the Adventure Works cube, and go to the Cube Structure tab. Right-click on the Adventure Works cube in the Measures section (top-left corner) and select Properties. There’s a defaultmember property. It says Reseller Sales Amount. There you go.</p>
<p>END TANGENT</p>
<p>But, we can do better. Let’s define a set that will give us column headers:</p>
<pre>SELECT
{
	 ([Sales Territory].[Sales Territory Country].[Australia])
	,([Sales Territory].[Sales Territory Country].[Canada])
	,([Sales Territory].[Sales Territory Country].[Germany])
	,([Sales Territory].[Sales Territory Country].[United Kingdom])
	,([Sales Territory].[Sales Territory Country].[United States])
} ON COLUMNS
FROM [Adventure Works];</pre>
<p><a href="http://datachix.files.wordpress.com/2010/12/mdx2_result2.png"><img class="alignnone size-full wp-image-764" src="http://datachix.files.wordpress.com/2010/12/mdx2_result2.png" alt="" width="469" height="94" /></a></p>
<p>That thing up there in the SELECT clause? A SET! Note that it’s enclosed in curly brackets ({}). Yes, I know they’re called braces. I call them curly brackets. More descriptive. Also note that each thing between the commas is a Tuple. Therefore, Collection of Tuples! There is an important, nay, vital rule that is being followed here: When I explicitly name a dimension in my tuple, each of the tuples in the set references the same hierarchy. Now, I don’t have to define the SAME LEVEL of the hierarchy in all of my tuples. I can do something like this:</p>
<pre>SELECT
{
	 ([Sales Territory].[Sales Territory Country].[Australia])
	,([Sales Territory].[Sales Territory Country].[Canada])
	,([Sales Territory].[Sales Territory Country].[Germany])
	,([Sales Territory].[Sales Territory Country].[United Kingdom])
	,([Sales Territory].[Sales Territory Country].[United States])
	,([Sales Territory].[Sales Territory Country])
} ON COLUMNS
FROM [Adventure Works];</pre>
<p><a href="http://datachix.files.wordpress.com/2010/12/mdx2_result3.png"><img class="alignnone size-full wp-image-765" src="http://datachix.files.wordpress.com/2010/12/mdx2_result3.png" alt="" width="579" height="91" /></a></p>
<p>Cool. But, I can’t reference two different hierarchies from the Sales Territory dimension in one set. Check this out:</p>
<pre>SELECT
{
	 ([Sales Territory].[Sales Territory Country].[Australia])
	,([Sales Territory].[Sales Territory Country].[Canada])
	,([Sales Territory].[Sales Territory Country].[Germany])
	,([Sales Territory].[Sales Territory Country].[United Kingdom])
	,([Sales Territory].[Sales Territory Country].[United States])
	,([Sales Territory].[Sales Territory Region].[Northeast])
} ON COLUMNS
FROM [Adventure Works];</pre>
<p><a href="http://datachix.files.wordpress.com/2010/12/mdx2_result4.jpg"><img class="alignnone size-full wp-image-766" src="http://datachix.files.wordpress.com/2010/12/mdx2_result4.jpg" alt="" width="558" height="106" /></a></p>
<p>Ooh, error. Back to the cake analogy… This would be sort of like starting to cut into the cake, and then picking up the knife and stabbing it into another part of the cake. You wouldn’t expect a clean slice, and the same goes for the query. It just doesn’t know how to pull this data back. By the same token, I can’t reference two different hierarchies either. Really, why would you do this to your lovely chocolate ganache, anyway?</p>
<p>Okay, there’s more we can do with these column headers that are being returned. We can define a more detailed tuple. Maybe I want to see why people bought products in Australia. Watch this:</p>
<pre>SELECT
{
	 ([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Quality])
	,([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Price])
	,([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Magazine Advertisement])
	,([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Review])
	,([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Manufacturer])
} ON COLUMNS
FROM [Adventure Works];</pre>
<p><a href="http://datachix.files.wordpress.com/2010/12/mdx2_result5.png"><img class="alignnone size-full wp-image-767" src="http://datachix.files.wordpress.com/2010/12/mdx2_result5.png" alt="" width="531" height="102" /></a></p>
<p>Sweet. Remember the rules from the Tuple episode? When a tuple is defined, every single dimension is actually represented in the query, even if you don’t explicitly name it. It defines the tuple members used according to the Other Three Rules*: Default Member, then (All) Members, then First Member. Before, the Sales Reason dimension was accounted for, but it was using the (All) Members rule because a Default Member isn’t defined. This time around, we’re telling the query exactly which members from the Sales Reason dimension to return, as well as which order to return them in. I could go on. I could define this tuple out to my heart’s content. BUT, there is one big rule to follow: The Set requires that the dimensions are given in the same order in every tuple. The following query will return an error:</p>
<pre>SELECT
{
	 ([Sales Reason].[Sales Reason].[Quality], [Sales Territory].[Sales Territory Country].[Australia])
	,([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Price])
	,([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Magazine Advertisement])
	,([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Review])
	,([Sales Territory].[Sales Territory Country].[Australia], [Sales Reason].[Sales Reason].[Manufacturer])
} ON COLUMNS
FROM [Adventure Works];</pre>
<p><a href="http://datachix.files.wordpress.com/2010/12/mdx2_result6.png"><img class="alignnone size-full wp-image-768" src="http://datachix.files.wordpress.com/2010/12/mdx2_result6.png" alt="" width="498" height="105" /></a></p>
<p>Again, this query is like stabbing your knife into the cake and expecting to come out with a beautiful slice. MDX likes clean cuts. So, it wants consistently defined tuples. Humor it.</p>
<p>Okay. Remember how I told you to quit after making the first cut into your cake? Go ahead, make the second cut. I’ll wait…… Oh good, you’re back. Hey, you have a little icing on your chin. Right there. No, there. To the left. There you go, got it. So, you cut twice (asked for two Sets) and ended up with a nice piece of cake (Data) didn’t you? Awesome. Let’s continue to wring the life out of this analogy and look at the MDX.</p>
<pre>SELECT
{
	 ([Sales Territory].[Sales Territory Country].[Australia])
	,([Sales Territory].[Sales Territory Country].[Canada])
	,([Sales Territory].[Sales Territory Country].[Germany])
	,([Sales Territory].[Sales Territory Country].[United Kingdom])
	,([Sales Territory].[Sales Territory Country].[United States])
} ON COLUMNS
,
{
	 ([Date].[Calendar Year].[CY 2005])
	,([Date].[Calendar Year].[CY 2006])
	,([Date].[Calendar Year].[CY 2007])
} ON ROWS
FROM [Adventure Works];</pre>
<p><a href="http://datachix.files.wordpress.com/2010/12/mdx2_result7.png"><img class="alignnone size-full wp-image-769" src="http://datachix.files.wordpress.com/2010/12/mdx2_result7.png" alt="" width="512" height="124" /></a></p>
<p>Okay, so what’s this doing? Well, it’s saying, “Hey, MDX, I want you to go out and find the Reseller Sales Amount. Then, I want you to break it down for me. I want column headers that show the Countries I’ve specified. Then, I want row headers that show the years 2005 – 2007. Finally, I want the portion of the overall Reseller Sales Amount in a cell at the intersection of the Country and the Year.”</p>
<p>I said that we weren&#8217;t going to get into functions yet, but I do have one little thing I want to close with. The Members function. This is sort of like the &#8220;SELECT *&#8221; of MDX. You can tag a &#8220;.Members&#8221; onto the end of a [Dimension].[Hierarchy].[Level] reference (or even a [Dimension].[Hierarchy] reference) inside a tuple. I&#8217;m going to re-write the COLUMN set to return pretty much the same data, but with less carpal-tunnel syndrome.</p>
<pre>SELECT
{
	 ([Sales Territory].[Sales Territory Country].Members)
} ON COLUMNS
,
{
	 ([Date].[Calendar Year].[CY 2005])
	,([Date].[Calendar Year].[CY 2006])
	,([Date].[Calendar Year].[CY 2007])
} ON ROWS
FROM [Adventure Works];</pre>
<p><a href="http://datachix.files.wordpress.com/2010/12/mdx2_result8.png"><img class="alignnone size-full wp-image-770" src="http://datachix.files.wordpress.com/2010/12/mdx2_result8.png" alt="" width="755" height="137" /></a></p>
<p>Check that out. It even gives us members we didn&#8217;t know to ask for, including an (All) Members summary. This function is great for a couple of reasons: 1) You don&#8217;t have to type so much. 2) If you don&#8217;t know all of the hierarchy members, you don&#8217;t have to go look them up.  And, if the members change down the road, you&#8217;re not slogging through MDX queries manually updating them. </p>
<p>Alright, so maybe we’re not to awesome-ness yet, but you have to admit, not too shabby. There are about a bazillion other things you can do with these sets, and we’ll get to them. But for now, let’s take a break and enjoy the lovely piece of cake… er, data we’ve created.</p>
<p>Query on, my friends.</p>
<p>*Other Three Rules because the Three Rules are strictly reserved for references to Isaac Asimov’s I, Robot and the Foundation series. If you’ve only seen he movie, for the love of all that is good in this world, go read the books. While you’re at it, go read Starship Troopers by Robert Heinlein. That book got the short end of the movie stick too. Seriously, Denise Richards? Denise Richards?!? They should have made her shave her head to stay true to the story.</p>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/2010/12/22/adventures-in-mdx-sets/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Adventures in MDX &#8211; Tuples</title>
		<link>http://key2consulting.com/Blogs/ahammonds/2010/11/11/adventures-in-mdx-tuples/</link>
		<comments>http://key2consulting.com/Blogs/ahammonds/2010/11/11/adventures-in-mdx-tuples/#comments</comments>
		<pubDate>Thu, 11 Nov 2010 21:49:24 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[SSAS]]></category>
		<category><![CDATA[MDX]]></category>
		<category><![CDATA[Tuples]]></category>

		<guid isPermaLink="false">http://6.44</guid>
		<description><![CDATA[Personal Note: I wrote the bulk of this post last night. Before I read Chris Webb&#8217;s blog post about the future of SSAS and MDX. I almost didn&#8217;t post this after reading that. But you know what? I&#8217;m learning MDX anyway. I hate the idea that Microsoft would potentially remove aspects of the BI stack [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://datachix.files.wordpress.com/2010/11/tuple-visual.png"></a>Personal Note: I wrote the bulk of this post last night. Before I read <a class="wpGallery" href="http://cwebbbi.wordpress.com/2010/11/11/pass-summit-day-2/?utm_source=feedburner&amp;utm_medium=feed&amp;utm_campaign=Feed%3A+wordpress%2FCpjz+%28Chris+Webb%27s+BI+Blog%29&amp;utm_content=Google+Reader" target="_blank">Chris Webb&#8217;s blog post </a>about the future of SSAS and MDX. I almost didn&#8217;t post this after reading that. But you know what? I&#8217;m learning MDX <a href="http://key2consulting.com/Blogs/ahammonds/files/2010/11/ElizaFlowerGirl.jpg"></a>anyway. I hate the idea that Microsoft would potentially remove aspects of the BI stack because the learning curve is too high. If I can&#8217;t keep up, then put me out of a job. Don&#8217;t dumb-down the functionality. That being said, I think there&#8217;s a lot of value in understanding the language that accesses any data store. It forces you to think about the internal structure of what you&#8217;re working with, and therefore, I see value in learning MDX either way. (But really&#8230; I was so disheartened after reading about some of the coming changes. You MS peeps had better know what you&#8217;re doing!) On to the original post:</p>
<p>My favorite movie is <a class="wpGallery" href="http://www.imdb.com/title/tt0058385/" target="_blank">My Fair Lady</a>. I love Audrey Hepburn. I love the Pygmalion story. Quick aside: I used to tell people that my parents named me after her. They didn&#8217;t, and the true story is convoluted. My mom loved the name Audrey Dalton (my middle name is Dalton), which was the name of a movie star. My great-great grandmother was Dalton Harris, and she thought it would be cool to name me after her and the actress. Then she met my dad. His mom&#8217;s name was Audrey. (She went by her middle name, Geraldine, which I never understood&#8230; but I digress.) Anyway, when I was born, she told everyone that I was named for my <a class="wp-caption-dd" href="http://www.flickr.com/photos/yellowstickies/3706250594/in/set-72157621074660365/" target="_blank">paternal grandmother </a>and my maternal great-great grandmother. When, secretly, I was just named after an actress with a name she liked. I&#8217;m glad she told me this. (Audrey Dalton was a total hottie.)</p>
<p><a href="http://datachix.files.wordpress.com/2010/11/audrey-dalton-new.png"></a><a href="http://datachix.files.wordpress.com/2010/11/audrey-dalton-new.png"><img class="alignnone size-medium wp-image-429" src="http://datachix.files.wordpress.com/2010/11/audrey-dalton-new.png?w=240" alt="" width="240" height="300" /></a>&lt;&#8211; Audrey Dalton, HOTTIE (courtesy of Ballybane Enterprise Centre <a href="http://www.bbec.ie/blog/?p=708">http://www.bbec.ie/blog/?p=708</a>)</p>
<p>This week, I&#8217;ve decided to start digging into MDX. There are three reasons for this:</p>
<p>1) It&#8217;s <a class="wp-caption-dd" href="http://www.sqlpass.org/summit/na2010/" target="_blank">PASS Summit </a>week. While I&#8217;m not there, I&#8217;m trying to get into the spirit of things by learning something new.<br />
2) I&#8217;m just not good at MDX. There is no excuse for this.<br />
3) I&#8217;m gearing up for my <a class="wp-caption-dd" href="http://www.microsoft.com/learning/en/us/exam.aspx?ID=70-452&amp;locale=en-us" target="_blank">MCITP exam </a>in Business Intelligence 2008. I hear rumor that there are MDX questions.</p>
<p>So anyway, I feel a lot like Eliza Doolittle this week. If you&#8217;re not familiar with the story, she is the subject of a bet between Henry Higgins and Colonel Pickering. They bet that Prof. Higgins can&#8217;t pass her off as a Lady (with a capital &#8220;L&#8221;) in a year. She&#8217;s just a lowly flower girl, complete with cockney accent. In order to refine her, he has to teach her how to speak again. It&#8217;s her own language, but she has to learn how to use it in a totally unfamiliar way. Instead of saying, &#8220;In &#8216;artford, &#8216;ereford, and &#8216;ampshire, &#8216;urricanes &#8216;ardly h-ever &#8216;appen&#8221;, she has to learn to say, &#8220;In Hartford, Hereford, and Hampshire, hurricanes hardly ever happen&#8221;. (Swear to cheesus, I haven&#8217;t hit IMDB yet&#8230; I really love this movie) Same words, same meaning, totally different accent.</p>
<p>Rather than a flower girl trying to sound like a Lady, I&#8217;m a T-SQL girl trying to sound like an MDX Lady. Or something like that. You know what I mean.</p>
<p><a href="http://datachix.files.wordpress.com/2010/11/elizaflowergirl.jpg"><img class="alignnone size-medium wp-image-430" src="http://datachix.files.wordpress.com/2010/11/elizaflowergirl.jpg?w=232" alt="" width="232" height="300" /></a> &lt;&#8211; T-SQL Flower Girl<a href="http://datachix.files.wordpress.com/2010/11/elizaflowergirl.jpg"></a></p>
<p>To get started, I picked up <a class="wp-caption-dd" href="http://www.amazon.com/Microsoft-SQL-Server-2008-Step/dp/0735626189/ref=sr_1_3?ie=UTF8&amp;s=books&amp;qid=1289509892&amp;sr=8-3" target="_blank">Microsoft SQL Server 2008 MDX Step by Step </a>(by Brian C. Smith, C. Ryan Clay, and Hitachi Consulting). I&#8217;m starting with the basics, so right now I&#8217;m in &#8220;SELECT * FROM&#8221; territory. Or, &#8220;SELECT FROM &#8221; territory, since we&#8217;re talking MDX.</p>
<p>Transitioning from T-SQL to MDX is not easy. The syntax is just familiar enough to me to trip me up. I keep catching myself trying to equate a query against a cube to a query against a relational data store. It&#8217;s not the same, and it has been tough for me to wrap my head around it. But, &#8220;I washed my face and &#8216;ands before I come, I did&#8221;, so I think I&#8217;m ready to get started.</p>
<p>So far, I&#8217;ve learned about one important concept: Tuples. The point of this blog post is to force myself to regurgitate what I&#8217;ve learned, because to paraphrase something Jen McCown (<a class="wp-caption-dd" href="http://www.midnightdba.com/Jen/" target="_blank">Blog</a> | <a class="wp-caption-dd" href="http://twitter.com/#!/search/users/midnightdba" target="_blank">Twitter</a>) said the other day, you don&#8217;t really know something until you&#8217;ve taught it. True that. Please keep reading, but also read a book by an expert. I&#8217;ve been happy with the Step by Step book so far.</p>
<p>Wait&#8230; one more silly analogy. Writing T-SQL is a bit like cutting out paper dolls. It can be complex, but it&#8217;s just two dimensional space. Writing MDX is like chiseling a hole into a big rock at a specific point. It&#8217;s n-dimensional space. While a bit goofy, this visualization has really helped me draw a line between T-SQL and MDX.</p>
<p><strong>Tuples (as Translated by Me)</strong></p>
<p>A tuple is basically the identifying characteristics of a cell inside a cube. Really, a data point inside a cube. Say I have three dimensions, Actor, Movie, and Year. Say I have a Measure Group that includes Budget Amount. Say I wanted to find the cell, or data point, identifying the budget for the movie My Fair Lady starring Audrey Hepburn that came out in 1964. I&#8217;d look at the attribute-hierarchies Audrey Hepburn, My Fair Lady, and 1964. (Hey, I didn&#8217;t say it was a well designed cube!) Those identifying characteristics of the cell are the tuple, which would be formatted something like this in MDX;</p>
<p>(<br />
[Actor].[Audrey Hepburn]<br />
,[Movie].[My Fair Lady]<br />
,[Year].[1964]<br />
,[Measures].[Budget Amount]<br />
)</p>
<p>Another way to look at it is in terms of math. I always swore that geometry and algebra were pointless in high school. Well, Mr. Smith, you were right. I&#8217;m about to talk axes. (axises? axii?) Each of my attribue-hierarchies and my measure group make up an axis within my cube. Don&#8217;t even try to visualize a 4-dimensional cube. I did, and it made my head hurt when I ran out of 3-dimensional space. Let&#8217;s label each axis:</p>
<p>[Actor].[Audrey Hepburn] = a1<br />
[Movie].[My Fair Lady] = a2<br />
[Year].[1964] = a3<br />
[Measures].[Budget Amount] = a4</p>
<p>Now, if I want to identify the point that is the intersection, my notation would look something like this: (a1, a2, a3, a4). I also imagine four lines (in 2-dimensional space, all intersecing one another at one point. That point is my tuple.</p>
<p><a href="http://datachix.files.wordpress.com/2010/11/tuple-visual1.png"></a></p>
<p><a href="http://datachix.files.wordpress.com/2010/11/tuple-visual2.png"><img class="alignnone size-medium wp-image-446" src="http://datachix.files.wordpress.com/2010/11/tuple-visual2.png?w=300" alt="" width="300" height="165" /></a></p>
<p>The MDX syntax for my query looks like this:</p>
<p>SELECT<br />
FROM [Pretend Movie Cube]<br />
WHERE<br />
(<br />
[Actor].[Audrey Hepburn]<br />
,[Movie].[My Fair Lady]<br />
,[Year].[1964]<br />
,[Measures].[Budget Amount]<br />
);</p>
<p>It would return one value: $17,000,000</p>
<p>Some Key Points:</p>
<p>1) Every attribute-hierarchy gets an axis, NOT every Dimension. So, if I had two attribute-hierarchies within my Actor dimension, Audrey Hepburn and Rex Harrison, they all have an axis. I could actually reference the same Dimension multiple times like so:</p>
<p>(<br />
[Actor].[Audrey Hepburn]<br />
,[Actor].[Rex Harrison] &lt;&#8211;Henry Higgins!<br />
,[Movie].[My Fair Lady]<br />
,[Year].[1964]<br />
,[Measures].[Budget Amount]<br />
);</p>
<p>2) Measures each get an axis. They are treated differently at design time, but for the purposes of seeking out that one cell or set of cells, it&#8217;s treated just the same as an attribute hierarchy.</p>
<p>3) Analysis Services allows you to be lazy. You can define what&#8217;s called a Partial Tuple, leaving out some axis references. But&#8230; it&#8217;s going to try to figure out where on that missing axis you were headed. It&#8217;s going to go in this order:<br />
1 &#8211; Default member (defined at design time)<br />
2 &#8211; (All) member &#8211;remember that Measures don&#8217;t have an (All) member<br />
3 &#8211; First member</p>
<p>Am I getting this? Have I missed the boat? Close, but no cigar? Any other cliche suggesting I don&#8217;t know what I&#8217;m talking about?</p>
<div class="mcePaste" style="width: 1px;height: 1px;overflow: hidden">﻿</div>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/ahammonds/2010/11/11/adventures-in-mdx-tuples/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Use a Common Table Expression and the ROW_NUMBER() Function to Eliminate Duplicate Rows</title>
		<link>http://key2consulting.com/Blogs/ahammonds/2010/11/11/use-a-common-table-expression-and-the-row_number-function-to-eliminate-duplicate-rows/</link>
		<comments>http://key2consulting.com/Blogs/ahammonds/2010/11/11/use-a-common-table-expression-and-the-row_number-function-to-eliminate-duplicate-rows/#comments</comments>
		<pubDate>Thu, 11 Nov 2010 20:29:35 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[Common Table Expression]]></category>
		<category><![CDATA[CTE]]></category>
		<category><![CDATA[OVER]]></category>
		<category><![CDATA[ROW_Number()]]></category>

		<guid isPermaLink="false">http://6.33</guid>
		<description><![CDATA[Or, removing duplicates with panache&#8230; I think of them as rogue tables. They&#8217;re quick and dirty and cause you a world of hurt before it&#8217;s all over. We&#8217;ve all got them. Like those photos from college that guarantee you&#8217;ll never run for public office, rogue tables are best left hidden. But, you&#8217;re always wondering when [...]]]></description>
			<content:encoded><![CDATA[<p>Or, removing duplicates with panache&#8230;</p>
<p>I think of them as rogue tables.  They&#8217;re quick and dirty and cause you a world of hurt before it&#8217;s all over.  We&#8217;ve all got them.  Like those photos from college that guarantee you&#8217;ll never run for public office, rogue tables are best left hidden.  But, you&#8217;re always wondering when they&#8217;re going to show up in public.  I admit, I have one.  There, I said it.  It&#8217;s a config table for our ETL processes that we threw out there at the last minute to handle a data-driven filter on an import process.  Didn&#8217;t stop and think about a primary key or constraints, just threw it into the database to get something done before the production push.  Yes, you heard right.  Production.  Oy vey.  </p>
<p>The other day, I was making use of my roguish table to add a few rows (in my development environment, thank the database gods).  Trying to multi-task, I ran a scripted insert on it.  Then I answered the phone, responded to an IM, read an e-mail, and turned around and executed the same blasted statement.  Without constraints of any kind to save my distracted soul, I now had two of each row.  I don&#8217;t care what those guys at Wrigley&#8217;s say, sometimes two of something doesn&#8217;t double the fun.  It did double the headache I was already nursing from a morning status meeting.  </p>
<p>I needed to get those extra rows out with as little pain as possible.  I needed to make it interesting.  Look, I find my thrills wherever I can.  I took a CTE/ROW_NUMBER() approach to finding and removing my duplicate rows.  First, let&#8217;s talk about these two constructs.  </p>
<p><a title="Common Table Expressions" href="http://msdn.microsoft.com/en-us/library/ms190766.aspx" target="_blank">Common Table Expressions (CTE)</a></p>
<p>I&#8217;ve heard CTE&#8217;s described a few different ways:  in-line temp table, in-line view, work area, etc.  What it does is allow you to create a temporary, named result set.  It persists (is scoped) for a single SELECT, INSERT, UPDATE, or DELETE statement.  It is a lot like creating a temporary table or using a table variable, but with about half the hassle.  The syntax is crazy-simple:  </p>
<p>WITH &lt;any name you want&gt; AS<br />
(<br />
SELECT col1, col2<br />
FROM tblx<br />
)<br />
&lt;Your SELECT, INSERT, UPDATE, or DELETE goes here&gt;; </p>
<p>Basically, you can prep data to be used in the statement that immediately follows your WITH.  It&#8217;s great for any pesky operation that just won&#8217;t work well in a single statement.  Personally, I think it is easier to read, too.  One note:  If you&#8217;re running multiple statements in a batch, make sure you end the statement just prior to the WITH with a semi-colon.  In fact, just end everything with a semi-colon.  It makes you look detail-oriented.  </p>
<p><a title="ROW_NUMBER()" href="http://msdn.microsoft.com/en-us/library/ms186734.aspx" target="_blank">ROW_NUMBER()</a></p>
<p><a title="ROW_NUMBER()" href="http://msdn.microsoft.com/en-us/library/ms186734.aspx"></a>ROW_NUMBER() falls into the &#8220;ranking functions&#8221; category.  With this quite functional function, you can number rows in your result set.  Even better, you can PARTITION BY to split your result set up into groups.  I might not want to see 1-10 as my row numbers, I might want to see 1-5 and 1-5 based on some column that I decide to partition the data by.  Note, this is a horizontal partition of rows.  If you&#8217;re trying to partition your columns vertically, we might need to talk over a beer or two.  You&#8217;ve got bigger issues than duplicate rows.  The syntax takes a little getting used to, but once you break it down, it makes pretty decent sense:  </p>
<p>ROW_NUMBER() OVER (PARTITION BY colx, coly&#8230; ORDER BY colz) as aliasname</p>
<p>Let&#8217;s take a closer look:  </p>
<ul>
<li>ROW_NUMBER() &#8211; you&#8217;re instructing the query engine to give you back a column with row numbers.  These come back as a bigint.  </li>
<li>OVER &#8211; you&#8217;re telling it that you&#8217;re about to give it some more information.  Specifically, an ORDER BY and an optional PARTITION BY. </li>
<li>PARTITION BY &#8211; you&#8217;re providing instructions about how to group the rows.  You can partition by multiple columns.  This works a little like a GROUP BY clause.</li>
<li>ORDER BY &#8211; what order do you want your rows numbered in?  If you have a PARTITION BY, it&#8217;ll order within each partition.  If you&#8217;ve left the PARTITION BY out, it&#8217;ll order the entire result set</li>
<li>alias &#8211; you&#8217;re going to have to alias this new column so that you can reference it later on  </li>
</ul>
<p>Now that we&#8217;re all CTE and ROW_NUMBER() experts, let&#8217;s talk about how we put these guys to work to undo my bone-headed duplicate row insert.  I&#8217;m scripting an example here, with bonus semi-witty comments.</p>
<p>&#8211;Create the rogue table<br />
IF EXISTS (SELECT * FROM sys.tables WHERE name = N&#8217;TableOfShame&#8217;)<br />
BEGIN<br />
    DROP TABLE TableOfShame<br />
END</p>
<p>CREATE TABLE TableOfShame<br />
(<br />
    ShameCode varchar(4) NULL,<br />
    ShameType varchar(15) NULL,<br />
    ShamePriority varchar(10) NULL<br />
);</p>
<p>&#8211;Insert the rows you really wanted in your table<br />
INSERT INTO TableOfShame<br />
VALUES<br />
    (&#8217;01&#8242;, &#8216;Chagrin&#8217;, &#8216;Low&#8217;),<br />
    (&#8217;02&#8242;, &#8216;Disgust&#8217;, &#8216;High&#8217;),<br />
    (&#8217;03&#8242;, &#8216;Abashment&#8217;, &#8216;Medium&#8217;),<br />
    (&#8217;04&#8242;, &#8216;Embarassment&#8217;, &#8216;Low&#8217;),<br />
    (&#8217;05&#8242;, &#8216;Humiliation&#8217;, &#8216;Medium&#8217;);</p>
<p>/* Answer the phone, check your e-mail, listen to your co-worker tell hilarious story, get generally distracted */</p>
<p>&#8211;Oops, insert them again (Note the sleek and modern <a title="Table Value Constructor" href="http://technet.microsoft.com/en-us/library/dd776382.aspx" target="_blank">Table Value Constructor</a>)<br />
INSERT INTO TableOfShame<br />
VALUES<br />
    (&#8217;01&#8242;, &#8216;Chagrin&#8217;, &#8216;Low&#8217;),<br />
    (&#8217;02&#8242;, &#8216;Disgust&#8217;, &#8216;High&#8217;),<br />
    (&#8217;03&#8242;, &#8216;Abashment&#8217;, &#8216;Medium&#8217;),<br />
    (&#8217;04&#8242;, &#8216;Embarassment&#8217;, &#8216;Low&#8217;),<br />
    (&#8217;05&#8242;, &#8216;Humiliation&#8217;, &#8216;Medium&#8217;);</p>
<p>&#8211;Look what you&#8217;ve done!  Damn that funny anecdote that completely derailed your train of thought.<br />
SELECT * FROM TableOfShame;</p>
<p>&#8211;Find the duplicates, give them something differentiating (a row number!)<br />
/* Based on my made-up business rules, I&#8217;ve partitioned by something resembling a business key.  It&#8217;ll give me unique groups, which is sort of an oxymoron, but you know what I mean.  */<br />
WITH cte_FindDuplicateShame as<br />
(<br />
SELECT ShameCode, ShameType, ShamePriority,<br />
ROW_NUMBER() over(PARTITION BY ShameCode, ShameType ORDER BY ShameCode DESC) as RowNum<br />
FROM dbo.TableOfShame<br />
)<br />
SELECT ShameCode, ShameType, ShamePriority, RowNum<br />
FROM cte_FindDuplicateShame<br />
ORDER BY ShameCode, ShameType, RowNum;</p>
<p>&#8211;Now, we know what we have, let&#8217;s delete the duplicates<br />
/*Note that I&#8217;m actually issuing the DELETE against the CTE.  Keep in mind that the CTE is only a temporary, named result set off of a physical table (sort of like an in-line view).  Running the DELETE against the CTE will affect the physical table that was used to create the result set. */</p>
<p>WITH cte_FindDuplicateShame as<br />
(<br />
SELECT ShameCode, ShameType, ShamePriority,<br />
ROW_NUMBER() over(PARTITION BY ShameCode, ShameType ORDER BY ShameCode DESC) RowNum<br />
FROM dbo.TableOfShame<br />
)<br />
DELETE cte_FindDuplicateShame<br />
WHERE RowNum &lt;&gt; 1;</p>
<p>&#8211;Aha!  Distraction-created rows are gone.<br />
SELECT *<br />
FROM TableOfShame<br />
ORDER BY ShameCode;</p>
<p>So there you have it.  A mildly interesting way to get myself out of the hole I dug by getting F5 happy.  CTE on, my friends.</p>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/ahammonds/2010/11/11/use-a-common-table-expression-and-the-row_number-function-to-eliminate-duplicate-rows/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Getting Schooled on Dynamic PIVOT</title>
		<link>http://key2consulting.com/Blogs/ahammonds/2010/11/11/getting-schooled-on-dynamic-pivot/</link>
		<comments>http://key2consulting.com/Blogs/ahammonds/2010/11/11/getting-schooled-on-dynamic-pivot/#comments</comments>
		<pubDate>Thu, 11 Nov 2010 20:14:56 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[Database Development]]></category>
		<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[Dynamic]]></category>
		<category><![CDATA[PIVOT]]></category>

		<guid isPermaLink="false">http://6.29</guid>
		<description><![CDATA[I wrote a post about Deconstructing Pivot. With my newfound confidence, I decided to tackle dynamic pivots. This is a common scenario where you need to PIVOT, but you don&#8217;t know exactly what you&#8217;re going to end up with. Basically, you want to allow all of the possible column headers to come back with the [...]]]></description>
			<content:encoded><![CDATA[<p>I wrote a post about <a href="http://key2consulting.com/Blogs/ahammonds/2010/10/28/deconstructing-pivot/" target="_blank">Deconstructing Pivot</a>.  With my newfound confidence, I decided to tackle dynamic pivots.  This is a common scenario where you need to PIVOT, but you don&#8217;t know exactly what you&#8217;re going to end up with.  Basically, you want to allow all of the possible column headers to come back with the aggregated data you need. </p>
<p>If you&#8217;re not familiar with PIVOT, go back and read the original post.  If I&#8217;ve done my job properly, it should make sense.  So, here&#8217;s what I did&#8230; I resisted the urge to hit Google to find a solution to the dynamic pivot problem.  I opened SSMS and said, &#8220;Self, you&#8217;re under a deadline.  Write it and see if you can get it to work all by your lonesome&#8221;.  45 minutes later, I had a working script that produced some cool real-world output, if I do say so myself. </p>
<p>Then, I hit Google.  Then I saw <a href="http://www.eggheadcafe.com/software/aspnet/32646190/hisee-itzik-bengan-example--to-build-dynamic-pivot-creating-and-populating.aspx" target="_blank">Itzik Ben-Gan&#8217;s solution</a>.  My first response was, &#8220;Crap!&#8221;  Actually, it was a much less ladylike expletive than that.  The solution was&#8230; Beautiful.  Elegant.  Blew my method out of the water.  You know how athletes have muscle memory?  Well, developers have it too.  We fall back to what&#8217;s comfortable and familiar.  Sort of like our own version of T-SQL sweatpants and chocolate ice cream.  Before I start in on the comparison of my solution and Itzik&#8217;s, let me say this:  <em>His is so much better than mine.</em>  Did I mention that it was elegant?  And beautiful?  But you know what?  In a real development environment, with deadlines and giant to-do lists, I would have fallen back to my own comfort zone.  I know this.  I also know that next time I need to write a dynamic PIVOT, I&#8217;m going to know how to use his method. </p>
<p>Authors, when asked to give advice to aspiring writers, always say the same thing.  &#8220;Write what you know.&#8221;  For us IT Folk, there&#8217;s a corollary.  &#8220;Write what you know.  Hit the deadline.  Then, go learn a better way.&#8221;  Am I proud that I figured a solution out on my own?  Yup.  Am I a bit deflated that I didn&#8217;t come up with the same solution as Itzik Ben-Gan?  Nope.  Come on, it&#8217;s <em>Itzik</em>. </p>
<p>Personal note:  I hate when I run across someone else&#8217;s T-SQL and ask them, &#8220;How does this work?&#8221;, and their response is, &#8220;I don&#8217;t know, I found it on a blog post/Google/forum.&#8221;  Peeps, this is unacceptable.  Don&#8217;t copy and paste until you understand what you&#8217;re seeing.  Because someday you&#8217;re going to have to maintain that pilfered bit of code.  If you don&#8217;t know what it does, then don&#8217;t use it.  Comprehend your own code.  We all borrow from the experts, but make sure you can explain it in 50 words or le<a href="http://datachix.files.wordpress.com/2010/10/pivot2-audrey-result1.jpg"></a>ss.  If you can&#8217;t, then back away from the Ctrl+V.    Stretch your skills, learn new things, just don&#8217;t jeopardize a project by jumping the gun. </p>
<p>Okay, enough commentary.  On to the solutions.  The trick in a dyamic PIVOT is to create a string that has all of the column headers you need.  This is where he and I diverged wildly.  I fell back on a WHILE Loop over a set of rows contained in a table variable, he used the STUFF function with a FOR XML Path() query output.  I wrote my solution to address the same example from BOL that I ranted about in my first post.  I modified his solution to produce the same output, and to clean out some unused variables that were in the sample I found.  I&#8217;ve also resisted the urge to make little tweaks to my script after doing some extra research.  Truly, I want to make the point that there&#8217;s what works&#8230; and what works beautifully. </p>
<p>My solution: </p>
<p><strong>SET NOCOUNT ON;</strong></p>
<p><strong>DECLARE @vEmployeeIDTable as TABLE </strong><br />
<strong>(</strong><br />
<strong>EmployeeID varchar(20) NOT NULL</strong><br />
<strong>,ProcessedFlag bit NOT NULL DEFAULT(0)</strong><br />
<strong>)</strong></p>
<p><strong>DECLARE @vEmployeeID varchar(20)</strong><br />
<strong>DECLARE @vSQLString varchar(max) = &#8221;</strong><br />
<strong>DECLARE @vEmployeeIDSELECT varchar(max) = &#8221;</strong><br />
<strong>DECLARE @vEmployeeIDFOR varchar(max) = &#8221;</strong><br />
<strong>DECLARE @vLoopCounter varchar(50) = 1</strong></p>
<p><strong>INSERT INTO @vEmployeeIDTable(EmployeeID)</strong><br />
<strong>SELECT DISTINCT EmployeeID </strong><br />
<strong>FROM Purchasing.PurchaseOrderHeader;</strong></p>
<p><strong>WHILE (SELECT count(ProcessedFlag) FROM @vEmployeeIDTable WHERE ProcessedFlag = 0) &gt; 0</strong><br />
<strong>BEGIN</strong></p>
<p><strong> SELECT @vEmployeeID = &#8216;['+cast(MIN(EmployeeID) as<a href="http://datachix.files.wordpress.com/2010/10/pivot2-audrey-result.jpg"></a> varchar(20)) +']&#8216;</strong><br />
<strong> FROM @vEmployeeIDTable</strong><br />
<strong> WHERE ProcessedFlag = 0</strong></p>
<p><strong> SET @vEmployeeIDSELECT = @vEmployeeIDSELECT + @vEmployeeID + &#8216; as Emp&#8217;+@vLoopCounter+&#8217;,&#8217;  </strong><br />
<strong> SET @vEmployeeIDFOR = @vEmployeeIDFOR + @vEmployeeID +&#8217;,&#8217;  </strong><br />
<strong> </strong></p>
<p><strong> UPDATE @vEmployeeIDTable</strong><br />
<strong> SET ProcessedFlag = 1</strong><br />
<strong> WHERE EmployeeID = cast(substring(@vEmployeeID,2, LEN(@vEmployeeID)-2) as int)</strong><br />
<strong> </strong><br />
<strong> SET @vLoopCounter = @vLoopCounter + 1</strong></p>
<p><strong>END</strong></p>
<p><strong>SET @vEmployeeIDSELECT = SUBSTRING(@vEmployeeIDSELECT,1, len(@vEmployeeIDSELECT)-1)</strong><br />
<strong>SET @vEmployeeIDFOR = SUBSTRING(@vEmployeeIDFOR,1, len(@vEmployeeIDFOR)-1)</strong></p>
<p><strong>SET @vSQLString = &#8216;</strong><br />
<strong>SELECT VendorID, &#8216;+@vEmployeeIDSELECT +&#8217;</strong><br />
<strong>FROM </strong><br />
<strong>(SELECT PurchaseOrderID, EmployeeID, VendorID</strong><br />
<strong>FROM Purchasing.PurchaseOrderHeader) p</strong><br />
<strong>PIVOT</strong><br />
<strong>(</strong><br />
<strong>COUNT (PurchaseOrderID)</strong><br />
<strong>FOR EmployeeID IN</strong><br />
<strong>(&#8216;+@vEmployeeIDFOR+&#8217;)</strong><br />
<strong>) AS pvt</strong><br />
<strong>ORDER BY pvt.VendorID; &#8216;</strong></p>
<p><strong>PRINT @vSQLString</strong></p>
<p><strong>EXECUTE (@vSQLString)</strong></p>
<p>So, a quick rundown of what I did: </p>
<p>1) Create a table variable (@vEmployeeIDTable).  Populate it with DISTINCT EmployeeID&#8217;s from Purchasing.PurchaseOrderHeader.<br />
2) Declare the following variables:<br />
 a) @vEmployeeID &#8211; holds the EmployeeID I&#8217;m concatenating into the string during the WHILE loop<br />
 b) @vEmployeeIDSELECT &#8211; holds the EmployeeID string that I&#8217;ll use in the SELECT clause of my PIVOT.  I separate this one out because I want to concatenate the column aliases just as they were in the BOL example.<br />
 c) @vEmployeeIDFOR &#8211; holds the EmployeeID string that I use in the FOR clause of my PIVOT.  I don&#8217;t need column aliases here.<br />
 d) @vLoopCounter &#8211; holds a counter as I loop through the string concatenation.  I use it to help name my column aliases (Emp1, Emp2&#8230;).  The 1 and 2 are coming from this variable<br />
3) While I have unprocessed rows in my table variable, I loop through with a WHILE<br />
 a) Set @vEmployeeID to the minimum EmployeeID that hasn&#8217;t been processed.  I also concatenate on the brackets I need since these will become column names.  (Those brackets were a pain.  I kept having to work around them.  Another place where Ben-Gan&#8217;s method was more elegant)<br />
 b) Set @vEmployeeIDSELECT to itself plus the EmployeeID being processed (@vEmployeeID), and then set up the alias.  (as &#8216;Emp&#8217;+@vLoopCounter).  Important note:  I initialized the variable as an empty string (&#8221;).  This is so that I&#8217;m not trying concatenate a NULL value to a string on the first go-round.<br />
 c) Set @vEmployeeIDFor to itself plus the EmployeeID being processed<br />
 d) Update @vEmployeeIDTable to indicate that the EmployeeID has been added to the string variables<br />
 e) Update @vLoopCounter so that the next table alias will be the next number<br />
4) Clean up the extra commas at the end of the string variables<br />
5) Put the whole thing together in @vSQLString<br />
 a) Place the @vEmployeeIDSELECT variable where it needs to go<br />
 b) Place the @vEmployeeIDFOR variable where it needs to go<br />
6) Execute the variable @vSQLString</p>
<p>This is the output: </p>
<p><a href="http://datachix.files.wordpress.com/2010/10/pivot2-audrey-result3.jpg"><img class="alignnone size-full wp-image-392" src="http://datachix.files.wordpress.com/2010/10/pivot2-audrey-result3.jpg" alt="" width="620" height="235" /></a><br />
<a class="alignleft" href="http://datachix.files.wordpress.com/2010/10/pivot2-audrey-result2.jpg"></a></p>
<p>Okay, not bad.  Now, the elegant Itzik Ben-Gan solution: </p>
<p><strong>DECLARE</strong><br />
<strong>  @cols AS NVARCHAR(MAX),</strong><br />
<strong>  @sql  AS NVARCHAR(MAX);</strong></p>
<p><strong>SET @cols = STUFF(</strong><br />
<strong>  (SELECT N&#8217;,&#8217; + QUOTENAME(EmployeeID) AS [text()]</strong><br />
<strong>   FROM (SELECT DISTINCT EmployeeID FROM Purchasing.PurchaseOrderHeader) AS Y</strong><br />
<strong>   ORDER BY EmployeeID</strong><br />
<strong>   FOR XML PATH(&#8221;)),</strong><br />
<strong>  1, 1, N&#8221;);</strong><br />
<strong>  </strong><br />
<strong>SET @sql = N&#8217;SELECT &#8216;+@cols +&#8217;</strong><br />
<strong>FROM (SELECT VendorID, EmployeeID, PurchaseOrderID</strong><br />
<strong>      FROM Purchasing.PurchaseOrderHeader) AS D</strong><br />
<strong>  PIVOT(COUNT(PurchaseOrderID) </strong><br />
<strong>  FOR EmployeeID IN(&#8216; + @cols + N&#8217;)) AS P</strong><br />
<strong>ORDER BY P.VendorID;&#8217;;</strong><br />
<strong>  </strong><br />
<strong>PRINT @SQL</strong></p>
<p><strong>EXEC sp_executesql @sql;</strong><br />
<strong>GO</strong></p>
<p>I know, right?  Elegant.  So what did he do?</p>
<p>1) Declared a couple of variables<br />
 a) @cols &#8211; holds the string of column values for the PIVOT<br />
 b) @sql &#8211; holds the SQL statment that gets executed<br />
2) Used a FOR XML PATH(&#8221;) command to concatenate the string.  This is cool.  The query pulls EmployeeID&#8217;s out of a derived table in the FROM Clause.  He orders by EmployeeID (which is not required), and outputs the result of this query using FOR XML PATH(&#8221;).  The FOR XML PATH(&#8221;) clause creates a single row that looks like this: </p>
<p> ,[250],[251],[252],[253],[254],[255],[256],[257],[258],[259],[260],[261]</p>
<p> Wow, exactly what we need for the PIVOT.  Well, almost.  That&#8217;s what the STUFF function is for.  Getting rid of &#8220;almost&#8221;.</p>
<p>3) Also, see how he used QUOTENAME to add the brackets he needed? </p>
<p> QUOTENAME(EmployeeID) AS [text()]</p>
<p>4) Then, since that leading comma (,[250]) is not needed, he uses the STUFF command to strip it off.  STUFF looks like this: </p>
<p> STUFF ( character_expression , start , length ,character_expression )</p>
<p> a) character_expression &#8211; the results of the query containing the FOR XML PATH(&#8221;) output<br />
 b) start &#8211; first character<br />
 c) length &#8211; how many characters to replace with what we&#8217;re &#8220;stuffing&#8221; in.  In this case, a length of 1.<br />
 d) character_expression &#8211; an empty string, which is what&#8217;s&#8217; &#8220;stuffed&#8221; into the first character expression, eliminating the comma. </p>
<p> Try this to illustrate it much more simply: </p>
<p> SELECT STUFF(&#8216;abcdef&#8217;, 1, 1, &#8221;);</p>
<p> Your result is:  &#8216;bcdef&#8217;.  The empty string he specified basically replaces the first character which is the comma we don&#8217;t want.  Seriously, I had to run the baby STUFF to understand it properly.  The beauty of STUFF over SUBSTRING is that SUBSTRING requires you to tell the function the length of the resulting string, which would require a LEN function over the entire subquery to get it right.  It saves you having to execute that bad boy more than once. </p>
<p>5) Finally, he just puts the PIVOT query into @sql, concatenating in @cols where he needs to, and then executes it. </p>
<p>This is his output: </p>
<p><a href="http://datachix.files.wordpress.com/2010/10/pivot2-itzik-result1.jpg"><img class="alignnone size-full wp-image-393" src="http://datachix.files.wordpress.com/2010/10/pivot2-itzik-result1.jpg" alt="" width="495" height="237" /></a></p>
<p><a href="http://datachix.files.wordpress.com/2010/10/pivot2-itzik-result.jpg"></a></p>
<p>So he didn&#8217;t do pretty column aliases, but the important data is the same.  And just take a look at the execution plans.  That&#8217;s where I do feel just a bit deflated.  Mine is monstrous.  His?  TWO queries.  TWO!  But that&#8217;s not the point.  The point is, I had a blast figuring out how to write my own dynamic PIVOT.  I had even more fun dissecting Itzik Ben-Gan&#8217;s method.  (Yeah, I know.  I&#8217;m a dork.)  And, you can bet your sweet bippy that I&#8217;ll be working to make sure that FOR XML PATH, STUFF, and QUOTENAME all become part of my T-SQL muscle memory.</p>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/ahammonds/2010/11/11/getting-schooled-on-dynamic-pivot/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Deconstructing PIVOT</title>
		<link>http://key2consulting.com/Blogs/ahammonds/2010/10/28/deconstructing-pivot/</link>
		<comments>http://key2consulting.com/Blogs/ahammonds/2010/10/28/deconstructing-pivot/#comments</comments>
		<pubDate>Thu, 28 Oct 2010 14:18:08 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[Database Development]]></category>
		<category><![CDATA[T-SQL]]></category>

		<guid isPermaLink="false">http://6.17</guid>
		<description><![CDATA[I&#8217;m intimidated by PIVOT. I&#8217;ve had a heck of a time wrapping my head around it, which is shameful, because Junior Accountants have been making pivot charts in Excel for years. They get it, so why can&#8217;t I? Well, I&#8217;ve got a few theories&#8230; Anyway, I finally got into a situation where I couldn&#8217;t avoid [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m intimidated by PIVOT. I&#8217;ve had a heck of a time wrapping my head around it, which is shameful, because Junior Accountants have been making pivot charts in Excel for years. They get it, so why can&#8217;t I? Well, I&#8217;ve got a few theories&#8230; Anyway, I finally got into a situation where I couldn&#8217;t avoid it, and I had to dig in there and learn it. Nothing like a deadline to make you act like a proper student.</p>
<p>I went to BOL, and looked it up. Now, I&#8217;m a fan of Books Online. It saves my tush daily. But in this case&#8230; I&#8217;m sorry, but the explanation is nonsensical. I mean, I read it, and what I comprehend is, &#8220;blah, blah, PIVOT, blah, you&#8217;re an idiot, Audrey, just give up now&#8221;.</p>
<p>So, being forced to use a PIVOT, I had to break it down into chunks that my tiny brain could consume. So, first, let&#8217;s look at the BOL syntax:</p>
<p>SELECT &lt;non-pivoted column&gt;,</p>
<p>[first pivoted column] AS &lt;column name&gt;,</p>
<p>[second pivoted column] AS &lt;column name&gt;,</p>
<p>&#8230;</p>
<p>[last pivoted column] AS &lt;column name&gt;</p>
<p>FROM</p>
<p>(&lt;SELECT query that produces the data&gt;)</p>
<p>AS &lt;alias for the source query&gt;</p>
<p>PIVOT</p>
<p>(</p>
<p>&lt;aggregation function&gt;(&lt;column being aggregated&gt;)</p>
<p>FOR</p>
<p>[&lt;column that contains the values that will become column headers&gt;]</p>
<p>IN ( [first pivoted column], [second pivoted column],</p>
<p>&#8230; [last pivoted column])</p>
<p>) AS &lt;alias for the pivot table&gt;</p>
<p>&lt;optional ORDER BY clause&gt;;<br />
Hoo-kay. I&#8217;m going to step you through my process of understanding this so I could construct my own PIVOT. I&#8217;m even going to use the complex pivot example from BOL, the AdventureWorks2008 database. We&#8217;re going in this order: FROM, PIVOT, FOR, SELECT.</p>
<p>But first, some rules. There are always rules:</p>
<p><strong>RULES:</strong><br />
1) You have to know how many columns you&#8217;re going to end up with after the PIVOT. This means that this operation is great for things like months in a year, not so great for a varying number of pivoted columns. You can tell it which columns to return, but the bottom line is you need to know what your output should look like. If you want to break this rule, you&#8217;re writing dynamic SQL.<br />
2) You&#8217;re going to have to aggregate. Even if you don&#8217;t really want to. It&#8217;s required, but as always, there are ways to work the syntax.</p>
<p><strong>THE BOL QUERY EXAMPLE: </strong></p>
<p>SELECT VendorID, [250] AS Emp1, [251] AS Emp2, [256] AS Emp3, [257] AS Emp4, [260] AS Emp5<br />
FROM<br />
(SELECT PurchaseOrderID, EmployeeID, VendorID<br />
FROM Purchasing.PurchaseOrderHeader) p<br />
PIVOT<br />
(<br />
COUNT (PurchaseOrderID)<br />
FOR EmployeeID IN<br />
( [250], [251], [256], [257], [260] )<br />
) AS pvt<br />
ORDER BY pvt.VendorID;</p>
<p><strong>THE BOL QUERY OUTPUT: </strong></p>
<p><a href="http://datachix.files.wordpress.com/2010/10/pivotoutput1.jpg"><img class="alignnone size-full wp-image-373" src="http://datachix.files.wordpress.com/2010/10/pivotoutput1.jpg" alt="" width="310" height="234" /></a></p>
<p><strong>THE BREAKDOWN: </strong></p>
<p><strong>1) FROM (Source Query):</strong> This is the derived table that lives in the FROM clause. It produces the data that is going to be aggregated and pivoted. Write this first. Get familiar with what data you&#8217;re working with. Don&#8217;t forget to give it an alias. I like the ever-creative &#8220;as SourceQuery&#8221; to help me remember what that derived table&#8217;s doing there in the first place.</p>
<p>FROM</p>
<p>(&lt;SELECT query that produces the data&gt;)</p>
<p>AS &lt;alias for the source query&gt;</p>
<p>In the BOL example, this is the Source Query:</p>
<p><strong>FROM (</strong><br />
<strong>SELECT PurchaseOrderID, EmployeeID, VendorID</strong><br />
<strong>FROM Purchasing.PurchaseOrderHeader) as p</strong></p>
<p>It returns this:<br />
<a href="http://datachix.files.wordpress.com/2010/10/sourcequeryresults.jpg"><img class="alignnone size-full wp-image-374" src="http://datachix.files.wordpress.com/2010/10/sourcequeryresults.jpg" alt="" width="274" height="231" /></a></p>
<p>This is our raw data. By the time we get to the bottom of this blog post, we&#8217;re going to COUNT PurchaseOrderID&#8217;s by EmployeeID, set some EmployeeID&#8217;s as column headers, and return what looks like a cross-tab report with VendorID&#8217;s as row headers, EmployeeID&#8217;s as column headers, and PurchaseOrder COUNT as detail data. Really. I promise.</p>
<p><strong>2) PIVOT (Aggregation/Summarization):</strong> This is where you&#8217;re saying how to aggregate, or summarize what will end up in the cells. Think of it this way: If this were a spreadsheet, with column headers and row headers, the data produced by the PIVOT clause is the detail data living in the cells. Now, you don&#8217;t always want to aggregate. Sometimes you don&#8217;t have anything to aggregate, you just want to flip your data from rows to columns. Too bad. You&#8217;re aggregating something. The solution I&#8217;ve seen is to do a MIN or MAX, but to make sure that the MIN or MAX is of a unique thing. You&#8217;ll have to examine your data to see what works for you. But back to PIVOT&#8230;</p>
<p>PIVOT<br />
(<br />
&lt;aggregation function&gt;(&lt;column being aggregated&gt;)</p>
<p>In the BOL example, it looks like this:</p>
<p><strong>PIVOT</strong><br />
<strong>(</strong><br />
<strong>COUNT (PurchaseOrderID)</strong></p>
<p>So, what it&#8217;s saying is that the &#8220;detail&#8221; data (think like you&#8217;re in Excel for a moment) should be the count of PurchaseOrderID&#8217;s. Simple enough. But where&#8217;s my GROUP BY? It feels like heresy, aggregating something without a GROUP BY. Hang in there&#8230;</p>
<p><strong>3) FOR (Sort-of GROUP BY):</strong> FOR establishes what will be column headers for the PIVOT-ed (aggregated) data. One cool thing about it not being a true GROUP BY is that I don&#8217;t have to include everything from my Source Query (FROM). If you look at the BOL example, VendorID from my Source Query (FROM) isn&#8217;t included in the PIVOT or FOR clauses. It&#8217;s a pass-through column. It&#8217;s going to be there in the SELECT, and therefore in the output, but it isn&#8217;t part of the PIVOT process. In fact, you don&#8217;t have to include VendorID at all. The data probably wouldn&#8217;t make sense, but to each his own, right?</p>
<p>FOR</p>
<p>[&lt;column that contains the values that will become column headers&gt;]</p>
<p>IN ( [first pivoted column], [second pivoted column],</p>
<p>&#8230; [last pivoted column])</p>
<p>) AS &lt;alias for the pivot table&gt;</p>
<p>In the BOL example, the query developer chooses to return the number of purchase orders for a specific set of Employees. Yes, in the example it&#8217;s arbitrary, because they return 5 and there are actually 12 distinct EmployeeID&#8217;s in the Purchasing.PurchaseOrderHeader table, but I&#8217;m not here to judge. How do they do this? Like this:</p>
<p><strong>FOR EmployeeID IN</strong><br />
<strong>( [250], [251], [256], [257], [260] )</strong><br />
<strong>) AS pvt</strong></p>
<p>This is telling the PIVOT to produce 5 columns, [250], [251], [256], [257], and [260]. (You don&#8217;t have to have the brackets, except that &#8220;250&#8243; wouldn&#8217;t be a valid column name without them.) Those numbers are the actual EmployeeID&#8217;s returned from the Source Query. You&#8217;re saying &#8220;FOR&#8221; an EmployeeID &#8220;IN&#8221; a specific set of values that were returned in the Source Query (FROM). You&#8217;re essentially establishing a GROUP BY on EmployeeID. What&#8217;s being &#8220;grouped&#8221; by the FOR clause? The data that you&#8217;re aggregating in the PIVOT clause. Cool, huh? The COUNT of PurchaseOrderID&#8217;s will be placed underneath the column corresponding to the EmployeeID it belongs to. Don&#8217;t forget to alias the FOR clause. Something like &#8220;IRockBecauseIFiguredThisOut&#8221; works well. <img src='http://key2consulting.com/Blogs/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  Also, this is where you&#8217;re going to close the parenthesis that you opened up in the FROM clause.</p>
<p>Personal Note: This clause is one of the reasons I hate this BOL example. It doesn&#8217;t make sense that I would hard-code EmployeeID&#8217;s. A PIVOT example with months or years or something would be a more likely real-world scenario. Making it an example implies that it&#8217;s a good idea, and that every person reading BOL knows not to assume that Employee 257 will be a lifer at Adventure Works. But like I said, I don&#8217;t judge.</p>
<p><strong>4) SELECT (Presentation):</strong> Why is it that SELECT is always the simplest part of a query? It seems so important, but it really doesn&#8217;t do much. It&#8217;s like the presentation layer of the query. Here, you&#8217;re telling the query what to output. As long as it was part of the Source Query (FROM), or defined as a column header in the FOR clause, you can include it in the SELECT clause. In fact, if you&#8217;re feeling frisky, you can leave off columns. The query doesn&#8217;t care, because the SELECT is just there to make things pretty.</p>
<p>SELECT &lt;non-pivoted column&gt;,</p>
<p>[first pivoted column] AS &lt;column name&gt;,</p>
<p>[second pivoted column] AS &lt;column name&gt;,</p>
<p>&#8230;</p>
<p>[last pivoted column] AS &lt;column name&gt;</p>
<p>In the BOL example, it looks like this:</p>
<p><strong>SELECT VendorID, [250] AS Emp1, [251] AS Emp2, [256] AS Emp3, [257] AS Emp4, [260] AS Emp5</strong></p>
<p>VendorID is a pass-through (non-pivoted) column. It&#8217;s there to supplement the PIVOTed data. The other columns are the ones we established in the FOR clause. Just remember that everything you want to work with needs to be included in that Source Query (FROM clause).</p>
<p>Putting it all together, it looks like this:</p>
<p><strong>SELECT VendorID, [250] AS Emp1, [251] AS Emp2, [256] AS Emp3, [257] AS Emp4, [260] AS Emp5</strong><br />
<strong>FROM </strong><br />
<strong>(SELECT PurchaseOrderID, EmployeeID, VendorID</strong><br />
<strong>FROM Purchasing.PurchaseOrderHeader) p</strong><br />
<strong>PIVOT</strong><br />
<strong>(</strong><br />
<strong>COUNT (PurchaseOrderID)</strong><br />
<strong>FOR EmployeeID IN</strong><br />
<strong>( [250], [251], [256], [257], [260] )</strong><br />
<strong>) AS pvt</strong><br />
<strong>ORDER BY pvt.VendorID;</strong></p>
<p>The output looks like this:</p>
<p><a href="http://datachix.files.wordpress.com/2010/10/pivotoutput2.jpg"><img class="alignnone size-full wp-image-375" src="http://datachix.files.wordpress.com/2010/10/pivotoutput2.jpg" alt="" width="310" height="234" /></a></p>
<p>So there you have it. A peek into my thought process as I worked to overcome my fear of PIVOT. I&#8217;m good now. I&#8217;ll still have to look up the syntax whenever I write it, but at least I won&#8217;t break out into a cold sweat next time. And next up for me&#8230; PIVOT with an unknown/dynamic number of output columns. Woo-hoo! Dynamic SQL!</p>
<p>Query on, my friends.</p>
<p>﻿﻿﻿﻿</p>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/ahammonds/2010/10/28/deconstructing-pivot/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Trusted Foreign Keys</title>
		<link>http://key2consulting.com/Blogs/ahammonds/2010/09/09/trusted-foreign-keys/</link>
		<comments>http://key2consulting.com/Blogs/ahammonds/2010/09/09/trusted-foreign-keys/#comments</comments>
		<pubDate>Thu, 09 Sep 2010 20:16:33 +0000</pubDate>
		<dc:creator>Audrey Hammonds</dc:creator>
				<category><![CDATA[Database Development]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[Foreign Key]]></category>
		<category><![CDATA[SQLServer]]></category>

		<guid isPermaLink="false">http://6.5</guid>
		<description><![CDATA[Hey friends!  Long time, no see.  I know, you’re wondering where I’ve been.  Lately, I’ve had the luxury of taking some time to work on Microsoft certifications.  I just got through 70-448, Microsoft SQL Server 2008, Business Intelligence Development and Maintenance.  Now, I’m working on 70-433, Microsoft SQL Server 2008, Database Development.  I’ll admit… I [...]]]></description>
			<content:encoded><![CDATA[<p>Hey friends!  Long time, no see.  I know, you’re wondering where I’ve been.  Lately, I’ve had the luxury of taking some time to work on Microsoft certifications.  I just got through <a href="http://www.microsoft.com/learning/en/us/exam.aspx?ID=70-448&amp;locale=en-us">70-448, Microsoft SQL Server 2008, Business Intelligence Development and Maintenance</a>.  Now, I’m working on <a href="http://www.microsoft.com/learning/en/us/exam.aspx?ID=70-433&amp;locale=en-us">70-433, Microsoft SQL Server 2008, Database Development</a>.  I’ll admit… I thought the Database Development exam would be a no-brainer.  Heck, I’ve been doing this for years.  Much to my chagrin, I’ve learned a few things I should have already known.  (Isn’t that how it always ends up?) </p>
<p>Anyway, I was working my way through the <a href="http://www.microsoft.com/learning/en/us/Book.aspx?ID=13159&amp;locale=en-us">Self-Paced Training Kit</a>, and stumbled across one thing that I hadn’t known before and am so excited about that I want to share it.  Here we go…</p>
<p>First, because good bloggers give credit where credit is due, major props to the team that wrote the training kit for exam 70-433.  They include:  Tobias Thernstrom, Ann Weber, Mike Hotek, and GrandMasters.  It’s a well-written book, and they obviously snuck some things in there that were more about good design and development and less about answering test questions.  Kudos, gentlemen and lady!  I&#8217;m basically re-writing something that you&#8217;ve already covered in your book, but I think it&#8217;s really, really cool and want everyone to see it regardless of whether they&#8217;ve bought the book or not.  Given that, buy the book, dear readers.  Even if you&#8217;re not studying for the exam.  I promise you&#8217;ll learn something new.</p>
<p>Okay, to the point… Do me a favor, get on your local database and run this query.  Go ahead, I’ll wait for you. </p>
<p>SELECT name, is_not_trusted<br />
FROM sys.foreign_keys; </p>
<p>Good to have you back.  I missed you.  I stared pensively into the horizon while I awaited your return.  I even wrote a poem and a folk song.  Sorry, I digress&#8230; one too many rom-coms lately.  Did you see any “1” values in the is_not_trusted column?  Did you even know that foreign keys could be trustworthy?  Nope, neither did I.  What does it mean?  It means that your foreign key hasn’t been verified by the system.  How does this happen?  Well, remember that optional clause called WITH CHECK | NOCHECK when you create a foreign key constraint?  Yup.  That did it. </p>
<p>So why does this matter? Well, it actually has an effect on your query execution plan in some cases.  You know, that Query Optimizer is pretty darn smart.  Let’s look at an example from the trusty old AdventureWorks database.  I’m using AdventureWorks2008R2, but it should work with the older AdventureWorks databases.  The scenario is this:  <strong>I want to know if I have any sales orders that have invalid customers</strong>.  CustomerID is a NOT NULL column in Sales.SalesOrderHeader.  I know that if I count all the rows in Sales.SalesOrderHeader and the number of rows returned in the query we&#8217;re about to run, I should get the same number of rows back each time.  But, sometimes rogue values slip in, especially when the database design has been refined over time.  This could happen for any number of reasons:  constraints that were added after the fact, legacy data, disabled constraints, etc. </p>
<p>Run this query, but turn on Include Actual Execution Plan (Ctrl+M) before you do. </p>
<p>SELECT soh.*<br />
FROM Sales.SalesOrderHeader soh<br />
WHERE EXISTS (SELECT * FROM Sales.Customer c WHERE soh.CustomerID = c.CustomerID);</p>
<p>Note that I’m using WHERE EXISTS rather than an IN clause with a subquery.  This is because in this business scenario, I just want a boolean result (true or false), and I want it to run FAST. </p>
<p>Check out the execution plan.  Note that the Customer table was never accessed.  Why?  Because that foreign key is trusted!  Since it was verified on creation, we know that no rogue CustomerID’s snuck into the Sales.SalesOrderHeader table.  It doesn’t even need to look at it.  <br />
 <a href="http://key2consulting.com/Blogs/ahammonds/files/2010/09/TrustedExecPlan1.jpg"><img class="alignnone size-large wp-image-10" src="http://key2consulting.com/Blogs/ahammonds/files/2010/09/TrustedExecPlan1-1024x118.jpg" alt="" width="600" height="69" /></a></p>
<p>Now, let’s muck with the foreign key and make it un-trusted.  We’re disabling the foreign key with this statement.  <a href="http://key2consulting.com//ms.sqlcc.v10/MS.SQLSVR.v10.en/s10de_1devconc/html/2198f1af-fa44-47e9-92df-f4fde322ba18.htm">Books Online </a>has a good article about what this means. </p>
<p>ALTER TABLE Sales.SalesOrderHeader<br />
 NOCHECK CONSTRAINT FK_SalesOrderHeader_Customer_CustomerID;</p>
<p>Verify that the foreign key is disabled by checking sys.foreign_keys again: </p>
<p>SELECT name, is_disabled, is_not_trusted<br />
FROM sys.foreign_keys<br />
WHERE name = &#8216;FK_SalesOrderHeader_Customer_CustomerID&#8217;;</p>
<p>If we run our query again, we see a completely different execution plan.  Now, the query optimizer has to go look at the Sales.Customer table to get us an answer: </p>
<p>SELECT soh.*<br />
FROM Sales.SalesOrderHeader soh<br />
WHERE EXISTS (SELECT * FROM Sales.Customer c WHERE soh.CustomerID = c.CustomerID);</p>
<p><a href="http://key2consulting.com/Blogs/ahammonds/files/2010/09/UntrustedExecPlan.jpg"><img class="alignnone size-large wp-image-11" src="http://key2consulting.com/Blogs/ahammonds/files/2010/09/UntrustedExecPlan-1024x204.jpg" alt="" width="600" height="119" /></a><br />
 <br />
The execution plan had to change because SQL Server cannot guarantee that a CustomerID wasn&#8217;t entered while the foreign key constraint was disabled. </p>
<p>Here’s where it gets interesting.  Enable the foreign key by executing the following: </p>
<p>ALTER TABLE Sales.SalesOrderHeader<br />
 CHECK CONSTRAINT FK_SalesOrderHeader_Customer_CustomerID;</p>
<p>Check out your sys.foreign_keys table again. </p>
<p>SELECT name, is_disabled, is_not_trusted<br />
FROM sys.foreign_keys<br />
WHERE name = &#8216;FK_SalesOrderHeader_Customer_CustomerID&#8217;;</p>
<p>What?  It’s enabled, but it’s still not trusted!  If we execute our query, we’re still going to get the execution plan that looks at Sales.Customer.  Why?  Well, that CHECK keyword up there just said to enable the foreign key, it didn’t say verify it.  We have to issue this statement (Of course, if an invalid CustomerID snuck in while your FK was disabled, this ALTER is going to fail): </p>
<p>ALTER TABLE Sales.SalesOrderHeader<br />
 WITH CHECK &#8212;&gt; this clause will make your FK trustworthy again<br />
 CHECK CONSTRAINT FK_SalesOrderHeader_Customer_CustomerID;  </p>
<p>If we run our query now, we’ll get the sleeker, more efficient plan, because now our foreign key is enabled and trusted. </p>
<p>SELECT soh.*<br />
FROM Sales.SalesOrderHeader soh<br />
WHERE EXISTS (SELECT * FROM Sales.Customer c WHERE soh.CustomerID = c.CustomerID);</p>
<p> <a href="http://key2consulting.com/Blogs/ahammonds/files/2010/09/TrustedExecPlan2.jpg"><img class="alignnone size-large wp-image-12" src="http://key2consulting.com/Blogs/ahammonds/files/2010/09/TrustedExecPlan2-1024x118.jpg" alt="" width="600" height="69" /></a><br />
Cool, huh?  The point is, this one teensy-tiny flag in the foreign key metadata makes a huge difference in how the query optimizer handles your query.  It might not be much, but why not make sure that you can get as many trusted foreign keys as possible?  You might just end up looking like a rock star for improving performance without having to modify any actual queries. </p>
<p>Now if I could just figure out put to get a is_not_trusted flag on people&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://key2consulting.com/Blogs/ahammonds/2010/09/09/trusted-foreign-keys/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

