<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Craig Freedman's SQL Server Blog : Rollup and Cube</title><link>http://blogs.msdn.com/craigfr/archive/tags/Rollup+and+Cube/default.aspx</link><description>Tags: Rollup and Cube</description><dc:language>en</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>GROUPING SETS in SQL Server 2008</title><link>http://blogs.msdn.com/craigfr/archive/2007/10/11/grouping-sets-in-sql-server-2008.aspx</link><pubDate>Thu, 11 Oct 2007 19:13:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:5402551</guid><dc:creator>craigfr</dc:creator><slash:comments>4</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/5402551.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=5402551</wfw:commentRss><description>In my last two posts, I gave examples of aggregation &lt;A title="Aggregation WITH ROLLUP" href="http://blogs.msdn.com/craigfr/archive/2007/09/21/aggregation-with-rollup.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2007/09/21/aggregation-with-rollup.aspx"&gt;WITH ROLLUP&lt;/A&gt; and &lt;A title="Aggregation WITH CUBE" href="http://blogs.msdn.com/craigfr/archive/2007/09/27/aggregation-with-cube.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2007/09/27/aggregation-with-cube.aspx"&gt;CUBE&lt;/A&gt;.&amp;nbsp; SQL Server 2008 continues to support this syntax, but also introduces new more powerful ANSI SQL 2006 compliant syntax.&amp;nbsp; In this post, I'll give an overview of the changes. 
&lt;P&gt;First, let's see how we rewrite simple WITH ROLLUP and CUBE queries using the new syntax.&amp;nbsp; I'll use the same schema and queries as in my previous posts:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE TABLE Sales (EmpId INT, Yr INT, Sales MONEY)&lt;BR&gt;INSERT Sales VALUES(1, 2005, 12000)&lt;BR&gt;INSERT Sales VALUES(1, 2006, 18000)&lt;BR&gt;INSERT Sales VALUES(1, 2007, 25000)&lt;BR&gt;INSERT Sales VALUES(2, 2005, 15000)&lt;BR&gt;INSERT Sales VALUES(2, 2006, 6000)&lt;BR&gt;INSERT Sales VALUES(3, 2006, 20000)&lt;BR&gt;INSERT Sales VALUES(3, 2007, 24000)&lt;/P&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY EmpId, Yr WITH ROLLUP&lt;/P&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY EmpId, Yr WITH CUBE&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;We can rewrite these two queries using the new syntax as:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY ROLLUP(EmpId, Yr)&lt;/P&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY CUBE(EmpId, Yr)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;These new queries are semantically equivalent to and use the same query plans as the original queries.&amp;nbsp; Note that the new ROLLUP and CUBE syntax is only available in compatibility level 100.&amp;nbsp; The more general GROUPING SETS syntax, which I will discuss next, is also available in earlier compatibility levels.&lt;/P&gt;
&lt;P&gt;The new GROUPING SETS syntax is considerably more powerful.&amp;nbsp; It allows us to specify precisely which aggregations we want to compute.&amp;nbsp; As the following table illustrates, our simple two dimensional schema has a total of only four possible aggregations:&lt;/P&gt;
&lt;TABLE class="" cellSpacing=0 cellPadding=0 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96 colSpan=2 rowSpan=2&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96 colSpan=4&gt;
&lt;P align=center&gt;&lt;B&gt;Yr&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;2005&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;2006&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;2007&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=133&gt;
&lt;P&gt;&lt;B&gt;ALL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" width=96 rowSpan=4&gt;
&lt;P&gt;&lt;B&gt;EmpId&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;1&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" width=288 colSpan=3 rowSpan=3&gt;
&lt;P&gt;GROUP BY (EmpId, Yr)&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" width=133 rowSpan=3&gt;
&lt;P&gt;GROUP BY (EmpId)&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;2&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;3&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;ALL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" width=288 colSpan=3&gt;
&lt;P&gt;GROUP BY (Yr)&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=133&gt;
&lt;P&gt;GROUP BY ()&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P mce_keep="true"&gt;ROLLUP and CUBE are just shorthand for two common usages of GROUPING SETS.&amp;nbsp; We can express the above ROLLUP query as:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS((EmpId, Yr), (EmpId), ())&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;EmpId       Yr          Sales
----------- ----------- ---------------------
1           2005        12000.00
1           2006        18000.00
1           2007        25000.00
1           NULL        55000.00
2           2005        15000.00
2           2006        6000.00
2           NULL        21000.00
3           2006        20000.00
3           2007        24000.00
3           NULL        44000.00
NULL        NULL        120000.00&lt;/PRE&gt;
&lt;P&gt;This query explicitly asks SQL Server to aggregate sales by employee and year, to aggregate by employee only, and to compute the total for all employees for all years.&amp;nbsp; The () syntax with no GROUP BY columns denotes the total.&amp;nbsp; Similarly, we can express the above CUBE query by asking SQL Server to compute all possible aggregate combinations:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS((EmpId, Yr), (EmpId), (Yr), ())&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;EmpId       Yr          Sales
----------- ----------- ---------------------
1           2005        12000.00
2           2005        15000.00
NULL        2005        27000.00
1           2006        18000.00
2           2006        6000.00
3           2006        20000.00
NULL        2006        44000.00
1           2007        25000.00
3           2007        24000.00
NULL        2007        49000.00
NULL        NULL        120000.00
1           NULL        55000.00
2           NULL        21000.00
3           NULL        44000.00&lt;/PRE&gt;
&lt;P&gt;We can also use GROUPING SETS to compute other results.&amp;nbsp; For example, we can perform a partial rollup aggregating sales by employee and year and by employee only but without computing the total for all employees for all years:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS((EmpId, Yr), (EmpId))&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;EmpId       Yr          Sales
----------- ----------- ---------------------
1           2005        12000.00
1           2006        18000.00
1           2007        25000.00
1           NULL        55000.00
2           2005        15000.00
2           2006        6000.00
2           NULL        21000.00
3           2006        20000.00
3           2007        24000.00
3           NULL        44000.00&lt;/PRE&gt;
&lt;P&gt;We can skip certain rollup levels.&amp;nbsp; For example, we can compute the total sales by employee and year and the total sales for all employees and all years without computing any of the intermediate results:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS((EmpId, Yr), ())&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;EmpId       Yr          Sales
----------- ----------- ---------------------
1           2005        12000.00
1           2006        18000.00
1           2007        25000.00
2           2005        15000.00
2           2006        6000.00
3           2006        20000.00
3           2007        24000.00
NULL        NULL        120000.00&lt;/PRE&gt;
&lt;P&gt;We can even compute multiple unrelated aggregations along disparate dimensions.&amp;nbsp; For example, we can compute the total sales by employee and the total sales by year:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS((EmpId), (Yr))&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;EmpId       Yr          Sales
----------- ----------- ---------------------
NULL        2005        27000.00
NULL        2006        44000.00
NULL        2007        49000.00
1           NULL        55000.00
2           NULL        21000.00
3           NULL        44000.00&lt;/PRE&gt;
&lt;P&gt;Note that we could also write GROUPING SETS (EmpId, Yr) without the extra set of parenthesis, but the extra parenthesis make the intent of the query more explicit and clearly differentiate the previous query from the following query which just performs a normal aggregation by employee and year:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS((EmpId, Yr))&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;EmpId       Yr          Sales
----------- ----------- ---------------------
1           2005        12000.00
2           2005        15000.00
1           2006        18000.00
2           2006        6000.00
3           2006        20000.00
1           2007        25000.00
3           2007        24000.00&lt;/PRE&gt;
&lt;P&gt;Here are some additional points worth noting about the GROUPING SETS syntax:&lt;/P&gt;
&lt;P&gt;As with any other aggregation query, if a column appears in the SELECT list and is not part of an aggregate function, it must appear somewhere in the GROUP BY clause.&amp;nbsp; Thus, the following is not valid:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS((EmpId), ())&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;Msg 8120, Level 16, State 1, Line 1&lt;BR&gt;Column 'Sales.Yr' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;The order of the columns within each GROUPING SET and the order of the GROUPING SETS does not matter.&amp;nbsp; So both of the following queries compute the same CUBE although the order that the rows are output differs:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS&lt;B&gt;((EmpId, Yr), (EmpId), (Yr), ())&lt;/B&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS&lt;B&gt;((), (Yr), (EmpId), (Yr, EmpId))&lt;/B&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;If the order that the rows are output matters, use an explicit ORDER BY clause to enforce that order.&lt;/P&gt;
&lt;P&gt;We can nest CUBE and ROLLUP within a GROUPING SETS clause as shorthand for expressing more complex GROUPING SETS.&amp;nbsp; This shorthand is most useful when we have more than three dimensions in our schema.&amp;nbsp; For example, suppose we add a month column to our sales table:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE TABLE Sales (EmpId INT, Month INT, Yr INT, Sales MONEY)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Now, suppose we want to compute sales for each employee by month and year, by year, and total.&amp;nbsp; We could write out all of the GROUPING SETS explicitly:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Month, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS((EmpId, &lt;B&gt;Yr, Month&lt;/B&gt;), (EmpId, &lt;B&gt;Yr&lt;/B&gt;), (EmpId))&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Or we can use ROLLUP to simplify the query:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Month, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY GROUPING SETS(&lt;STRONG&gt;(&lt;/STRONG&gt;EmpId, &lt;STRONG&gt;ROLLUP(Yr, Month))&lt;/STRONG&gt;)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Note that once again the correct use of parenthesis is critical.&amp;nbsp; If we omit one set of parenthesis from the above query, the meaning changes significantly and we end up separately aggregating by employee and then computing the year and month ROLLUP for all employees.&lt;/P&gt;The new GROUPING SETS syntax is available in all of &lt;A title="Microsoft SQL Server 2008" href="http://www.microsoft.com/sql/2008/default.mspx" mce_href="http://www.microsoft.com/sql/2008/default.mspx"&gt;SQL Server 2008&lt;/A&gt; Community Technology Preview (CTP) releases.&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=5402551" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Aggregation/default.aspx">Aggregation</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Rollup+and+Cube/default.aspx">Rollup and Cube</category></item><item><title>Aggregation WITH CUBE</title><link>http://blogs.msdn.com/craigfr/archive/2007/09/27/aggregation-with-cube.aspx</link><pubDate>Thu, 27 Sep 2007 21:30:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:5172072</guid><dc:creator>craigfr</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/5172072.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=5172072</wfw:commentRss><description>&lt;P&gt;In my last post, I wrote about how&amp;nbsp;&lt;A title="Aggregation WITH ROLLUP" href="http://blogs.msdn.com/craigfr/archive/2007/09/21/aggregation-with-rollup.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2007/09/21/aggregation-with-rollup.aspx"&gt;aggregation WITH ROLLUP&lt;/A&gt; works.&amp;nbsp; In this post, I will discuss how aggregation WITH CUBE works.&amp;nbsp; Like the WITH ROLLUP clause, the WITH CUBE clause permits us to compute multiple "levels" of aggregation in a single statement.&amp;nbsp; To understand the difference between these two clauses, let's look at an example.&amp;nbsp; We'll use the same fictitious sales data from last week's example.&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE TABLE Sales (EmpId INT, Yr INT, Sales MONEY)&lt;BR&gt;INSERT Sales VALUES(1, 2005, 12000)&lt;BR&gt;INSERT Sales VALUES(1, 2006, 18000)&lt;BR&gt;INSERT Sales VALUES(1, 2007, 25000)&lt;BR&gt;INSERT Sales VALUES(2, 2005, 15000)&lt;BR&gt;INSERT Sales VALUES(2, 2006, 6000)&lt;BR&gt;INSERT Sales VALUES(3, 2006, 20000)&lt;BR&gt;INSERT Sales VALUES(3, 2007, 24000)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Consider the following query from last week:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY EmpId, Yr WITH ROLLUP&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;It will be easier to see what is happening if we pivot the sales data:&lt;/P&gt;
&lt;TABLE class="" cellSpacing=0 cellPadding=0 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=213 colSpan=2 rowSpan=2&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=426 colSpan=4&gt;
&lt;P align=center&gt;&lt;B&gt;Yr&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;2005&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;2006&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;2007&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;ALL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" width=106 rowSpan=4&gt;
&lt;P&gt;&lt;B&gt;EmpId&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;1&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;12000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;18000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;25000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;55000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;2&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;15000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;6000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;21000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;3&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;20000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;24000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;44000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;ALL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;120000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P mce_keep="true"&gt;The table clearly shows that the WITH ROLLUP clause computes the total for each employee for all years and the grand total for all employees and all years.&amp;nbsp; The query does not compute the totals for each year for all employees.&amp;nbsp; Moreover, the order of the columns in the GROUP BY clause determines in which order the data is totaled.&lt;/P&gt;
&lt;P&gt;Now let's repeat the same query but replace the WITH ROLLUP clause with a WITH CUBE clause:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY EmpId, Yr WITH CUBE&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;This query computes all possible sub-totals and totals:&lt;/P&gt;
&lt;TABLE class="" cellSpacing=0 cellPadding=0 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=213 colSpan=2 rowSpan=2&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=426 colSpan=4&gt;
&lt;P align=center&gt;&lt;B&gt;Yr&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;2005&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;2006&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;2007&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;ALL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" width=106 rowSpan=4&gt;
&lt;P&gt;&lt;B&gt;EmpId&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;1&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;12000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;18000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;25000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;55000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;2&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;15000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;6000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;21000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;3&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;20000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;24000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;44000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;ALL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;&lt;I&gt;27000.00&lt;/I&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;&lt;I&gt;44000.00&lt;/I&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;&lt;I&gt;49000.00&lt;/I&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=106&gt;
&lt;P&gt;&lt;B&gt;120000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P mce_keep="true"&gt;Because the WITH CUBE clause causes the query to compute all possible totals, the order of the columns in the GROUP BY clause does not matter.&amp;nbsp; Of course, by default, SQL Server does not pivot the results of either of the above queries.&amp;nbsp; Here is the actual output from the WITH CUBE query:&lt;/P&gt;&lt;PRE&gt;EmpId       Yr          Sales
----------- ----------- ---------------------
1           2005        12000.00
1           2006        18000.00
1           2007        25000.00
1           NULL        55000.00
2           2005        15000.00
2           2006        6000.00
2           NULL        21000.00
3           2006        20000.00
3           2007        24000.00
3           NULL        44000.00
NULL        NULL        120000.00
NULL        2005        27000.00
NULL        2006        44000.00
NULL        2007        49000.00&lt;/PRE&gt;
&lt;P&gt;Next, let's look at the query plan for the WITH CUBE query:&lt;/P&gt;
&lt;P&gt;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [Expr1005]=(0) THEN NULL ELSE [Expr1006] END))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Concatenation&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(GROUP BY:([Sales].[EmpId], [Sales].[Yr]) DEFINE:([Expr1005]=SUM([Expr1007]), [Expr1006]=SUM([Expr1008])))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Sort(ORDER BY:([Sales].[EmpId] ASC, [Sales].[Yr] ASC))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Spool&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(GROUP BY:([Sales].[Yr], [Sales].[EmpId]) DEFINE:([Expr1007]=COUNT_BIG([Sales].[Sales]), [Expr1008]=SUM([Sales].[Sales])))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Sort(ORDER BY:([Sales].[Yr] ASC, [Sales].[EmpId] ASC))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([Sales]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1012]=NULL))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(GROUP BY:([Sales].[Yr]) DEFINE:([Expr1005]=SUM([Expr1007]), [Expr1006]=SUM([Expr1008])))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Spool&lt;/P&gt;
&lt;P&gt;This plan consists of two parts.&amp;nbsp; SQL Server has effectively rewritten our query as follows:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY EmpId, Yr WITH ROLLUP&lt;BR&gt;UNION ALL&lt;BR&gt;SELECT NULL, Yr, SUM(Sales)&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY Yr&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;The first part of the plan computes the result for the WITH ROLLUP query above.&amp;nbsp; I described how this query plan works in last week's post.&amp;nbsp; The second part of this plan computes the missing year sub-totals yielding the entire CUBE result.&amp;nbsp; Note that this plan employs a common sub-expression spool.&amp;nbsp; As I discussed in &lt;A title="Optimized Non-clustered Index Maintenance in Per-Index Plans" href="http://blogs.msdn.com/craigfr/archive/2007/08/22/optimized-non-clustered-index-maintenance-in-per-index-plans.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2007/08/22/optimized-non-clustered-index-maintenance-in-per-index-plans.aspx"&gt;this post&lt;/A&gt;, a common sub-expression spool copies its input rows into a worktable and then reads and returns the rows from the worktable multiple times - in this case twice.&amp;nbsp; The spool is meant to improve performance although, in this example, it has little impact since the server could just as easily have re-read the original Sales table. &amp;nbsp;However, if the input to the aggregation was more complex and cost more to evaluate, the spool would help.&lt;/P&gt;
&lt;P&gt;If we use the WITH CUBE clause when aggregating on more than two columns, SQL Server simply generates increasingly complex plans with additional inputs to the concatentation operator.&amp;nbsp; As with the simple two column example, the idea is to compute the whole CUBE by computing all of the individual ROLLUPs that compose it.&lt;/P&gt;
&lt;P&gt;Finally, we can actually combine WITH CUBE and PIVOT to generate the above table in a single simple statement.&amp;nbsp; (I actually proposed a variation of this query in an answer to a reader's &lt;A title="The PIVOT Operator" href="http://blogs.msdn.com/craigfr/archive/2007/07/03/the-pivot-operator.aspx#comments" mce_href="http://blogs.msdn.com/craigfr/archive/2007/07/03/the-pivot-operator.aspx#comments"&gt;comment&lt;/A&gt; on my post about the PIVOT operator but I like this solution better.)&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, [2005], [2006], [2007], [ALL]&lt;BR&gt;FROM&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CASE WHEN GROUPING(EmpId) = 0&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; THEN CAST (EmpId AS CHAR(7))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ELSE 'ALL'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; END AS EmpId,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CASE WHEN GROUPING(Yr) = 0&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; THEN CAST (Yr AS CHAR(7))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ELSE 'ALL'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; END AS Yr,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM(Sales) AS Sales&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM Sales&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; GROUP BY EmpId, Yr WITH CUBE&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) AS s&lt;BR&gt;PIVOT (SUM(Sales) FOR Yr IN ([2005], [2006], [2007], [ALL])) AS p&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Here is the output from this query:&lt;/P&gt;&lt;PRE&gt;EmpId   2005                  2006                  2007                  ALL
------- --------------------- --------------------- --------------------- ---------------------
1       12000.00              18000.00              25000.00              55000.00
2       15000.00              6000.00               NULL                  21000.00
3       NULL                  20000.00              24000.00              44000.00
ALL     27000.00              44000.00              49000.00              120000.00&lt;/PRE&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=5172072" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Aggregation/default.aspx">Aggregation</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Rollup+and+Cube/default.aspx">Rollup and Cube</category></item><item><title>Aggregation WITH ROLLUP</title><link>http://blogs.msdn.com/craigfr/archive/2007/09/21/aggregation-with-rollup.aspx</link><pubDate>Fri, 21 Sep 2007 20:36:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:5036687</guid><dc:creator>craigfr</dc:creator><slash:comments>3</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/5036687.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=5036687</wfw:commentRss><description>&lt;P mce_keep="true"&gt;In this post, I'm going to discuss how aggregation WITH ROLLUP works.&amp;nbsp; The WITH ROLLUP clause permits us to execute multiple "levels" of aggregation in a single statement.&amp;nbsp; For example, suppose we have the following fictitious sales data.&amp;nbsp; (This is the same data that I used for my series of posts on the PIVOT operator.)&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE TABLE Sales (EmpId INT, Yr INT, Sales MONEY)&lt;BR&gt;INSERT Sales VALUES(1, 2005, 12000)&lt;BR&gt;INSERT Sales VALUES(1, 2006, 18000)&lt;BR&gt;INSERT Sales VALUES(1, 2007, 25000)&lt;BR&gt;INSERT Sales VALUES(2, 2005, 15000)&lt;BR&gt;INSERT Sales VALUES(2, 2006, 6000)&lt;BR&gt;INSERT Sales VALUES(3, 2006, 20000)&lt;BR&gt;INSERT Sales VALUES(3, 2007, 24000)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;We can write a simple aggregation query to compute the total sales by year:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY Yr&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;As expected, this query returns three rows - one for each year:&lt;/P&gt;&lt;PRE&gt;Yr          Sales
----------- ---------------------
2005        27000.00
2006        44000.00
2007        49000.00&lt;/PRE&gt;
&lt;P&gt;The query plan is a simple &lt;A title="Stream Aggregate" href="http://blogs.msdn.com/craigfr/archive/2006/09/13/752728.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2006/09/13/752728.aspx"&gt;stream aggregate&lt;/A&gt;:&lt;/P&gt;
&lt;P&gt;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [Expr1010]=(0) THEN NULL ELSE [Expr1011] END))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(GROUP BY:([Sales].[Yr]) DEFINE:([Expr1010]=COUNT_BIG([Sales].[Sales]), [Expr1011]=SUM([Sales].[Sales])))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Sort(ORDER BY:([Sales].[Yr] ASC))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([Sales]))&lt;/P&gt;
&lt;P&gt;Now suppose that we want to compute not just the sale by year but the total sales as well.&amp;nbsp; We could write a UNION ALL query:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY Yr&lt;BR&gt;UNION ALL&lt;BR&gt;SELECT NULL, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;This query works and does give the correct result:&lt;/P&gt;&lt;PRE&gt;Yr          Sales
----------- ---------------------
2005        27000.00
2006        44000.00
2007        49000.00
NULL        120000.00&lt;/PRE&gt;
&lt;P&gt;However, the query plan performs two scans and two aggregations (one to compute the sales by year and one to compute the total sales):&lt;/P&gt;
&lt;P&gt;&amp;nbsp; |--Concatenation&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [Expr1023]=(0) THEN NULL ELSE [Expr1024] END))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(GROUP BY:([Sales].[Yr]) DEFINE:([Expr1023]=COUNT_BIG([Sales].[Sales]), [Expr1024]=SUM([Sales].[Sales])))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Sort(ORDER BY:([Sales].[Yr] ASC))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([Sales]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1010]=NULL))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1009]=CASE WHEN [Expr1025]=(0) THEN NULL ELSE [Expr1026] END))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(DEFINE:([Expr1025]=COUNT_BIG([Sales].[Sales]), [Expr1026]=SUM([Sales].[Sales])))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([Sales]))&lt;/P&gt;
&lt;P&gt;We can do better by adding a WITH ROLLUP clause to the original query:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY Yr WITH ROLLUP&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;This query is simpler to write and uses a more efficient query plan with only a single scan:&lt;/P&gt;
&lt;P&gt;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [Expr1005]=(0) THEN NULL ELSE [Expr1006] END))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(GROUP BY:([Sales].[Yr]) DEFINE:([Expr1005]=SUM([Expr1007]), [Expr1006]=SUM([Expr1008])))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Stream Aggregate(GROUP BY:([Sales].[Yr]) DEFINE:([Expr1007]=COUNT_BIG([Sales].[Sales]), [Expr1008]=SUM([Sales].[Sales])))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Sort(ORDER BY:([Sales].[Yr] ASC))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([Sales]))&lt;/P&gt;
&lt;P&gt;The bottom stream aggregate in this query plan is the same as the stream aggregate in the original non-ROLLUP query.&amp;nbsp; This aggregation is a normal aggregation and, as such, it can be implemented using a stream aggregate (as in this example) or a &lt;A title="Hash Aggregate" href="http://blogs.msdn.com/craigfr/archive/2006/09/20/hash-aggregate.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2006/09/20/hash-aggregate.aspx"&gt;hash aggregate&lt;/A&gt; (try adding an OPTION (HASH GROUP) clause to the above query).&amp;nbsp; It can also be parallelized.&lt;/P&gt;
&lt;P&gt;The top stream aggregate is a special aggregate that computes the ROLLUP.&amp;nbsp; (Unfortunately, in SQL Server 2005 there is no way to discern from the query plan that this aggregate implements a ROLLUP.&amp;nbsp; This issue will be fixed in SQL Server 2008 graphical and XML plans.)&amp;nbsp; A ROLLUP aggregate is always implemented using stream aggregate and cannot be parallelized.&amp;nbsp; In this simple example, the ROLLUP stream aggregate merely returns each pre-aggregated input row while maintaining a running total of the Sales column.&amp;nbsp; After outputting the final input row, the aggregate also returns one additional row with the final sum.&amp;nbsp; Since SQL lacks a concept of an ALL value, the Yr column is set to NULL for this final row.&amp;nbsp; If NULL is valid value for Yr, we can identify the ROLLUP row using the GROUPING(Yr) construct.&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CASE WHEN GROUPING(Yr) = 0&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; THEN CAST (Yr AS CHAR(5))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ELSE 'ALL'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; END AS Yr,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY Yr WITH ROLLUP&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;Yr    Sales
----- ---------------------
2005  27000.00
2006  44000.00
2007  49000.00
ALL   120000.00&lt;/PRE&gt;
&lt;P&gt;We can also compute multiple ROLLUP levels in a single query.&amp;nbsp; For example, suppose that we want to compute the sales first by employee and then for each employee by year:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT EmpId, Yr, SUM(Sales) AS Sales&lt;BR&gt;FROM Sales&lt;BR&gt;GROUP BY EmpId, Yr WITH ROLLUP&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;EmpId       Yr          Sales
----------- ----------- ---------------------
1           2005        12000.00
1           2006        18000.00
1           2007        25000.00
1           NULL        55000.00
2           2005        15000.00
2           2006        6000.00
2           NULL        21000.00
3           2006        20000.00
3           2007        24000.00
3           NULL        44000.00
NULL        NULL        120000.00&lt;/PRE&gt;
&lt;P&gt;There are a couple of points worth noting about this query.&amp;nbsp; First, since the combination of the EmpId and Yr columns is unique, in the absence of the WITH ROLLUP clause, this query would just return the original data.&amp;nbsp; However, with the WITH ROLLUP clause the query produces a useful result.&amp;nbsp; Second, the order of the columns in the GROUP BY clause is relevant with the WITH ROLLUP clause.&amp;nbsp; To see why simply try the same query but reverse the EmpId and Yr columns.&amp;nbsp; Instead of computing the sales first by employee it will compute the sales first by year.&lt;/P&gt;
&lt;P&gt;The query plan for this query is identical to the query plan for the prior query except that it groups on both the EmpId and Yr columns instead of on just the EmpId column.&amp;nbsp; Like the prior query plan, this query plan includes two stream aggregates: the bottom one which is a normal stream aggregate and the top one which computes the ROLLUP.&amp;nbsp; This ROLLUP stream aggregate actually computes two running totals: one which computes the total sales for an employee for all years and one which compute the total sales for all employees and all years.&amp;nbsp; This table shows how the ROLLUP computation proceeds:&lt;/P&gt;
&lt;P&gt;
&lt;TABLE class="" cellSpacing=0 cellPadding=0 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;EmpId&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;Yr&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;SUM(Sales) BY EmpId, Yr&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;SUM(Sales) BY EmpId&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;SUM(Sales)&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;1&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2005&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;12000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;12000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;12000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;1&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2006&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;18000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;30000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;30000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;1&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2007&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;25000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;55000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;55000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;1&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;NULL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;55000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;55000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2005&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;15000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;15000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;70000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2006&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;6000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;21000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;76000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;2&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;NULL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;21000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;76000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;3&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2006&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;20000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;20000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;96000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;3&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;2007&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;24000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;44000.00&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;120000.00&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;3&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;NULL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;44000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;120000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;NULL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;NULL&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD class="" vAlign=top width=96&gt;
&lt;P&gt;&lt;B&gt;120000.00&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;In my next post, I'll take a look at the WITH CUBE clause.&amp;nbsp; I'll discuss how it differs from WITH ROLLUP both in terms of function and in terms of its implementation.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=5036687" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Aggregation/default.aspx">Aggregation</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Rollup+and+Cube/default.aspx">Rollup and Cube</category></item></channel></rss>