SQL GROUP BY and HAVING
GROUP BY puts rows with the same value into groups, so an aggregate function can give one answer for each group.
GROUP BY Syntax
Example
SELECT column, AGGREGATE_FUNCTION(column)
FROM table_name
WHERE condition
GROUP BY column
HAVING condition
ORDER BY column;Count Students per City
Without GROUP BY, COUNT(*) gives one number for the whole table. With it, you get one number per city:
Example
SELECT city, COUNT(*) AS total
FROM students
GROUP BY city
ORDER BY total DESC;| city | total |
|---|---|
| Karachi | 3 |
| Lahore | 2 |
| Islamabad | 1 |
The database makes three groups (Karachi, Lahore, Islamabad) and counts the rows in each.
Several Aggregates per Group
Example
SELECT city, COUNT(*) AS total, MAX(marks) AS best
FROM students
GROUP BY city
ORDER BY total DESC;| city | total | best |
|---|---|---|
| Karachi | 3 | 78 |
| Lahore | 2 | 88 |
| Islamabad | 1 | 92 |
Every column in SELECT must either be in GROUP BY or be inside an aggregate function. SELECT city, name, COUNT(*) ... GROUP BY city is an error in most databases, because a city group has many names and SQL does not know which one to show.
GROUP BY with a Join
Joins and grouping work well together. This report shows how many students each course has and their average marks:
Example
SELECT c.title,
COUNT(s.id) AS students,
ROUND(AVG(s.marks), 1) AS avg_marks
FROM courses AS c
LEFT JOIN students AS s ON s.course_id = c.id
GROUP BY c.id, c.title
ORDER BY c.id;| title | students | avg_marks |
|---|---|---|
| Web Development | 2 | 71.0 |
| Python Programming | 2 | 86.5 |
| Graphic Designing | 1 | 92.0 |
| Digital Marketing | 0 | NULL |
We used COUNT(s.id), not COUNT(*), so the course with no students shows 0 instead of 1.
HAVING: Filter the Groups
WHERE filters rows before they are grouped. HAVING filters groups after the aggregate is calculated. That is why aggregates can go in HAVING but not in WHERE.
Cities with more than one student:
Example
SELECT city, COUNT(*) AS total
FROM students
GROUP BY city
HAVING COUNT(*) > 1;| city | total |
|---|---|
| Karachi | 3 |
| Lahore | 2 |
Courses whose students average more than 75 marks:
Example
SELECT c.title, AVG(s.marks) AS avg_marks
FROM students AS s
JOIN courses AS c ON s.course_id = c.id
GROUP BY c.title
HAVING AVG(s.marks) > 75;| title | avg_marks |
|---|---|
| Graphic Designing | 92.0 |
| Python Programming | 86.5 |
Web Development is left out, because its average is 71.
WHERE and HAVING Together
| WHERE | HAVING | |
|---|---|---|
| Filters | Single rows | Groups |
| Runs | Before grouping | After grouping |
| Can use COUNT, AVG... | No | Yes |
Example
SELECT city, MAX(marks) AS top
FROM students
WHERE age > 18
GROUP BY city
HAVING MAX(marks) > 80
ORDER BY top DESC;| city | top |
|---|---|
| Islamabad | 92 |
| Lahore | 88 |
First Hamza (18) is removed by WHERE. Then the groups are made, and Karachi is removed by HAVING because its best mark is 78.
Exercise
Exercise
You want only the cities that have at least 2 students. Where does the condition COUNT(*) >= 2 go?
