SkillByAIOpen interactive version →

Lesson 9 / 25

Aggregation With GROUP BY and HAVING

Summaries per group.

WHERE filters rows, HAVING filters groups

GROUP BY collapses rows into groups and aggregate functions (count, sum, avg, min, max, string_agg) summarise each group. WHERE filters rows before grouping; HAVING filters groups after aggregation. Every selected column must be grouped or aggregated. count(DISTINCT ...) avoids double counting when joins multiply rows.

Revenue by city, excluding cancelled orders, run

I ran this with psql against PostgreSQL 16.2 (a local server started with the pgserver Python package), on a fresh database loaded with the shop schema from the first section. Cancelled orders are removed with WHERE, revenue is summed per city, and HAVING keeps cities above 1,000: Pune has 3 orders worth 4,556 and Delhi 1 order worth 1,200.

SELECT c.city,
       count(DISTINCT o.id)          AS orders,
       sum(oi.qty * p.price)         AS revenue
FROM orders o
JOIN customers c   ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p    ON p.id = oi.product_id
WHERE o.status <> 'cancelled'
GROUP BY c.city
HAVING sum(oi.qty * p.price) > 1000
ORDER BY revenue DESC;

Output:

 city  | orders | revenue 
-------+--------+---------
 Pune  |      3 | 4556.00
 Delhi |      1 | 1200.00
(2 rows)

Filter early with WHERE

Conditions that do not depend on aggregates belong in WHERE, which reduces the rows to group.

Quick check: Where do you filter on sum(...) > 1000?

  • In ORDER BY
  • In WHERE
  • In HAVING
  • In LIMIT
Answer

In HAVING — HAVING works on aggregated groups.