Calculate a Grand Total using a Window Function in SQL

When we use an OVER() clause to create a window function in SQL, we often use a PARTITION BY clause to partition the results. This can be handy if we want to do stuff like calculate subtotals.

But we can also use an empty OVER clause to calculate a grand total.

We might want to do this if we’re already using another OVER clause to calculate subtotals, but we also want a column to provide the grand total.

Example

Here’s an example of using two window functions; one to calculate subtotals, and another to calculate the grand total.

We’ll use the world sample database, which contains data such as countries, districts, cities, and city population.

This example was done in MySQL, but it should work in most major RDBMSs. That said, you should check your RDBMS’s documentation for window functions in case it uses a different syntax.

Sample Data

We have a table called City with the following data:

SELECT * FROM City
WHERE CountryCode = 'AUS';

Result:

+-----+---------------+-------------+-----------------+------------+
| ID  | Name          | CountryCode | District        | Population |
+-----+---------------+-------------+-----------------+------------+
| 130 | Sydney        | AUS         | New South Wales |    3276207 |
| 131 | Melbourne     | AUS         | Victoria        |    2865329 |
| 132 | Brisbane      | AUS         | Queensland      |    1291117 |
| 133 | Perth         | AUS         | West Australia  |    1096829 |
| 134 | Adelaide      | AUS         | South Australia |     978100 |
| 135 | Canberra      | AUS         | Capital Region  |     322723 |
| 136 | Gold Coast    | AUS         | Queensland      |     311932 |
| 137 | Newcastle     | AUS         | New South Wales |     270324 |
| 138 | Central Coast | AUS         | New South Wales |     227657 |
| 139 | Wollongong    | AUS         | New South Wales |     219761 |
| 140 | Hobart        | AUS         | Tasmania        |     126118 |
| 141 | Geelong       | AUS         | Victoria        |     125382 |
| 142 | Townsville    | AUS         | Queensland      |     109914 |
| 143 | Cairns        | AUS         | Queensland      |      92273 |
+-----+---------------+-------------+-----------------+------------+
14 rows in set (0.00 sec)

This table contains city names and their populations, as well as the district and country (country code) that each city belongs to.

Note that these populations don’t reflect the actual populations in those cities. Nor does it represent all cities in the respective districts. This is just a sample database with sample data.

Calculate the Grand Total (and Subtotals)

Here’s how we can use a window function to calculate a grand total for the above query, as well as another one to calculate subtotals:

SELECT
    District,
    Name AS "City Name",
    Population AS "City Pop",
    SUM(Population)
        OVER (
            PARTITION BY District
            ORDER BY Population
            ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ) AS "District Pop",
    SUM(Population)
        OVER (
            ) AS "Country Pop"
FROM City
WHERE CountryCode = 'AUS';

Result:

+-----------------+---------------+----------+--------------+-------------+
| District        | City Name     | City Pop | District Pop | Country Pop |
+-----------------+---------------+----------+--------------+-------------+
| Capital Region  | Canberra      |   322723 |       322723 |    11313666 |
| New South Wales | Wollongong    |   219761 |      3993949 |    11313666 |
| New South Wales | Central Coast |   227657 |      3993949 |    11313666 |
| New South Wales | Newcastle     |   270324 |      3993949 |    11313666 |
| New South Wales | Sydney        |  3276207 |      3993949 |    11313666 |
| Queensland      | Cairns        |    92273 |      1805236 |    11313666 |
| Queensland      | Townsville    |   109914 |      1805236 |    11313666 |
| Queensland      | Gold Coast    |   311932 |      1805236 |    11313666 |
| Queensland      | Brisbane      |  1291117 |      1805236 |    11313666 |
| South Australia | Adelaide      |   978100 |       978100 |    11313666 |
| Tasmania        | Hobart        |   126118 |       126118 |    11313666 |
| Victoria        | Geelong       |   125382 |      2990711 |    11313666 |
| Victoria        | Melbourne     |  2865329 |      2990711 |    11313666 |
| West Australia  | Perth         |  1096829 |      1096829 |    11313666 |
+-----------------+---------------+----------+--------------+-------------+
14 rows in set (0.00 sec)

In this case, the grand total is in the Country Pop column. The subtotals are in the District Pop column.

Both columns use data from the Population column, which holds the population of each city. We used the SUM() function to calculate the subtotals and grand total, based on the values in this column.

When we calculate the grand total, we get the same value across all rows. This is the way window functions work. We get this because of the granularity of the query. We want to see each city in its own row, and so therefore subtotals and grand total values will be repeated whenever there’s more than one city within a partition. The District Pop column is partitioned by district. We didn’t specify a partition for the Country Pop column and so it calculates its value based on the whole result set.

However, that’s not to say that an empty OVER clause is the only way to do it. We could have explicitly partitioned the column by the CountryCode column to achieve the same result:

SELECT
    District,
    Name AS "City Name",
    Population AS "City Pop",
    SUM(Population)
        OVER (
            PARTITION BY District
            ORDER BY Population
            ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ) AS "District Pop",
    SUM(Population)
        OVER (
            PARTITION BY CountryCode
            ) AS "Country Pop"
FROM City
WHERE CountryCode = 'AUS';

Result:

+-----------------+---------------+----------+--------------+-------------+
| District        | City Name     | City Pop | District Pop | Country Pop |
+-----------------+---------------+----------+--------------+-------------+
| Capital Region  | Canberra      |   322723 |       322723 |    11313666 |
| New South Wales | Wollongong    |   219761 |      3993949 |    11313666 |
| New South Wales | Central Coast |   227657 |      3993949 |    11313666 |
| New South Wales | Newcastle     |   270324 |      3993949 |    11313666 |
| New South Wales | Sydney        |  3276207 |      3993949 |    11313666 |
| Queensland      | Cairns        |    92273 |      1805236 |    11313666 |
| Queensland      | Townsville    |   109914 |      1805236 |    11313666 |
| Queensland      | Gold Coast    |   311932 |      1805236 |    11313666 |
| Queensland      | Brisbane      |  1291117 |      1805236 |    11313666 |
| South Australia | Adelaide      |   978100 |       978100 |    11313666 |
| Tasmania        | Hobart        |   126118 |       126118 |    11313666 |
| Victoria        | Geelong       |   125382 |      2990711 |    11313666 |
| Victoria        | Melbourne     |  2865329 |      2990711 |    11313666 |
| West Australia  | Perth         |  1096829 |      1096829 |    11313666 |
+-----------------+---------------+----------+--------------+-------------+
14 rows in set (0.00 sec)

An Alternative

Some RDBMSs allow a WITH ROLLUP modifier to be attached to the GROUP BY clause clause. This provides us with an alternative way to get grand totals and subtotals in our result set. See How to Add Totals and Subtotals When Using GROUP BY in MySQL for examples.