HAVING Clause
Learn how to filter grouped and aggregated results using HAVING, and how it differs from WHERE.
Introduction
In the previous lesson, GROUP BY let you summarize students per grade — counting them, averaging their marks, and so on. But what if you only want to see the grades that have more than 2 students? WHERE cannot help here, because WHERE filters individual rows before any grouping happens.
HAVING solves exactly this problem. It filters groups after GROUP BY has already summarized the data, letting you keep only the groups that meet a certain condition.
- What HAVING does and when to use it.
- The key difference between WHERE and HAVING.
- A worked example finding grades with more than 2 students.
- The correct order of WHERE, GROUP BY, HAVING, and ORDER BY in a query.
What is HAVING?
HAVING is a clause used to filter the results of a GROUP BY, based on the value of an aggregate function like COUNT(), AVG(), or SUM(). It is written directly after GROUP BY.
Using the same students table from the previous lesson:
| id | name | grade | marks |
|---|---|---|---|
| 1 | Alex | A | 92 |
| 2 | Sam | B | 74 |
| 3 | Priya | A | 88 |
| 4 | Jon | C | 55 |
| 5 | Maya | B | 69 |
| 6 | Leo | A | 95 |
Grouping by grade and counting gives three groups: A has 3 students, B has 2, and C has 1. HAVING lets you keep only the groups whose count satisfies a condition.
SELECT grade, COUNT(*) AS total_students
FROM students
GROUP BY grade
HAVING COUNT(*) > 2;+-------+----------------+
| grade | total_students |
+-------+----------------+
| A | 3 |
+-------+----------------+Only grade "A" is returned, because it is the only group where COUNT(*) is greater than 2. Grades B (2 students) and C (1 student) are filtered out.
WHERE vs HAVING
The single most important distinction is timing: WHERE filters individual rows before grouping happens, while HAVING filters entire groups after grouping and aggregation have already happened.
WHERE
- Filters individual rows.
- Runs before GROUP BY.
- Cannot reference aggregate functions like COUNT(*) or AVG().
- Example: WHERE marks > 60
HAVING
- Filters entire groups.
- Runs after GROUP BY.
- Can reference aggregate functions like COUNT(*) or AVG().
- Example: HAVING COUNT(*) > 2
-- WHERE: keep only rows where marks is above 60, BEFORE grouping
SELECT grade, COUNT(*) AS total_students
FROM students
WHERE marks > 60
GROUP BY grade;+-------+----------------+
| grade | total_students |
+-------+----------------+
| A | 3 |
| B | 2 |
+-------+----------------+Writing WHERE COUNT(*) > 2 is invalid in MySQL, because WHERE runs before aggregation exists — COUNT(*) has not been calculated yet at that point. The condition must be written as HAVING COUNT(*) > 2 instead.
Worked Example: Grades With More Than 2 Students
You can combine WHERE and HAVING in the same query — WHERE filters raw rows first, then GROUP BY summarizes what remains, and HAVING filters those summarized groups.
SELECT grade, COUNT(*) AS total_students, AVG(marks) AS average_marks
FROM students
WHERE marks >= 50
GROUP BY grade
HAVING COUNT(*) > 1
ORDER BY total_students DESC;+-------+----------------+---------------+
| grade | total_students | average_marks |
+-------+----------------+---------------+
| A | 3 | 91.6667 |
| B | 2 | 71.5000 |
+-------+----------------+---------------+Here, WHERE marks >= 50 keeps every row (all marks already satisfy this in the sample data), GROUP BY grade creates the three grade groups, HAVING COUNT(*) > 1 removes grade "C" (only 1 student), and ORDER BY sorts the remaining groups by size.
Clause Order
MySQL requires clauses to be written in a specific order in your query. Understanding this order helps you reason about exactly what each clause is filtering.
| Order | Clause | Purpose |
|---|---|---|
| 1 | WHERE | Filters individual rows before any grouping. |
| 2 | GROUP BY | Groups the remaining rows by shared column values. |
| 3 | HAVING | Filters the resulting groups, often using aggregate functions. |
| 4 | ORDER BY | Sorts the final, filtered set of grouped rows. |
SELECT grade, COUNT(*) AS total_students
FROM students
WHERE marks >= 50 -- 1. filter rows
GROUP BY grade -- 2. group remaining rows
HAVING COUNT(*) > 1 -- 3. filter groups
ORDER BY total_students DESC; -- 4. sort final resultThink of it as: WHERE picks rows, GROUP BY buckets them, HAVING picks buckets, ORDER BY arranges what is left. Writing the clauses out of this order causes a syntax error.
Common Mistakes
- Using WHERE with an aggregate function like WHERE COUNT(*) > 2 — this is invalid; use HAVING instead.
- Placing HAVING before GROUP BY in the query — HAVING must always come after GROUP BY.
- Using HAVING to filter individual rows when WHERE would be faster and more appropriate.
- Forgetting ORDER BY must come after HAVING, not before.
Best Practices
- Use WHERE whenever possible, since filtering rows early is more efficient than filtering after grouping.
- Reserve HAVING specifically for conditions that depend on an aggregate function.
- Always write clauses in the order WHERE, GROUP BY, HAVING, ORDER BY.
- Alias aggregate expressions with AS so HAVING conditions stay readable.
Frequently Asked Questions
Can I use HAVING without GROUP BY?
It is legal in MySQL, but unusual — without GROUP BY, the whole table is treated as one group, so HAVING behaves like a WHERE clause applied to a single aggregate result.
Can HAVING reference a column alias from SELECT?
Yes, MySQL allows this (unlike some other databases), so HAVING total_students > 2 works if total_students was aliased in SELECT.
Is HAVING slower than WHERE?
It can be, because WHERE reduces the number of rows before the expensive grouping and aggregation work happens, while HAVING filters after that work is already done.
Can I combine multiple conditions in HAVING?
Yes, using AND, OR, and NOT, exactly like in a WHERE clause — for example HAVING COUNT(*) > 1 AND AVG(marks) > 60.
Key Takeaways
- HAVING filters groups produced by GROUP BY, after aggregation has happened.
- WHERE filters individual rows before any grouping takes place.
- HAVING can reference aggregate functions like COUNT(*) and AVG(); WHERE cannot.
- The correct clause order is WHERE, then GROUP BY, then HAVING, then ORDER BY.
Summary
HAVING completes the picture started by GROUP BY: it lets you filter down to only the groups that matter, based on aggregate conditions like a minimum count or average.
In this lesson, you learned what HAVING does, how it differs from WHERE, and the strict clause order MySQL enforces. Next, you will step back and get a conceptual overview of SQL operators as a whole.
- You understand what HAVING filters and when it runs.
- You can clearly explain the difference between WHERE and HAVING.
- You know the correct order of WHERE, GROUP BY, HAVING, and ORDER BY.
- You are ready to explore SQL operators as a category.