Intermediate · SQL in depth · topic 22 of 54 · 20 min
GROUP BY and HAVING
Explanation
One answer per group
In the last module SUM(amount) gave you one number for the whole table. Often you want one number per something: sales per city, orders per customer, average marks per subject. GROUP BY does that. It puts rows with the same value into one group, then each aggregate (COUNT, SUM, AVG, MIN, MAX) runs once per group.
SELECT c.city, COUNT(*) AS orders, SUM(o.amount) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
GROUP BY c.city
ORDER BY total DESC;| city | orders | total |
|---|---|---|
| Mumbai | 3 | 4509 |
| Delhi | 2 | 1650 |
| Kochi | 2 | 1260 |
| Chennai | 1 | 799 |
Grouping by several columns
GROUP BY city, status makes one group for each different pair: (Mumbai, delivered), (Mumbai, pending) and so on. Use it for reports like "orders per month per status" or "deliveries per restaurant per status".
SELECT c.city, o.status, COUNT(*) AS orders
FROM orders o
JOIN customers c ON c.id = o.customer_id
GROUP BY c.city, o.status
ORDER BY c.city, o.status;WHERE or HAVING?
WHERE looks at single rows, before any grouping, so it cannot use SUM or COUNT. HAVING looks at whole groups, after grouping, so it can. A query can use both: WHERE throws away cancelled orders, then HAVING keeps only cities whose total is more than 1,000 rupees.
SELECT c.city, SUM(o.amount) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status <> 'cancelled'
GROUP BY c.city
HAVING SUM(o.amount) > 1000
ORDER BY total DESC;- FROM and JOIN: collect the rows
- WHERE: drop rows you do not want
- GROUP BY: put the rest into groups
- HAVING: drop groups you do not want
- SELECT: work out the output columns
- ORDER BY and LIMIT: sort and cut
The golden rule
After grouping there is only one output row per group. So each selected column must either be in GROUP BY (the same for the whole group) or be inside an aggregate (one value made from the whole group). SELECT city, name, SUM(amount) ... GROUP BY city breaks the rule: a city has many names, so which one should SQL show?
Watch the lesson
GROUP BY and HAVING · 1/6
Totals for a whole table are useful, but reports need totals per city, per day or per customer. That is GROUP BY.
Prefer a video by a teacher? Find free videos on “sql group by having explained tutorial” on YouTube ↗
In real life: A restaurant partner dashboard
A food delivery app shows each restaurant owner a dashboard: orders today, revenue this week, late deliveries per day. Every one of those numbers is a GROUP BY query. The operations team runs a similar one with HAVING to list restaurants whose late deliveries are more than a set number this week, so someone can call them before customers complain.
SELECT r.name, COUNT(*) AS late_count
FROM deliveries d
JOIN restaurants r ON r.id = d.restaurant_id
WHERE d.status = 'late'
GROUP BY r.name
HAVING COUNT(*) >= 2
ORDER BY late_count DESC;Practice (0/3 done)
Tables: customers(id, name, city) and orders(id, customer_id, order_date, amount, status). Show each city and its total order amount, ignoring cancelled orders. Keep only cities whose total is more than 1000 rupees. Highest total first.
Tables: restaurants(id, name, city) and deliveries(id, restaurant_id, status, minutes). Count deliveries for each restaurant name and status ('late' or 'on_time'). Show name, status and the count, sorted by name, then status.
Tables: students(id, name, class) and marks(student_id, subject, score). Show each student's name and total score across all subjects, only for students whose total is at least 220. Highest total first.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.