When using the GROUP BY
clause in SQL Server, we can use the GROUPING SETS()
option to combine multiple GROUP BY
clauses into one GROUP BY
clause. When we do this, the results are the equivalent of doing a UNION ALL
of the specified groups.
Example
Suppose we have the following table:
SELECT * FROM Dogs;
Result:
DogName Activity Score ------- ----------- ----- Bruno Fetch Stick 43 Cooper Fetch Stick 43 Max Fetch Stick 91 Bruno Wag Tail 65 Cooper Wag Tail 51 Max Wag Tail 87 Bruno Keep Quiet 3 Cooper Keep Quiet null Max Keep Quiet 1
This is a table of dogs and their scores at performing various activities.
Here’s an example of a query that uses GROUP BY GROUPING SETS()
against that table:
SELECT
Activity,
DogName,
SUM(Score) AS "Score"
FROM Dogs
GROUP BY GROUPING SETS (
ROLLUP (Activity, DogName),
CUBE (Activity, DogName)
);
Result:
Activity DogName Score ----------- ------- ----- Fetch Stick Bruno 43 Keep Quiet Bruno 3 Wag Tail Bruno 65 null Bruno 111 Fetch Stick Cooper 43 Keep Quiet Cooper null Wag Tail Cooper 51 null Cooper 94 Fetch Stick Max 91 Keep Quiet Max 1 Wag Tail Max 87 null Max 179 null null 384 Fetch Stick Bruno 43 Fetch Stick Cooper 43 Fetch Stick Max 91 Fetch Stick null 177 Keep Quiet Bruno 3 Keep Quiet Cooper null Keep Quiet Max 1 Keep Quiet null 4 Wag Tail Bruno 65 Wag Tail Cooper 51 Wag Tail Max 87 Wag Tail null 203 null null 384 Fetch Stick null 177 Keep Quiet null 4 Wag Tail null 203
In this example I used the SUM()
function to calculate the total scores for each of the dogs, for each activity, and also a grand total for all activities.
I then used GROUPING SETS()
to combine a GROUP BY ROLLUP
and GROUP BY CUBE
into one.
The GROUP BY()
Option
There’s also a GROUP BY()
option that provides an easy way to get a grand total.
Here’s an example that uses this option:
SELECT
Activity,
SUM(Score) AS "Score"
FROM Dogs
GROUP BY GROUPING SETS (Activity, ());
Result:
Activity Score ----------- ----- Fetch Stick 177 Keep Quiet 4 Wag Tail 203 null 384
In this case I simply got the total scores for each activity, as well as a grand total for all activities.
Without the GROUP BY()
option, we would get the following:
SELECT
Activity,
SUM(Score) AS "Score"
FROM Dogs
GROUP BY Activity;
Result:
Activity Score ----------- ----- Fetch Stick 177 Keep Quiet 4 Wag Tail 203
So we would get the sub-totals, but not the grand total.
Changing the NULL
Values
We can see that the ROLLUP
and CUBE
operations result in NULL
being returned in certain columns for that row.
The NULL
returned by ROLLUP
, CUBE
, and GROUPING SETS
is a special use of NULL
. Microsoft’s documentation states:
The
NULL
returned as the result of aROLLUP
,CUBE
orGROUPING SETS
operation is a special use ofNULL
. This acts as a column placeholder in the result set and means all.
So in our case, whenever we see null
in either the Activity
column or the DogName
column, we know that it means “all”.
That being said, it’s also entirely possible that a column could contain NULL values that aren’t related to the ROLLUP
or CUBE
operations. In that case, it could be easy to get confused over which one was produced by ROLLUP
or CUBE
and which one is simply a NULL value in the underlying data.
Fortunately, we can use the following technique to help us distinguish between these different NULL
s:
SELECT
IIF(GROUPING(Activity) = 1, 'All Activities', Activity) AS "Activity",
IIF(GROUPING(DogName) = 1, 'All Dogs', DogName) AS "DogName",
SUM(Score) AS "Score"
FROM Dogs
GROUP BY GROUPING SETS (
ROLLUP (Activity, DogName),
CUBE (Activity, DogName)
);
Result:
Activity DogName Score -------------- -------- ----- Fetch Stick Bruno 43 Keep Quiet Bruno 3 Wag Tail Bruno 65 All Activities Bruno 111 Fetch Stick Cooper 43 Keep Quiet Cooper null Wag Tail Cooper 51 All Activities Cooper 94 Fetch Stick Max 91 Keep Quiet Max 1 Wag Tail Max 87 All Activities Max 179 All Activities All Dogs 384 Fetch Stick Bruno 43 Fetch Stick Cooper 43 Fetch Stick Max 91 Fetch Stick All Dogs 177 Keep Quiet Bruno 3 Keep Quiet Cooper null Keep Quiet Max 1 Keep Quiet All Dogs 4 Wag Tail Bruno 65 Wag Tail Cooper 51 Wag Tail Max 87 Wag Tail All Dogs 203 All Activities All Dogs 384 Fetch Stick All Dogs 177 Keep Quiet All Dogs 4 Wag Tail All Dogs 203
Here I used the GROUPING()
function in conjunction with the IIF()
function to replace any aggregated NULL
s with a string.
The GROUPING()
function returns 1
whenever the column expression is aggregated. I therefore passed it to the IIF()
function to present a different value, depending on whether it’s aggregated or not.
In this case, the Activity
column returns the string All Activities
if it’s aggregated, and just the column value if it’s not aggregated. The DogName
column returns All Dogs
if it’s aggregated, and just the column value if not.