MySQL provides us with a handy WITH ROLLUP
modifier that allows us to get totals and subtotals when using the GROUP BY
clause.
Below is an example of how the MySQL WITH ROLLUP
modifier works.
Example
Suppose we have a table called City
and we run a simple SELECT
query to retrieve some of its data:
SELECT
CountryCode,
District,
Name,
Population
FROM City
WHERE CountryCode IN ('AUS', 'NZL');
Result:
+-------------+-----------------+---------------+------------+ | CountryCode | District | Name | Population | +-------------+-----------------+---------------+------------+ | AUS | New South Wales | Sydney | 3276207 | | AUS | Victoria | Melbourne | 2865329 | | AUS | Queensland | Brisbane | 1291117 | | AUS | West Australia | Perth | 1096829 | | AUS | South Australia | Adelaide | 978100 | | AUS | Capital Region | Canberra | 322723 | | AUS | Queensland | Gold Coast | 311932 | | AUS | New South Wales | Newcastle | 270324 | | AUS | New South Wales | Central Coast | 227657 | | AUS | New South Wales | Wollongong | 219761 | | AUS | Tasmania | Hobart | 126118 | | AUS | Victoria | Geelong | 125382 | | AUS | Queensland | Townsville | 109914 | | AUS | Queensland | Cairns | 92273 | | NZL | Auckland | Auckland | 381800 | | NZL | Canterbury | Christchurch | 324200 | | NZL | Auckland | Manukau | 281800 | | NZL | Auckland | North Shore | 187700 | | NZL | Auckland | Waitakere | 170600 | | NZL | Wellington | Wellington | 166700 | | NZL | Dunedin | Dunedin | 119600 | | NZL | Hamilton | Hamilton | 117100 | | NZL | Wellington | Lower Hutt | 98100 | +-------------+-----------------+---------------+------------+
Let’s say we want to group these results by the CountryCode
, District
, and then Name
columns, and then provide a sum of the population of each country, district, and city within those results.
First, here’s a query without the WITH ROLLUP
modifier:
SELECT
CountryCode,
District,
Name,
SUM(Population) AS Population
FROM City
WHERE CountryCode IN ('AUS', 'NZL')
GROUP BY CountryCode, District, Name;
Result:
+-------------+-----------------+---------------+------------+ | CountryCode | District | Name | Population | +-------------+-----------------+---------------+------------+ | AUS | New South Wales | Sydney | 3276207 | | AUS | Victoria | Melbourne | 2865329 | | AUS | Queensland | Brisbane | 1291117 | | AUS | West Australia | Perth | 1096829 | | AUS | South Australia | Adelaide | 978100 | | AUS | Capital Region | Canberra | 322723 | | AUS | Queensland | Gold Coast | 311932 | | AUS | New South Wales | Newcastle | 270324 | | AUS | New South Wales | Central Coast | 227657 | | AUS | New South Wales | Wollongong | 219761 | | AUS | Tasmania | Hobart | 126118 | | AUS | Victoria | Geelong | 125382 | | AUS | Queensland | Townsville | 109914 | | AUS | Queensland | Cairns | 92273 | | NZL | Auckland | Auckland | 381800 | | NZL | Canterbury | Christchurch | 324200 | | NZL | Auckland | Manukau | 281800 | | NZL | Auckland | North Shore | 187700 | | NZL | Auckland | Waitakere | 170600 | | NZL | Wellington | Wellington | 166700 | | NZL | Dunedin | Dunedin | 119600 | | NZL | Hamilton | Hamilton | 117100 | | NZL | Wellington | Lower Hutt | 98100 | +-------------+-----------------+---------------+------------+
As expected it simply returned the population of each city in the results, but not for the countries or districts.
Now let’s add the WITH ROLLUP
modifier to get the population of the countries and districts:
SELECT
CountryCode,
District,
Name,
SUM(Population) AS Population
FROM City
WHERE CountryCode IN ('AUS', 'NZL')
GROUP BY CountryCode, District, Name WITH ROLLUP;
Result:
+-------------+-----------------+---------------+------------+ | CountryCode | District | Name | Population | +-------------+-----------------+---------------+------------+ | AUS | Capital Region | Canberra | 322723 | | AUS | Capital Region | NULL | 322723 | | AUS | New South Wales | Central Coast | 227657 | | AUS | New South Wales | Newcastle | 270324 | | AUS | New South Wales | Sydney | 3276207 | | AUS | New South Wales | Wollongong | 219761 | | AUS | New South Wales | NULL | 3993949 | | AUS | Queensland | Brisbane | 1291117 | | AUS | Queensland | Cairns | 92273 | | AUS | Queensland | Gold Coast | 311932 | | AUS | Queensland | Townsville | 109914 | | AUS | Queensland | NULL | 1805236 | | AUS | South Australia | Adelaide | 978100 | | AUS | South Australia | NULL | 978100 | | AUS | Tasmania | Hobart | 126118 | | AUS | Tasmania | NULL | 126118 | | AUS | Victoria | Geelong | 125382 | | AUS | Victoria | Melbourne | 2865329 | | AUS | Victoria | NULL | 2990711 | | AUS | West Australia | Perth | 1096829 | | AUS | West Australia | NULL | 1096829 | | AUS | NULL | NULL | 11313666 | | NZL | Auckland | Auckland | 381800 | | NZL | Auckland | Manukau | 281800 | | NZL | Auckland | North Shore | 187700 | | NZL | Auckland | Waitakere | 170600 | | NZL | Auckland | NULL | 1021900 | | NZL | Canterbury | Christchurch | 324200 | | NZL | Canterbury | NULL | 324200 | | NZL | Dunedin | Dunedin | 119600 | | NZL | Dunedin | NULL | 119600 | | NZL | Hamilton | Hamilton | 117100 | | NZL | Hamilton | NULL | 117100 | | NZL | Wellington | Lower Hutt | 98100 | | NZL | Wellington | Wellington | 166700 | | NZL | Wellington | NULL | 264800 | | NZL | NULL | NULL | 1847600 | | NULL | NULL | NULL | 13161266 | +-------------+-----------------+---------------+------------+
We can see that by adding WITH ROLLUP
to our query, MySQL has calculated the totals for each city, district, for each country, and for the whole result set.
I should point out that this is a sample database, and it’s no way reflective of the actual population in those countries.
Change the NULL
Headings
One problem with the default results of WITH ROLLUP
is that it presents NULL
as its label/heading. This could potentially make it difficult for us to quickly scan and understand the results. This would be even more pronounced if the underlying data also contained NULL
values.
Fortunately, we can use the following technique to overcome this issue:
SELECT
IF(GROUPING(CountryCode), 'All Countries', CountryCode) AS CountryCode,
IF(GROUPING(District), 'All Districts', District) AS District,
IF(GROUPING(Name), 'All Cities', Name) As CityName,
SUM(Population) AS Population
FROM City
WHERE CountryCode IN ('AUS', 'NZL')
GROUP BY CountryCode, District, Name WITH ROLLUP;
Result:
+---------------+-----------------+---------------+------------+ | CountryCode | District | CityName | Population | +---------------+-----------------+---------------+------------+ | AUS | Capital Region | Canberra | 322723 | | AUS | Capital Region | All Cities | 322723 | | AUS | New South Wales | Central Coast | 227657 | | AUS | New South Wales | Newcastle | 270324 | | AUS | New South Wales | Sydney | 3276207 | | AUS | New South Wales | Wollongong | 219761 | | AUS | New South Wales | All Cities | 3993949 | | AUS | Queensland | Brisbane | 1291117 | | AUS | Queensland | Cairns | 92273 | | AUS | Queensland | Gold Coast | 311932 | | AUS | Queensland | Townsville | 109914 | | AUS | Queensland | All Cities | 1805236 | | AUS | South Australia | Adelaide | 978100 | | AUS | South Australia | All Cities | 978100 | | AUS | Tasmania | Hobart | 126118 | | AUS | Tasmania | All Cities | 126118 | | AUS | Victoria | Geelong | 125382 | | AUS | Victoria | Melbourne | 2865329 | | AUS | Victoria | All Cities | 2990711 | | AUS | West Australia | Perth | 1096829 | | AUS | West Australia | All Cities | 1096829 | | AUS | All Districts | All Cities | 11313666 | | NZL | Auckland | Auckland | 381800 | | NZL | Auckland | Manukau | 281800 | | NZL | Auckland | North Shore | 187700 | | NZL | Auckland | Waitakere | 170600 | | NZL | Auckland | All Cities | 1021900 | | NZL | Canterbury | Christchurch | 324200 | | NZL | Canterbury | All Cities | 324200 | | NZL | Dunedin | Dunedin | 119600 | | NZL | Dunedin | All Cities | 119600 | | NZL | Hamilton | Hamilton | 117100 | | NZL | Hamilton | All Cities | 117100 | | NZL | Wellington | Lower Hutt | 98100 | | NZL | Wellington | Wellington | 166700 | | NZL | Wellington | All Cities | 264800 | | NZL | All Districts | All Cities | 1847600 | | All Countries | All Districts | All Cities | 13161266 | +---------------+-----------------+---------------+------------+
Here, we used MySQL’s GROUPING()
function along with the IF()
function to present user-friendly text as the headings for the rows that are generated by the WITH ROLLUP
modifier. We presented All Countries
, All Districts
, or All Cities
, depending on which column we were dealing with. This makes the result set more meaningful.
The GROUPING()
function returns a 1
or 0
, depending on whether or not the NULL
value is due to the row representing a super-aggregate value. Specifically, it returns 1
if the expression value in the current row is a NULL
representing a super-aggregate value. Otherwise, it returns 0
, which indicates that the expression value is a NULL
for a regular result row or is not NULL
.
More Information
See the MySQL documentation for more information about the WITH ROLLUP
modifier.