<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>CrossTabs / Pivoting Data</title>
        <link>http://weblogs.sqlteam.com/jeffs/category/156.aspx</link>
        <description>Everything you always wanted to know about crosstabbing data (summarizing multiple rows into multiple columns) but were afraid to ask!

The key to success is to remember:  This is a presentation issue, not a database issue.  Even though I show a few ways to get this done using T-SQL, that doesn't mean that you should it in general; most times, it is much more efficient and much easier to crosstab your data at the presentation layer.
</description>
        <language>en-US</language>
        <copyright>Jeff Smith</copyright>
        <managingEditor>smith_jeffreyt@yahoo.com</managingEditor>
        <generator>Subtext Version 1.9.4.0</generator>
        <item>
            <title>UNPIVOT: Normalizing data on the fly</title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2008/04/23/unpivot.aspx</link>
            <description>Everyone seems to want to &lt;a href="http://weblogs.sqlteam.com/jeffs/category/156.aspx"&gt;"pivot" or "cross tab"&lt;/a&gt; data, but knowing how to do the opposite is equally important.  In fact, I would argue that the skill of "unpivoting" data is more useful and more important and more relevant to a SQL programmer, since pivoting results in denormalized data, while unpivoting can transform non-normalized data into a normalized result set.  We all know that there's lots of bad databases designs out there, so this can be a handy technique to know.  &lt;br /&gt;
&lt;br /&gt;
Of course, even a well designed, fully normalized database can still benefit from "unpivoting" from time to time, so let's take a look at some common situations and some of the options we have to handle this at our disposal.  We will focus on some traditional SQL techniques to do this, and then take a close look at the &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;operator that was introduced with SQL Server 2005.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;Example #1:  A Bad database design&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Let's start with a commonly bad table design, in which someone has decided to relate a client to multiple contacts by designing their client table like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="text-align: left; margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;create table Clients &lt;br /&gt;
(   &lt;br /&gt;
    clientID int primary key, &lt;br /&gt;
    clientName varchar(100), &lt;br /&gt;
    contact1 int, &lt;br /&gt;
    contact2 int, &lt;br /&gt;
    contact3 int, &lt;br /&gt;
    contact4 int&lt;br /&gt;
)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;insert into Clients&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 1,'ABC Corp',1,34,2,null union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 2,'DEF Foundation',6,2,8,9 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 3,'GHI Inc.',5,9,null,null union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 4,'XYZ Industries',24,null,6,null&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;clientID    clientName           contact1    contact2    contact3    contact4&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------- -------------------- ----------- ----------- ----------- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           ABC Corp             1           34          2           NULL&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           DEF Foundation       6           2           8           9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           GHI Inc.             5           9           NULL        NULL&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           XYZ Industries       24          NULL        6           NULL&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(4 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style="font-style: italic;"&gt;(Note: For brevity, I am not including the contact table here, nor the foreign key constraints.  Of course, with this table design, it would probably be pretty unlikely to find such constraints in the database anyway)&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br /&gt;
With this design, it is not very easy or efficient to get a count of all contacts for each client, or to find out which contacts are related to which clients.   One thing we can do, however is to "unpivot" this table in a query that returns 1 row per ClientID/ContactID combination.  With that result set, we can easily now reference the table as if it were normalized and we can get the information we need.&lt;br /&gt;
&lt;br /&gt;
One way to do is to use UNION ALL to return each row in the clients table 4 times, and each time return a different contactID column:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select clientID, contact1 as ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from clients&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;where contact1 is not null&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select clientID, contact2 as ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from clients&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;where contact2 is not null&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select clientID, contact3 as ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from clients&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;where contact3 is not null&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select clientID, contact4 as ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from clients&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;where contact4 is not null&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;clientID    ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           5&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           24&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           34&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           8&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(11 row(s) affected)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;/div&gt;
Another option is to CROSS JOIN the Clients table with a table or resultset that returns 4 rows, which also effectively returns each row in the clients table 4 times.  For each of the 4 values in the table we are cross joining, we grab a different contact column:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select *&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    select c.clientID,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;       case n.n when 1 then c.contact1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;            when 2 then c.contact2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;            when 3 then c.contact3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;            when 4 then c.contact4 end as ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    from&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;        clients c&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    cross join&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;        (select 1 as n union all select 2 union all select 3 union all select 4) n&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;)    &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    x&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;where&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    x.ContactID is not null&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;clientID    ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           34&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           8&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           5&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           24&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(11 row(s) affected)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New; font-style: italic;" /&gt;
&lt;/div&gt;
&lt;span style="font-style: italic;"&gt;(Note that you can use a permanent table of Numbers in your database instead of generating it on the fly with a UNION, as shown)&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Finally, however, there is an even eaiser way to handle this: the &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;operator, new with SQL 2005.  &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;works very efficiently and really allows you to handle this exact situation quite easily:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select clientID, Contact.ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from clients&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (ContactID for ContactNumber in (contact1, contact2,contact3,contact4)) as Contact&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;clientID    ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           34&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           8&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           5&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           24&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(11 row(s) affected)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;/div&gt;
&lt;br /&gt;
Much shorter to write, and more efficient to execute as well.  &lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;Taking a Closer Look at UNPIVOT&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The UNPIVOT operator is tricky to get a feel for, however, so let's take a look at it.&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;unpivot (ContactID for ContactNumber in (contact1, contact2,contact3,contact4)) &lt;span style="font-weight: bold; background-color: rgb(255, 255, 153);"&gt;as Contact&lt;/span&gt;&lt;/span&gt;&lt;br style="background-color: rgb(255, 255, 153);" /&gt;
&lt;/div&gt;
&lt;br style="background-color: rgb(255, 255, 153);" /&gt;
&lt;span style="background-color: rgb(255, 255, 153);"&gt; &lt;/span&gt;First, the "As Contact" at the end is just labeling the entire unpivot result set with an alias, just as you must alias a derived table.  Each column returned by the pivot operator can be referenced by the alias if necessary.&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;unpivot (&lt;span style="background-color: rgb(255, 255, 153);"&gt;ContactID&lt;/span&gt; for&lt;span style="font-weight: bold; font-family: Courier New;"&gt; &lt;/span&gt;ContactNumber &lt;span style="font-weight: bold; background-color: rgb(255, 255, 153);"&gt;in (contact1, contact2,contact3,contact4)&lt;/span&gt;) as Contact&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
The "ContactID for" part says that we want to return a column called "ContactID" for each unpivoted row.  The IN() list is the columns that we are unpivoting; the values in the 4 columns listed here will be assigned to the ContactID column in the result.  So, the first time a particular row is unpivoted, the value of the 'contact1' column is assigned to ContactID, the next time it is the 'contact2' column, then 'contact3', and then finally 'contact4'.  Then, the next row is processed and it all begins again.  &lt;br /&gt;
&lt;br /&gt;
Thus, because we are unpivoting 4 values, we know that the result of the unpivot will have 4 times as many rows as the source data.&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;unpivot (ContactID &lt;span style="font-weight: bold; background-color: rgb(255, 255, 153);"&gt;for ContactNumber&lt;/span&gt; in (contact1, contact2,contact3,contact4)) as Contact&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
UNPIVOT returns an additional column as well, which contains the name of the column that was used to produce each unpivoted row.   Here, we have specified that to be called ContactNumber.  Note that we actually did not return ContactNumber in our example, be we can easily add that in so you can see how it works:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select clientID,&lt;span style="font-weight: bold;"&gt; Contact.ContactNumber,&lt;/span&gt; Contact.ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from clients&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (ContactID for ContactNumber in (contact1, contact2,contact3,contact4)) as Contact&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;clientID    ContactNumber           ContactID&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------- ----------------------- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           contact1                1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           contact2                34&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           contact3                2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           contact1                6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           contact2                2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           contact3                8&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2           contact4                9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           contact1                5&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;3           contact2                9&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           contact1                24&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;4           contact3                6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(11 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
So, you can see that the code to write is very short, but a little difficult to grasp at first.  In the end, though, we are able to take a bad table design and easily "fix it", at least temporarily, so that we can query it using simple and standard SQL statements to get what we need.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;Example #2:  Normalizing a Transaction Table&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Here's another common example:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;create table Transactions&lt;br /&gt;
(&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    TranDate datetime,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Account varchar(10),&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    BudgetAmount money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    ActualAmount money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    ProjectionAmount money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    primary key (TranDate, Account)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;go&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;insert into Transactions&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-01-01','0001',354,65,58 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-01-02','0001',14,65,34 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-01-03','0001',0,65,622 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-01-04','0001',9,32,84&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;go&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;TranDate                Account    BudgetAmount          ActualAmount          ProjectionAmount&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- ---------- --------------------- --------------------- ---------------------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-01 00:00:00.000 0001       354.00                65.00                 58.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-02 00:00:00.000 0001       14.00                 65.00                 34.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-03 00:00:00.000 0001       0.00                  65.00                 622.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-04 00:00:00.000 0001       9.00                  32.00                 84.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(4 row(s) affected)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
Notice that we have different columns for Budget, Actual and Projection, which is not really a great database design.  Much better would be to break this data out so that we have a single 'Amount' column and a 'TransactionType' column that specifies the type of each transaction.  We can transform our Transactions tables into this format using UNION ALL:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select TranDate, Account, 'BudgetAmount' as Type, BudgetAmount as Amount from transactions&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select TranDate, Account, 'ActualAmount' as Type, ActualAmount as Amount from transactions&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select TranDate, Account, 'ProjectionAmount' as Type, ProjectionAmount as Amount from transactions&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;TranDate                Account    Type             Amount&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- ---------- ---------------- ---------------------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-01 00:00:00.000 0001       BudgetAmount     354.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-02 00:00:00.000 0001       BudgetAmount     14.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-03 00:00:00.000 0001       BudgetAmount     0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-04 00:00:00.000 0001       BudgetAmount     9.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-01 00:00:00.000 0001       ActualAmount     65.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-02 00:00:00.000 0001       ActualAmount     65.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-03 00:00:00.000 0001       ActualAmount     65.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-04 00:00:00.000 0001       ActualAmount     32.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-01 00:00:00.000 0001       ProjectionAmount 58.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-02 00:00:00.000 0001       ProjectionAmount 34.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-03 00:00:00.000 0001       ProjectionAmount 622.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-04 00:00:00.000 0001       ProjectionAmount 84.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(12 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
Or, we can use the &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;operator to do the same much easier:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select TranDate, Account, Type, Amount&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from Transactions&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Amount for Type in (BudgetAmount, ActualAmount, ProjectionAmount)) as Amount&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;/span&gt;&lt;span style="font-family: Courier New;"&gt;TranDate                Account   Type                     Amount&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- --------- ------------------------ ---------------------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-01 00:00:00.000 0001      BudgetAmount             354.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-01 00:00:00.000 0001      ActualAmount             65.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-01 00:00:00.000 0001      ProjectionAmount         58.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-02 00:00:00.000 0001      BudgetAmount             14.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-02 00:00:00.000 0001      ActualAmount             65.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-02 00:00:00.000 0001      ProjectionAmount         34.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-03 00:00:00.000 0001      BudgetAmount             0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-03 00:00:00.000 0001      ActualAmount             65.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-03 00:00:00.000 0001      ProjectionAmount         622.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-04 00:00:00.000 0001      BudgetAmount             9.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-04 00:00:00.000 0001      ActualAmount             32.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-01-04 00:00:00.000 0001      ProjectionAmount         84.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(12 row(s) affected)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;Example #3: "Unsummarizing" Data&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
For those who work with accounting systems, this example may be familiar to you.   Many times, Accounting systems have "summary" tables that roll up transactional data into a structure like this:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;create table AccountBalances&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    CompanyID int,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    AccountID int,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    TransactionTypeID int,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Year int,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period1 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period2 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period3 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period4 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period5 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period6 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period7 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period8 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period9 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period10 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period11 money,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Period12 money&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;insert into AccountBalances&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 1,1,1,2008,200,300,400,500,400,0,0,0,0,0,0,0 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 1,2,1,2008,100,100,100,100,100,100,100,0,0,0,0,0 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 1,3,1,2008,150,0,50,10,10,200,400,45,0,0,0,0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style="font-style: italic;"&gt;(As before, let's not worry about those foreign key constraints)&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
These tables are often calculated when transactions are posted, or periods are closed.   Typically, many reports pull from these tables because it is much more efficient than summarizing thousands or millions of transactions, and the data is already "cross-tabbed" the way most reporting tools would like to display it.&lt;br /&gt;
&lt;br /&gt;
We can take this summarized data and "unpivot" it so we can still access the summarized data, but now it will be in a normalized structure.  All it takes is a simple &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;like this:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select CompanyID, AccountID, TransactionTypeID, Year, substring(Period,7,2) as PeriodNo, Amount&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from AccountBalances&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Amount for Period in (Period1,Period2,Period3,Period4,Period5,Period6,Period7,Period8,Period9,Period10,Period11,Period12)) as Amount&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;CompanyID   AccountID   TransactionTypeID Year        PeriodNo Amount&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------- ----------- ----------------- ----------- -------- ---------------------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        1        200.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        2        300.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        3        400.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        4        500.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        5        400.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        6        0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        7        0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        8        0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        9        0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        10       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        11       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           1           1                 2008        12       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        1        100.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        2        100.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        3        100.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        4        100.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        5        100.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        6        100.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        7        100.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        8        0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        9        0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        10       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        11       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           2           1                 2008        12       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        1        150.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        2        0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        3        50.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        4        10.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        5        10.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        6        200.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        7        400.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        8        45.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        9        0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        10       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        11       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;1           3           1                 2008        12       0.00&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(36 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
We can filter so that only periods with a non-zero amount are included, and we can SELECT FROM this result set and get the exact data we need for whatever date range we want without worrying which column the actual data is in.&lt;br /&gt;
&lt;br /&gt;
This can also be done with a CROSS JOIN or a UNION ALL, but with 12 values to pivot, those options become much longer to write and &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;appears to be the way to go in this case.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;Example #4: Multiple unpivots&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Finally, let's consider a more complicated example.  Here, we have a table that stores games played between two teams, where one team is the &lt;span style="font-style: italic;"&gt;HomeTeam &lt;/span&gt;and the other is the &lt;span style="font-style: italic;"&gt;AwayTeam:&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;create table Teams&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    TeamCode char(3) primary key not null,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    TeamName varchar(100) not null&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;create table Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    GameDate datetime,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    HomeTeam char(3) references Teams(TeamCode),&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    AwayTeam char(3) references Teams(TeamCode),&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    HomeScore int,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    AwayScore int,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    primary key (GameDate, HomeTeam), &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    constraint pk2 unique (GameDate, AwayTeam),&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    check (HomeTeam &amp;lt;&amp;gt; AwayTeam)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;insert into Teams&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 'BOS','Boston Red Sox' union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select 'NYY','New York Yankees'&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;insert into Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-04-01','BOS','NYY',3,1 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-04-02','BOS','NYY',6,4 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-04-03','BOS','NYY',2,3 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-04-08','NYY','BOS',6,0 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-04-09','NYY','BOS',2,6 union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select '2008-04-10','NYY','BOS',1,10&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;GameDate                HomeTeam AwayTeam HomeScore   AwayScore&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- -------- -------- ----------- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 BOS      NYY      3           1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 BOS      NYY      6           4&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 BOS      NYY      2           3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 NYY      BOS      6           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 NYY      BOS      2           6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 NYY      BOS      1           10&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(6 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
That may or may not be the best possible design for the Games table, but it is a common way to do it.  With data in that form, if we want to get the total runs scored per team across all games, or get each teams won-loss record, we need to make two passes through the table. This can be done fairly easily and efficiently with a union:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select GameDate, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    HomeTeam as TeamCode, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    'Home' as HomeOrAway, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    HomeScore as Score, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    case when HomeScore &amp;gt; AwayScore then 1 else 0 end as Win,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    case when HomeScore &amp;lt; AwayScore then 1 else 0 end as Loss,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    case when HomeScore = AwayScore then 1 else 0 end as Tie&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;union all&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;select GameDate, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    AwayTeam as TeamCode, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    'Away' as HomeOrAway, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    AwayScore as Score, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    case when HomeScore &amp;lt; AwayScore then 1 else 0 end as Win,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    case when HomeScore &amp;gt; AwayScore then 1 else 0 end as Loss,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    case when HomeScore = AwayScore then 1 else 0 end as Tie&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;GameDate                TeamCode HomeOrAway Score       Win         Loss        Tie&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- -------- ---------- ----------- ----------- ----------- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 BOS      Home       3           1           0           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 BOS      Home       6           1           0           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 BOS      Home       2           0           1           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 NYY      Home       6           1           0           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 NYY      Home       2           0           1           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 NYY      Home       1           0           1           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 NYY      Away       1           0           1           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 NYY      Away       4           0           1           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 NYY      Away       3           1           0           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 BOS      Away       0           0           1           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 BOS      Away       6           1           0           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 BOS      Away       10          1           0           0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(12 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
Now, how can we do this with &lt;span style="font-weight: bold;"&gt;UNPIVOT&lt;/span&gt;?  This example is a bit more complicated, because we are "unpivoting" not only the score, but also the TeamCode, and we are also calculating a few extra columns (Win, Loss and Tie).&lt;br /&gt;
&lt;br /&gt;
So, can we &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;more than 1 column?  Let's start simply and do things one at a time.  First, let's &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;just the TeamCode:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select GameDate, HomeOrAway, Team&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Team for HomeOrAway in (HomeTeam, AwayTeam)) as Team&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;GameDate                HomeOrAway     Team&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- -------------- ----&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 HomeTeam       BOS&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 AwayTeam       NYY&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 HomeTeam       BOS&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 AwayTeam       NYY&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 HomeTeam       BOS&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 AwayTeam       NYY&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 HomeTeam       NYY&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 AwayTeam       BOS&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 HomeTeam       NYY&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 AwayTeam       BOS&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 HomeTeam       NYY&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 AwayTeam       BOS&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(12 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
OK, so far so good.  Now, how do we get the score for the team as well?  Let's add another &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;clause to the SELECT, this time for Score:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select GameDate, HomeOrAway, Team, Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Team for HomeOrAway in (HomeTeam, AwayTeam)) as Team&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;unpivot (Score for HomeOrAway in (HomeScore, AwayScore)) as Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New; color: rgb(128, 0, 0);"&gt;Msg 265, Level 16, State 1, Line 1&lt;/span&gt;&lt;br style="font-family: Courier New; color: rgb(128, 0, 0);" /&gt;
&lt;span style="font-family: Courier New; color: rgb(128, 0, 0);"&gt;The column name "HomeOrAway" specified in the UNPIVOT operator conflicts with the existing column name in the UNPIVOT argument.&lt;/span&gt;&lt;br style="font-family: Courier New; color: rgb(128, 0, 0);" /&gt;
&lt;span style="font-family: Courier New; color: rgb(128, 0, 0);"&gt;Msg 8156, Level 16, State 1, Line 1&lt;/span&gt;&lt;br style="font-family: Courier New; color: rgb(128, 0, 0);" /&gt;
&lt;span style="font-family: Courier New; color: rgb(128, 0, 0);"&gt;The column 'HomeOrAway' was specified multiple times for 'Score'.&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
Hmmm. OK, we cannot specify the same column for both pivots.  That is just an alias specification, no big deal, so let's just alias it as "HomeOrAway2" :&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select GameDate, HomeOrAway, Team, Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Team for HomeOrAway in (HomeTeam, AwayTeam)) as Team&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Score for &lt;span style="font-weight: bold;"&gt;HomeOrAway2 &lt;/span&gt;in (HomeScore, AwayScore)) as Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;GameDate                HomeOrAway Team Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- ---------- ---- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 HomeTeam   BOS  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 HomeTeam   BOS  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 AwayTeam   NYY  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 AwayTeam   NYY  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 HomeTeam   BOS  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 HomeTeam   BOS  4&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 AwayTeam   NYY  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 AwayTeam   NYY  4&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 HomeTeam   BOS  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 HomeTeam   BOS  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 AwayTeam   NYY  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 AwayTeam   NYY  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 HomeTeam   NYY  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 HomeTeam   NYY  0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 AwayTeam   BOS  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 AwayTeam   BOS  0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 HomeTeam   NYY  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 HomeTeam   NYY  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 AwayTeam   BOS  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 AwayTeam   BOS  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 HomeTeam   NYY  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 HomeTeam   NYY  10&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 AwayTeam   BOS  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 AwayTeam   BOS  10&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(24 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
Holy schnikies, it works!  We can specify more than one &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;clause for the same SQL statement!  Who would have thunk it?&lt;br /&gt;
&lt;br /&gt;
Uh oh -- wait a second.  We have 24 results returned.  We should have only 12.  Something is not right here.  Remember when we aliased our second unpivot column as "HomeOrAway2"?  We did not return that anywhere in our results.  Let's add that in and take a look:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select GameDate, HomeOrAway, HomeOrAway2, Team, Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Team for HomeOrAway in (HomeTeam, AwayTeam)) as Team&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Score for HomeOrAway2 in (HomeScore, AwayScore)) as Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;GameDate                HomeOrAway HomeOrAway2 Team Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- ---------- ----------- ---- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 HomeTeam   HomeScore   BOS  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 HomeTeam   AwayScore   BOS  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 AwayTeam   HomeScore   NYY  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 AwayTeam   AwayScore   NYY  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 HomeTeam   HomeScore   BOS  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 HomeTeam   AwayScore   BOS  4&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 AwayTeam   HomeScore   NYY  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-02 00:00:00.000 AwayTeam   AwayScore   NYY  4&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 HomeTeam   HomeScore   BOS  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 HomeTeam   AwayScore   BOS  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 AwayTeam   HomeScore   NYY  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-03 00:00:00.000 AwayTeam   AwayScore   NYY  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 HomeTeam   HomeScore   NYY  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 HomeTeam   AwayScore   NYY  0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 AwayTeam   HomeScore   BOS  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-08 00:00:00.000 AwayTeam   AwayScore   BOS  0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 HomeTeam   HomeScore   NYY  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 HomeTeam   AwayScore   NYY  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 AwayTeam   HomeScore   BOS  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-09 00:00:00.000 AwayTeam   AwayScore   BOS  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 HomeTeam   HomeScore   NYY  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 HomeTeam   AwayScore   NYY  10&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 AwayTeam   HomeScore   BOS  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-10 00:00:00.000 AwayTeam   AwayScore   BOS  10&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;(24 row(s) affected)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
Notice what is happening here. Without an UNPIVOT, we had 6 rows.  We added one UNPIVOT to pivot two values, and got 12 rows.  Then, we added another pivot, again with two values, and got back 24.  Looking at the results, we can see that the single game at '2008-04-01' was unpivoted into 4 rows.  So, while we can unpivot multiple times, &lt;span style="font-style: italic;"&gt;there is no relation between each UNPIVOT.  &lt;/span&gt;That is, notice there is no correlation between HomeOrAway and HomeOrAway2.  Let's look at just that first game:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;GameDate                HomeOrAway HomeOrAway2 Team Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- ---------- ----------- ---- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 HomeTeam   HomeScore   BOS  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 HomeTeam   AwayScore   BOS  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 AwayTeam   HomeScore   NYY  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 AwayTeam   AwayScore   NYY  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;/div&gt;
&lt;br /&gt;
Both scores, home and away, are specified for each team -- even if it is not their matching score.  What we really want is only these two lines:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;GameDate                HomeOrAway HomeOrAway2 Team Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;----------------------- ---------- ----------- ---- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 HomeTeam   HomeScore   BOS  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New; text-decoration: line-through;"&gt;2008-04-01 00:00:00.000 HomeTeam   AwayScore   BOS  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New; text-decoration: line-through;"&gt;2008-04-01 00:00:00.000 AwayTeam   HomeScore   NYY  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;2008-04-01 00:00:00.000 AwayTeam   AwayScore   NYY  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;/div&gt;
How can we get there?  Well, there's always a WHERE clause!  We can simply filter our result so that the Left 4 characters of HomeOrAway matches the left 4 characters of HomeOrAway2.  After all, there is nothing magical about these unpivoted columns, they are data just like anything else.&lt;br /&gt;
&lt;br /&gt;
So, we can now write:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select GameDate, left(HomeOrAway,4) as HomeOrAway, Team, Score&lt;br /&gt;
from Games&lt;br /&gt;
unpivot (Team for HomeOrAway in (HomeTeam, AwayTeam)) as Team&lt;br /&gt;
unpivot (Score for HomeOrAway2 in (HomeScore, AwayScore)) as Score&lt;br /&gt;
where left(HomeOrAway,4) = left(HomeOrAway2,4)&lt;/span&gt;&lt;span style="font-family: Courier New;"&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;GameDate                HomeOrAway Team Score&lt;br /&gt;
----------------------- ---------- ---- -----------&lt;br /&gt;
2008-04-01 00:00:00.000 Home       BOS  3&lt;br /&gt;
2008-04-01 00:00:00.000 Away       NYY  1&lt;br /&gt;
2008-04-02 00:00:00.000 Home       BOS  6&lt;br /&gt;
2008-04-02 00:00:00.000 Away       NYY  4&lt;br /&gt;
2008-04-03 00:00:00.000 Home       BOS  2&lt;br /&gt;
2008-04-03 00:00:00.000 Away       NYY  3&lt;br /&gt;
2008-04-08 00:00:00.000 Home       NYY  6&lt;br /&gt;
2008-04-08 00:00:00.000 Away       BOS  0&lt;br /&gt;
2008-04-09 00:00:00.000 Home       NYY  2&lt;br /&gt;
2008-04-09 00:00:00.000 Away       BOS  6&lt;br /&gt;
2008-04-10 00:00:00.000 Home       NYY  1&lt;br /&gt;
2008-04-10 00:00:00.000 Away       BOS  10&lt;br /&gt;
&lt;br /&gt;
(12 row(s) affected)&lt;br /&gt;
&lt;/span&gt;&lt;span style="font-family: Courier New;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
Now we are back to 12 results returned, the Homes and Aways match up, and things look good!  Hey, we just pulled off 2 unpivots at once, not bad!  From here, we can add our Win, Loss and Tie calculations as well.  Note that we also have removed "HomeOrAway2" from the SELECT, since we don't need to see that column in our result.  Finally, we are returning only the left 4 characters of HomeTeam or AwayTeam, so that the word 'Home' or 'Away' is returned in the HomeOrAway column.&lt;br /&gt;
&lt;br /&gt;
This seems like quite a bit of work.  Is &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;really the way to go in this scenario?  Maybe not.  I have not done full performance testing of &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;versus the other options shown (this article is way too long as it is!) but as always you should fully test your options and decide which offers the best performance and code simplicity.&lt;br /&gt;
&lt;br /&gt;
Another option if you want to unpivot more than 1 column is to just use one &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;cause, but then use CASE expressions to return the values you need.  For example:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select GameDate, left(HomeOrAway,4) as HomeOrAway, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;    Team, &lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;    case when HomeOrAway='HomeTeam' then HomeScore else AwayScore end as Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from Games&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;unpivot (Team for HomeOrAway in (HomeTeam, AwayTeam)) as Team&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;GameDate                HomeOrAway Team Score&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; ----------------------- ---------- ---- -----------&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-01 00:00:00.000 Home       BOS  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-01 00:00:00.000 Away       NYY  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-02 00:00:00.000 Home       BOS  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-02 00:00:00.000 Away       NYY  4&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-03 00:00:00.000 Home       BOS  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-03 00:00:00.000 Away       NYY  3&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-08 00:00:00.000 Home       NYY  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-08 00:00:00.000 Away       BOS  0&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-09 00:00:00.000 Home       NYY  2&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-09 00:00:00.000 Away       BOS  6&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-10 00:00:00.000 Home       NYY  1&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; 2008-04-10 00:00:00.000 Away       BOS  10&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; (12 row(s) affected)&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;/div&gt;
&lt;br /&gt;
This is kind of a combination of using &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;and a CROSS JOIN; the &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;doubles-up the results returned just as a CROSS JOIN would, and we just use CASE to return the column that we need depending if the current row is a "HomeTeam" or an "AwayTeam" row.    This is a fairly good solution and is probably better than using multiple &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;clauses.  &lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;Summary&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;is a really handy operator, but it can be tricky to use.  You can &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;more than 1 column at a time, but beware of how it works -- each &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;uses it's own "pivot values" and you may get many more results than you intend. Thus, you may need to add criteria to your SELECT to ensure that only the results you are looking for are returned.  Alternatively, you can use one &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;along with CASE expressions to achieve the same results.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;is great, but the main thing is that you are at the mercy of the column names; the naming conventions of the columns used becomes data, and that can really complicate things.   In general, we want to keep our object names and our data seperate, so keep that limitation in mind if you wish to employ &lt;span style="font-weight: bold;"&gt;UNPIVOT &lt;/span&gt;in your code.   In some cases, an old-school UNION ALL or CROSS JOIN may be a preferrable solution.&lt;img src="http://weblogs.sqlteam.com/jeffs/aggbug/60573.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jeff Smith</dc:creator>
            <guid>http://weblogs.sqlteam.com/jeffs/archive/2008/04/23/unpivot.aspx</guid>
            <pubDate>Wed, 23 Apr 2008 14:33:15 GMT</pubDate>
            <wfw:comment>http://weblogs.sqlteam.com/jeffs/comments/60573.aspx</wfw:comment>
            <comments>http://weblogs.sqlteam.com/jeffs/archive/2008/04/23/unpivot.aspx#feedback</comments>
            <slash:comments>11</slash:comments>
            <wfw:commentRss>http://weblogs.sqlteam.com/jeffs/comments/commentRss/60573.aspx</wfw:commentRss>
            <trackback:ping>http://weblogs.sqlteam.com/jeffs/services/trackbacks/60573.aspx</trackback:ping>
        </item>
        <item>
            <title>Rewriting correlated sub-queries with CASE expressions</title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2008/01/09/rewrite-correlated-sub-query-with-case-sql.aspx</link>
            <description>Here's a very common situation that is very easy to optimize and simplify, submitted via the &lt;a target="_blank" href="http://weblogs.sqlteam.com/jeffs/contact.aspx"&gt;mailbag&lt;/a&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;Nate writes:&lt;span style="font-style: italic;"&gt;&lt;br /&gt;
&lt;br /&gt;
Hey, I have a read a bunch of your stuff on your blog and you seem to&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; be right on the money. I thought maybe you would be able to point me in&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; the right direction and possibly address this issue on your blog so&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; others could benefit from your understanding. &lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;I have been searching for the best way to do what I think should be a&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; simple task in SQL. I have a table full of call history events and I&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; want to get a summary of some events for each calling party that matches&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; the form "username(extension)". Using correlated subqueries I do the&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; following.&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;SELECT calling_party,&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;(SELECT SUM(end_time-start_time) AS total_time FROM `event` ei WHERE&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; event_type=4 AND ei.calling_party = eo.calling_party) AS&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; total_talking_time, &lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;(SELECT SUM(end_time-start_time) AS total_time FROM `event` ei WHERE&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; event_type=7 AND ei.calling_party = eo.calling_party) AS&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; total_ringing_time,&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;(SELECT MAX(end_time-start_time) AS total_time FROM `event` ei WHERE&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; event_type=4 AND ei.calling_party = eo.calling_party) AS max_talking_time&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;FROM `event` eo WHERE calling_party LIKE '%(%)'&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;GROUP BY calling_party&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;This works, but ends up taking a really long time to run. I figured&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; that each subquery was getting executed multiple times. I made a quick php&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; script that returns the same information but uses multiple queries. I&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; just query all of the "groups" (SELECT DISTINCT calling_party FROM&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; `event` WHERE calling_party LIKE '%(%)') and then iterate over that&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; recordset plugging the actual calling_party value into the where clause of the&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; aggregate query. The output is identical, but this script runs in well&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; under a second.&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;So the question is what is the correct way to do this using SQL? Any&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; help would be really appreciated.&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt; &lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
Hi Nate --&lt;br /&gt;
&lt;br /&gt;
The script as written is making 4 different calls to the calling_party table, and we can reduce this to one call to the table by using CASE expressions to only aggregate the data we need for each column.  This is pretty much the standard "static cross-tab" technique to summarize data into multiple columns in a single SELECT.&lt;br /&gt;
&lt;br /&gt;
So, all we need to do is re-write your SELECT like this:&lt;br /&gt;
&lt;br style="font-family: Courier New;" /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;select&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;  calling_party,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;  sum(case when event_type=4 then end_time-start_time else 0 end) as total_talking_time,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;  sum(case when event_type=7 then end_time-start_time else 0 end) as total_ringing_time,&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;  max(case when event_type=4 then end_time-start_time else 0 end) as max_talking_time&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;from&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;  event&lt;br /&gt;
where&lt;br /&gt;
&lt;/span&gt;    &lt;span style="font-family: Courier New;"&gt;calling_party LIKE '%(%)'&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;group by&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt;  calling_party&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
That will return the same results as what you had written, only it should be much more efficient.  It is also much shorter.  Notice that the CASE expressions ensure that only the data with the specified event_type is including in each aggregate calculation, otherwise we aggregate a value of 0 which has no affect on the results.  You can also use a default value of NULL instead of 0 if you'd like, depending on your desired results.&lt;br /&gt;
&lt;br /&gt;
If you have more data in that table for event_types other than 4 and 7, and you only want to return calling_party rows that contain data with those event_types, then be sure to add&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;and event_type in (4,7)&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
to your WHERE clause, which will increase the efficiency even further.   However, adding that will return different results from your original SELECT (it returns all calling_groups regardless of event_type) so be sure that what you do meets the specifications you require.&lt;br /&gt;
&lt;br /&gt;
As always, be sure that you have proper indexes on all of your tables, and in this case it seems that event_type should certainly be indexed. &lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-style: italic;"&gt;see also:&lt;br /&gt;
&lt;/span&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl06_HyperLink1" title="View Entry" href="../../../../jeffs/archive/2007/11/13/sql-aggregate-totals.aspx"&gt;Some SELECTs will never return 0 rows -- regardless of the criteria&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a title="View Entry" href="../../../../jeffs/archive/2007/10/18/sql-server-cross-apply.aspx"&gt;Taking a look at CROSS APPLY&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a title="View Entry" href="../../../../jeffs/archive/2007/09/18/sql-conditional-where-clauses.aspx"&gt;Optimizing Conditional WHERE Clauses: Avoiding ORs and CASE Expressions&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl10_HyperLink1" title="View Entry" href="../../../../jeffs/archive/2007/06/12/60230.aspx"&gt;Using GROUP BY to avoid self-joins&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a title="View Entry" href="../../../../jeffs/archive/2007/05/14/60205.aspx"&gt;Criteria on Outer Joined Tables&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a title="View Entry" href="../../../../jeffs/archive/2007/04/19/Full-Outer-Joins.aspx"&gt;Better Alternatives to a FULL OUTER JOIN&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl02_HyperLink1" title="View Entry" href="../../../../jeffs/archive/2007/05/03/60195.aspx"&gt;In SQL, it's a Case Expression, *not* a Case Statement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://weblogs.sqlteam.com/jeffs/aggbug/60452.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jeff Smith</dc:creator>
            <guid>http://weblogs.sqlteam.com/jeffs/archive/2008/01/09/rewrite-correlated-sub-query-with-case-sql.aspx</guid>
            <pubDate>Wed, 09 Jan 2008 13:42:16 GMT</pubDate>
            <wfw:comment>http://weblogs.sqlteam.com/jeffs/comments/60452.aspx</wfw:comment>
            <comments>http://weblogs.sqlteam.com/jeffs/archive/2008/01/09/rewrite-correlated-sub-query-with-case-sql.aspx#feedback</comments>
            <slash:comments>2</slash:comments>
            <wfw:commentRss>http://weblogs.sqlteam.com/jeffs/comments/commentRss/60452.aspx</wfw:commentRss>
            <trackback:ping>http://weblogs.sqlteam.com/jeffs/services/trackbacks/60452.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL 2005 PIVOT Operator (link)</title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2007/07/12/60253.aspx</link>
            <description>There's two interesting posts over at the &lt;a target="_blank" href="http://blogs.msdn.com"&gt;MSDN blogs&lt;/a&gt; from &lt;a href="http://blogs.msdn.com/craigfr/"&gt;Craig Freedman&lt;/a&gt; about the new PIVOT operator in SQL 2005.  First, he gives a nice &lt;a href="http://blogs.msdn.com/craigfr/archive/2007/07/03/the-pivot-operator.aspx" target="_blank"&gt;overview&lt;/a&gt; of the operator and how to use it, and then he follows it up with a &lt;a href="http://blogs.msdn.com/craigfr/archive/2007/07/09/pivot-query-plans.aspx" target="_blank"&gt;performance analysis&lt;/a&gt; as well.  A good read, check it out.  &lt;br /&gt;
&lt;br /&gt;
I hope to cover both the PIVOT and also the UNPIVOT operators a little myself at some point in a future post.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-style: italic;"&gt;see also:&lt;br /&gt;
&lt;/span&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2003/10/24/363.aspx" title="View Entry"&gt;Keep those SQL Crosstabs flexible!&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/11/5101.aspx" title="View Entry"&gt;.NET CrossTabs: Transforming a Normalized DataReader into a Pivoted DataTable&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/12/5127.aspx" title="View Entry" id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl04_HyperLink1"&gt;.NET CrossTabs versus SQL Server CrossTabs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/15/5175.aspx" title="View Entry"&gt;ASP and ADO Pivots -- Old School CrossTabs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2007/04/20/60182.aspx" title="View Entry" id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl02_HyperLink1"&gt;Sorting Columns with the C# Pivot Function&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;span style="text-decoration: underline;"&gt;&lt;span style="font-weight: bold;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;img src="http://weblogs.sqlteam.com/jeffs/aggbug/60253.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jeff Smith</dc:creator>
            <guid>http://weblogs.sqlteam.com/jeffs/archive/2007/07/12/60253.aspx</guid>
            <pubDate>Thu, 12 Jul 2007 13:24:38 GMT</pubDate>
            <wfw:comment>http://weblogs.sqlteam.com/jeffs/comments/60253.aspx</wfw:comment>
            <comments>http://weblogs.sqlteam.com/jeffs/archive/2007/07/12/60253.aspx#feedback</comments>
            <slash:comments>1</slash:comments>
            <wfw:commentRss>http://weblogs.sqlteam.com/jeffs/comments/commentRss/60253.aspx</wfw:commentRss>
            <trackback:ping>http://weblogs.sqlteam.com/jeffs/services/trackbacks/60253.aspx</trackback:ping>
        </item>
        <item>
            <title>Sorting Columns with the C# Pivot Function</title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2007/04/20/60182.aspx</link>
            <description>Time for another exciting edition of the &lt;a href="http://weblogs.sqlteam.com/jeffs/contact.aspx"&gt;mailbag&lt;/a&gt;!&lt;br /&gt;
&lt;br /&gt;
Maxime writes:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-style: italic;"&gt;Hi,&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;First of all, your class is really nice and it handles my problem &lt;/span&gt;&lt;span style="font-style: italic;"&gt;really well.&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;I would like to ask you something about the class.  I would like to &lt;/span&gt;&lt;span style="font-style: italic;"&gt;order the pivot columns by column name but I'm unable to do that yet.  Do &lt;/span&gt;&lt;span style="font-style: italic;"&gt;you think you could help me with that ?&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;Thank you in advance for your time and sorry to bother you with this &lt;/span&gt;&lt;span style="font-style: italic;"&gt;question but I've tried to post in forums but didn't get any results.&lt;/span&gt;&lt;br style="font-style: italic;" /&gt;
&lt;br style="font-style: italic;" /&gt;
&lt;span style="font-style: italic;"&gt;Maxime &lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
(Note: Maxime is referring to my simple Pivot function discussed &lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/11/5101.aspx"&gt;here&lt;/a&gt;.  The idea is, as always, to let SQL focus on returning raw, summarized and filtered &lt;span style="font-style: italic;"&gt;data&lt;/span&gt; in a standard row/column result, and to let your very &lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/12/5127.aspx"&gt;capable and efficient presentation layer&lt;/a&gt; handle all formatting -- which also happens to include pivoting data from rows to columns.)&lt;br /&gt;
&lt;br /&gt;
Sure thing, Maxime, that's a reasonable request and a great idea for improving the code.&lt;br /&gt;
&lt;br /&gt;
If you are using .NET 2.0, you can easily re-arrange columns in a DataTable, but this is not so easy in 1.1.  By using the setOrdinal method of a DataColumn, we can ensure that we are creating those new columns in the proper place.&lt;br /&gt;
&lt;br /&gt;
All you need to do is take the existing code for the Pivot function shown &lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/11/5091.aspx"&gt;here&lt;/a&gt; and change just this line:&lt;br /&gt;
&lt;br /&gt;
&lt;p style="margin: 0in 0in 0pt; font-family: Courier New;" class="MsoNormal"&gt;      if (!tmp.Columns.Contains(s))&lt;/p&gt;
                      &lt;span style="font-family: Courier New;"&gt;tmp.Columns.Add(s, dataValues.GetFieldType(pValIndex));&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
To:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-left: 40px;"&gt;&lt;span style="font-family: Courier New;"&gt;if (!tmp.Columns.Contains(s))&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;{&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;  DataColumn c = tmp.Columns.Add(s, dataValues.GetFieldType(pValIndex));&lt;/span&gt;&lt;br style="font-family: Courier New; font-weight: bold;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;  // set the index so that it is sorted properly:&lt;/span&gt;&lt;br style="font-family: Courier New; font-weight: bold;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;  int newOrdinal = c.Ordinal;&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New;"&gt; &lt;span style="font-weight: bold;"&gt; for (i = newOrdinal - 1; i &amp;gt;= dataValues.FieldCount - 2; i--)&lt;/span&gt;&lt;/span&gt;&lt;br style="font-family: Courier New; font-weight: bold;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;    if (c.ColumnName.CompareTo(tmp.Columns[i].ColumnName) &amp;lt; 0)&lt;/span&gt;&lt;br style="font-family: Courier New; font-weight: bold;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;      newOrdinal = i;&lt;/span&gt;&lt;br style="font-family: Courier New; font-weight: bold;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;  c.SetOrdinal(newOrdinal);&lt;/span&gt;&lt;br style="font-family: Courier New;" /&gt;
&lt;span style="font-family: Courier New; font-weight: bold;"&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
That should result in "sorted" pivot columns.  Use similar tricks if necessary to move other columns around in any DataTable as needed.  To sort the columns in descending order, change the CompareTo() method to compare with &amp;gt; 0.  In addition, keep in mind that this is a regular text comparison; if you need to sort numeric or date column headers, you should first convert those strings to proper datatypes before doing the compare, or at the very least to a string format that compares and sorts you way you'd like.  &lt;br /&gt;
&lt;br /&gt;
If you are using .NET 1.1, your only options are to a) move the data from this DataTable to &lt;span style="font-style: italic;"&gt;another&lt;/span&gt; one, with columns that are properly sorted as the new table is created, or b) gather the column names in advance and pass those to the function and create the DataTable with those columns already added and ready to be filled in the proper order.&lt;br /&gt;
&lt;br /&gt;
I hope this helps, and congratulations on writing simple, clear, short and efficient SQL code while letting your presentation layer do its job!&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-style: italic;"&gt;see also:&lt;br /&gt;
&lt;/span&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2003/10/24/363.aspx" title="View Entry"&gt;Keep those SQL Crosstabs flexible!&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/11/5101.aspx" title="View Entry"&gt;.NET CrossTabs: Transforming a Normalized DataReader into a Pivoted DataTable&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/12/5127.aspx" title="View Entry" id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl04_HyperLink1"&gt;.NET CrossTabs versus SQL Server CrossTabs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/15/5175.aspx" title="View Entry"&gt;ASP and ADO Pivots -- Old School CrossTabs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2007/07/12/60253.aspx" title="View Entry"&gt;SQL 2005 PIVOT Operator (link)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://weblogs.sqlteam.com/jeffs/aggbug/60182.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jeff Smith</dc:creator>
            <guid>http://weblogs.sqlteam.com/jeffs/archive/2007/04/20/60182.aspx</guid>
            <pubDate>Fri, 20 Apr 2007 16:05:42 GMT</pubDate>
            <comments>http://weblogs.sqlteam.com/jeffs/archive/2007/04/20/60182.aspx#feedback</comments>
            <slash:comments>2</slash:comments>
            <wfw:commentRss>http://weblogs.sqlteam.com/jeffs/comments/commentRss/60182.aspx</wfw:commentRss>
            <trackback:ping>http://weblogs.sqlteam.com/jeffs/services/trackbacks/60182.aspx</trackback:ping>
        </item>
        <item>
            <title>ASP and ADO Pivots -- Old School CrossTabs</title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2005/05/15/5175.aspx</link>
            <description>&lt;p&gt;OK, so let's sum up my recent flury of crosstab / pivoting posts the past week or so:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/02/4842.aspx"&gt;Here's a dynamic way to do crosstabs in SQL Server&lt;/a&gt;  &lt;/li&gt;
    &lt;li&gt;&lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/11/5101.aspx"&gt;But here's an alternate way to do this at the presentation layer in .NET&lt;/a&gt;  &lt;/li&gt;
    &lt;li&gt;&lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/12/5127.aspx"&gt;And here's a performance comparison&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jumping back a bit, I thought I'd also post a bit of code for those still using good old ASP and/or ADO.  This is a function writen in VBScript that works much in the same way as the &lt;a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdmthgetrows.asp"&gt;GetRows()&lt;/a&gt; method of an ADO recordset -- it returns the entire contents of the recordset in a 2-dimensional array.  The difference with this one, of course, is that it will also do a pivot for you.   &lt;/p&gt;
&lt;p&gt;An entire sample ASP page with the function embedded in it for you to enjoy can be &lt;a href="http://weblogs.sqlteam.com/jeffs/articles/5174.aspx"&gt;found here&lt;/a&gt;.  This example, like the others, uses the Northwind database and requires that you have &lt;a href="http://weblogs.sqlteam.com/jeffs/articles/5125.aspx"&gt;created the view defined here&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Here are the parameters and expected data types of this “old school” Pivot function:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;em&gt;rsData&lt;/em&gt; -- (ADODB.Recordset) This is the ADO recordset which contains the data to be pivoted. As before, it must be fully grouped, summarized and sorted properly.  &lt;/li&gt;
    &lt;li&gt;&lt;em&gt;strRow &lt;/em&gt;- (String) This is the name of the field that can determines the Rows in the pivot table  &lt;/li&gt;
    &lt;li&gt;&lt;em&gt;strColumn&lt;/em&gt; -- (String)  The name of the field that will be pivoted into columns  &lt;/li&gt;
    &lt;li&gt;&lt;em&gt;strValue&lt;/em&gt; --  (String) The the name of the field that contains the summarized data to be pivoted into columns.  &lt;/li&gt;
    &lt;li&gt;&lt;em&gt;rsColumnVals -- &lt;/em&gt;(ADODB.Recordset) This is a new one -- pass in a recordset containing a single column of vals for which to create the pivoted columns.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last parameter is a new one, and it makes things as efficient as possible to get the list of column values ahead of time.  This is similiar to what the dynamic stored procedure method does.  Typically, if you are pivoting on field “XYZ”, then you will pass in a recordset that is defined along the lines of “SELECT DISTINCT XYZ FROM ...” for the rsColumnVals parameter.  Another thing to note is that the first row of values returned by this function will contain the column headers for the result, which is necessary since those column headers are unknown at runtime unlike when using GetRows().   See the comments and the example for more details.&lt;/p&gt;
&lt;p&gt;As before, this method, even when compared to the ultra-speedy GetRows() method in ADO, is clearer, faster, and more efficient than the dynamic sql technique.   The main benefit, of course, is that you can use standard SQL with normal grouping and summarizing and you are just returning the data from the database, and not worrying about formatting the results into columns for display purposes.&lt;/p&gt;
&lt;p&gt;Feel free to use or modify my code as needed; it is not designed to be a complete ready-to-use libary, but rather an example of ways you can create your own functions at the presentation layer in your applications to do things that the database layer should not have to worry about. &lt;/p&gt;&lt;img src="http://weblogs.sqlteam.com/jeffs/aggbug/5175.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jeff Smith</dc:creator>
            <guid>http://weblogs.sqlteam.com/jeffs/archive/2005/05/15/5175.aspx</guid>
            <pubDate>Mon, 16 May 2005 01:26:00 GMT</pubDate>
            <comments>http://weblogs.sqlteam.com/jeffs/archive/2005/05/15/5175.aspx#feedback</comments>
            <slash:comments>2</slash:comments>
            <wfw:commentRss>http://weblogs.sqlteam.com/jeffs/comments/commentRss/5175.aspx</wfw:commentRss>
            <trackback:ping>http://weblogs.sqlteam.com/jeffs/services/trackbacks/5175.aspx</trackback:ping>
        </item>
        <item>
            <title>.NET CrossTabs versus SQL Server CrossTabs </title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2005/05/12/5127.aspx</link>
            <description>As promised in my last post, here is some performance testing to help you determine the performance benefits (if any) of performing your crosstabs at the presentation or code layer, as opposed to forcing SQL Server to do this.
&lt;br&gt;&lt;br&gt;
&lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/12/5127.aspx"&gt;read more...&lt;/a&gt;&lt;img src="http://weblogs.sqlteam.com/jeffs/aggbug/5127.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jeff Smith</dc:creator>
            <guid>http://weblogs.sqlteam.com/jeffs/archive/2005/05/12/5127.aspx</guid>
            <pubDate>Thu, 12 May 2005 14:01:00 GMT</pubDate>
            <comments>http://weblogs.sqlteam.com/jeffs/archive/2005/05/12/5127.aspx#feedback</comments>
            <slash:comments>4</slash:comments>
            <wfw:commentRss>http://weblogs.sqlteam.com/jeffs/comments/commentRss/5127.aspx</wfw:commentRss>
            <trackback:ping>http://weblogs.sqlteam.com/jeffs/services/trackbacks/5127.aspx</trackback:ping>
        </item>
        <item>
            <title>.NET CrossTabs: Transforming a Normalized DataReader into a Pivoted DataTable</title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2005/05/11/5101.aspx</link>
            <description>&lt;p&gt;In my &lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/02/4842.aspx"&gt;last post&lt;/a&gt;, I spoke briefly about how I felt that in general &lt;a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/acdata/ac_8_qd_14_04j7.asp"&gt;crosstabbing&lt;/a&gt; data is something that a presentation layer should do and not the database.  Consider the result of a crosstab operation -- the columns returned will vary depending on the data.  There is pretty much no further manipulation you can do with that result in T-SQL; in the relational database world, the column names of our database objects should be constants and not continually changing as the data changes.  Also, in T-SQL there is no easy way to dynamically pivot data, and even doing it with hard-coded pivot values results in long, complicated SELECT statements.  So, in my opinion there is really is no benefit of doing this transformation within SQL Server.  (Unless you are in the mood for some fun dynamic sql and cursors ...)&lt;/p&gt;
&lt;p&gt;Almost all modern report designers allow you to quickly and easily crosstab a standard recordset from SQL Server, so be sure to learn how to use this feature and take advantage of it.  Some are more flexible than others, but for the most part they work as you'd expect.  Either way, for report-writing crosstabing data at the presentation layer should not be a concern.&lt;/p&gt;
&lt;p&gt;The main issue is programmers who are writing their own presentation layer.  Many programmers have difficulty writing code to do the transformation; I believe this may be why it is common to be tempted to force SQL Server to do all of the work.&lt;/p&gt;
&lt;p&gt;Here's a quick example of an easy way to implement a crosstab at the presentation layer using C#. To keep things short and simple, I have just written a small static function that accepts any object that implements IDataReader and returns a crosstabbed DataTable object.  The code for the function is &lt;a href="http://weblogs.sqlteam.com/jeffs/articles/5091.aspx"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All you need to do is open up an IDataReader object that returns the data you wish to Pivot.  The data should be fully summarized by SQL Server already -- i.e., you should GROUP BY your pivot column plus any other columns you wish to return, and you should aggregate your pivot value accordingly.  The DataReader should also be sorted so that all rows which will be pivoted into one are sorted together.   &lt;/p&gt;
&lt;p&gt;For example, consider the Orders table in the Northwind database.  Suppose you want to display CompanyNames as rows, ProductNames as columns, and the total quantity of orders per CompanyName per ProductName in your crosstab.  The DataReader's recordset should be grouped by CompanyName and ProductName, it should include a SUM(Qty) calculation, and it should be sorted by CompanyName.  (see &lt;a href="http://weblogs.sqlteam.com/jeffs/articles/5093.aspx"&gt;the example&lt;/a&gt; for this exact scenario using Northwind).  &lt;/p&gt;
&lt;p&gt;What's nice is, this is all standard SQL and requires very little effort in terms of writing the SELECT and for the server to process and return the results.  We are letting the presentation layer handle the formatting.  Another benefit is that we can use this technique with &lt;em&gt;any&lt;/em&gt; datasource that ADO.NET can connect to, not just SQL Server.  (In fact, we can do this with anything that implements IDataReader)&lt;/p&gt;
&lt;p&gt;The arguments for the Pivot() function are as follows:&lt;/p&gt;
&lt;p&gt;&lt;font color="#000080"&gt;public static DataTable &lt;a href="http://weblogs.sqlteam.com/jeffs/articles/5091.aspx"&gt;Pivot &lt;/a&gt;(IDataReader &lt;em&gt;dataValues&lt;/em&gt;, string &lt;em&gt;keyColumn&lt;/em&gt;, string &lt;em&gt;pivotNameColumn&lt;/em&gt;, string &lt;em&gt;pivotValueColumn&lt;/em&gt;)&lt;/font&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;em&gt;dataValues&lt;/em&gt; -- this is any open DataReader object, ready to be transformed and pivoted into a DataTable.  As mentioned, it should be fully grouped, aggregated, sorted and ready to go.   &lt;/li&gt;
    &lt;li&gt;&lt;em&gt;keyColumn&lt;/em&gt; -- This is the column in the DataReader which serves to identify each row.  In the previous example, this would be CustomerID.  Your DataReader's recordset should be grouped and sorted by this column as well.  &lt;/li&gt;
    &lt;li&gt;&lt;em&gt;pivotNameColumn -- &lt;/em&gt;This is the column in the DataReader that contains the values you'd like to transform from rows into columns.   In the example, this would be ProductName.  &lt;/li&gt;
    &lt;li&gt;&lt;em&gt;pivotValueColumn -- &lt;/em&gt;This is the column that in the DataReader that contains the values to pivot into the appropriate columns.  For our example, it would be Qty, which has been defined in the SELECT statement as SUM(Qty).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And, that's it.  You just call the Pivot() function on any object that implements IDataReader, specify the pivot information, and a DataTable is returned that you can use however you like.  The example uses this to fill up a DataGrid so you can see the results on a form.&lt;/p&gt;
&lt;p&gt;Stay tuned for a performance comparison between this method and doing the crosstab transformation on the SQL Server .... You might be surprised by the results.&lt;/p&gt;
&lt;br /&gt;
&lt;span style="font-style: italic;"&gt;see also:&lt;br /&gt;
&lt;/span&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2003/10/24/363.aspx" title="View Entry"&gt;Keep those SQL Crosstabs flexible!&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/12/5127.aspx" title="View Entry" id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl04_HyperLink1"&gt;.NET CrossTabs versus SQL Server CrossTabs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/15/5175.aspx" title="View Entry"&gt;ASP and ADO Pivots -- Old School CrossTabs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2007/04/20/60182.aspx" title="View Entry" id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl02_HyperLink1"&gt;Sorting Columns with the C# Pivot Function&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2007/07/12/60253.aspx" title="View Entry"&gt;SQL 2005 PIVOT Operator (link)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://weblogs.sqlteam.com/jeffs/aggbug/5101.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jeff Smith</dc:creator>
            <guid>http://weblogs.sqlteam.com/jeffs/archive/2005/05/11/5101.aspx</guid>
            <pubDate>Wed, 11 May 2005 19:49:00 GMT</pubDate>
            <comments>http://weblogs.sqlteam.com/jeffs/archive/2005/05/11/5101.aspx#feedback</comments>
            <slash:comments>21</slash:comments>
            <wfw:commentRss>http://weblogs.sqlteam.com/jeffs/comments/commentRss/5101.aspx</wfw:commentRss>
            <trackback:ping>http://weblogs.sqlteam.com/jeffs/services/trackbacks/5101.aspx</trackback:ping>
        </item>
        <item>
            <title>Another Dynamic SQL CrossTab Stored Procedure</title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2005/05/02/4842.aspx</link>
            <description>First off, before going any further make sure you have read the hall of fame SQLTeam article by Rob Volk on generating crosstab results using a flexible, dynamic stored procedure that has been viewed over 100,000 times!  &lt;br&gt;&lt;br&gt;&lt;a href="http://weblogs.sqlteam.com/jeffs/archive/2005/05/02/4842.aspx"&gt;read more...&lt;/a&gt;&lt;img src="http://weblogs.sqlteam.com/jeffs/aggbug/4842.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jeff Smith</dc:creator>
            <guid>http://weblogs.sqlteam.com/jeffs/archive/2005/05/02/4842.aspx</guid>
            <pubDate>Mon, 02 May 2005 18:00:00 GMT</pubDate>
            <comments>http://weblogs.sqlteam.com/jeffs/archive/2005/05/02/4842.aspx#feedback</comments>
            <slash:comments>51</slash:comments>
            <wfw:commentRss>http://weblogs.sqlteam.com/jeffs/comments/commentRss/4842.aspx</wfw:commentRss>
            <trackback:ping>http://weblogs.sqlteam.com/jeffs/services/trackbacks/4842.aspx</trackback:ping>
        </item>
        <item>
            <title>Keep those SQL Crosstabs flexible!</title>
            <link>http://weblogs.sqlteam.com/jeffs/archive/2003/10/24/363.aspx</link>
            <description>&lt;p&gt;I recently helped someone with performing some simple cross tabs (or pivots) in SQL, such as:&lt;/p&gt;
&lt;p style="font-family: Courier New; margin-left: 40px;"&gt;Select CustomerID, &lt;br /&gt;
  SUM(CASE WHEN type = 'Land' THEN Amount ELSE 0 END) as Land,&lt;br /&gt;
  SUM(CASE WHEN type = 'Sea' THEN Amount ELSE 0 END) as Sea,&lt;br /&gt;
  SUM(CASE WHEN type = 'Air' THEN Amount ELSE 0 END) as Air&lt;br /&gt;
FROM&lt;br /&gt;
  SomeTable&lt;br /&gt;
GROUP BY CustomerID&lt;/p&gt;
&lt;p&gt;This made the person I helped happy -- it worked well and gave him what he wanted.  &lt;/p&gt;
&lt;p&gt;Of course, I had to ask: “These 3 values -- Land, Sea, Air -- can they ever change?”  And he indicated that they might -- they are user definable.  There can only be 3, but the user can name them as they like. &lt;/p&gt;
&lt;p&gt;The solution, then, was to add a column called “ColIndex” to the master table of “Types” with values of 1-3 for each type and to re-write the query this way:&lt;/p&gt;
&lt;p style="font-family: Courier New; margin-left: 40px;"&gt;Select CustomerID, &lt;br /&gt;
  SUM(CASE WHEN ColIndex=1 THEN Amount ELSE 0 END) as Type1,&lt;br /&gt;
  SUM(CASE WHEN ColIndex=2 THEN Amount ELSE 0 END) as Type2,&lt;br /&gt;
  SUM(CASE WHEN ColIndex=3 THEN Amount ELSE 0 END) as Type3&lt;br /&gt;
FROM&lt;br /&gt;
  SomeTable&lt;br /&gt;
GROUP BY CustomerID&lt;/p&gt;
&lt;p&gt;This allowed the cross tab to work regardless of their names.  The other advantage is now the columns returned by this query are constant -- they never change regardless of the data in the database.  Just generic Type1, Type2, etc are returned.  There is a limit to the number of Types we can handle -- that must be fixed without using dynamic SQL -- but now we can handle changed Types without re-writing any code.&lt;/p&gt;
&lt;p&gt;The final thing to do, then, at the presentation layer just make sure to display column headers containing the names for each type.  Much better to simply &lt;em&gt;present &lt;/em&gt;changing column headers than to actually &lt;em&gt;physcially change &lt;/em&gt;the column names in your views or stored procs.&lt;/p&gt;
&lt;p&gt;The moral:  keep things generic, plan for the future, and let the presentation layer do its share of the work.  &lt;/p&gt;
&lt;p&gt;(of course, if the presentation layer is capable of pivoting -- even better !!)&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
&lt;span style="font-style: italic;"&gt;see also:&lt;br /&gt;
&lt;/span&gt; &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2003/10/24/363.aspx" title="View Entry"&gt;Keep those SQL Crosstabs flexible!&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/11/5101.aspx" title="View Entry"&gt;.NET CrossTabs: Transforming a Normalized DataReader into a Pivoted DataTable&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/12/5127.aspx" title="View Entry" id="ctl00_pageContent_Editor_Results_rprSelectionList_ctl04_HyperLink1"&gt;.NET CrossTabs versus SQL Server CrossTabs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/archive/2005/05/15/5175.aspx" title="View Entry"&gt;ASP and ADO Pivots -- Old School CrossTabs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="../../../../jeffs/a