select facid, extract(month from starttime) as month, sum(slots) as slots from cd.bookings where starttime >= '2012-01-01' and starttime < '2013-01-01' group by rollup(facid, month) order by facid, month;
When we are doing data analysis, we sometimes want to perform multiple levels of aggregation to allow ourselves to 'zoom' in and out to different depths. In this case, we might be looking at each facility's overall usage, but then want to dive in to see how they've performed on a per-month basis. Using the SQL we know so far, it's quite cumbersome to produce a single query that does what we want - we effectively have to resort to concatenating multiple queries using UNION ALL:
select facid, extract(month from starttime) as month, sum(slots) as slots from cd.bookings where starttime >= '2012-01-01' and starttime < '2013-01-01' group by facid, month union all select facid, null, sum(slots) as slots from cd.bookings where starttime >= '2012-01-01' and starttime < '2013-01-01' group by facid union all select null, null, sum(slots) as slots from cd.bookings where starttime >= '2012-01-01' and starttime < '2013-01-01' order by facid, month;
As you can see, each subquery performs a different level of aggregation, and we just combine the results. We can clean this up a lot by factoring out commonalities using a CTE:
with bookings as ( select facid, extract(month from starttime) as month, slots from cd.bookings where starttime >= '2012-01-01' and starttime < '2013-01-01' ) select facid, month, sum(slots) from bookings group by facid, month union all select facid, null, sum(slots) from bookings group by facid union all select null, null, sum(slots) from bookings order by facid, month;
This version is not excessively hard on the eyes, but it becomes cumbersome as the number of aggregation columns increases. Fortunately, PostgreSQL 9.5 introduced support for the ROLLUP operator, which we've used to simplify our accepted answer.
ROLLUP produces a hierarchy of aggregations in the order passed into it: for example, ROLLUP(facid, month) outputs aggregations on (facid, month), (facid), and (). If we wanted an aggregation of all facilities for a month (instead of all months for a facility) we'd have to reverse the order, using ROLLUP(month, facid). Alternatively, if we instead want all possible permutations of the columns we pass in, we can use CUBE rather than ROLLUP. This will produce (facid, month), (month), (facid), and ().
ROLLUP and CUBE are special cases of GROUPING SETS. GROUPING SETS allow you to specify the exact aggregation permutations you want: you could, for example, ask for just (facid, month) and (facid), skipping the top-level aggregation.