Filtering Groups with HAVING
Now that you've mastered GROUP BY, it's time to control which groups make it into your results. The University schema makes this easy to visualize: maybe you want to see only the departments that teach at least four courses, or semesters where students completed more than ten classes. The HAVING clause lets you filter groups after aggregation, complementing the WHERE clause that filters individual rows before grouping.
Learning Goals
- Know when to use
WHEREvsHAVING - Filter grouped data using counts, averages, and sums
- Combine row-level and group-level filters in the same query
- Avoid common mistakes that lead to confusing results
Understanding HAVING
HAVING evaluates conditions on aggregated groups. A typical pattern looks like this:
SELECT d.dept_name,
AVG(c.credits) AS avg_credits
FROM course AS c
JOIN department AS d ON d.dept_id = c.dept_id
GROUP BY d.dept_name
HAVING AVG(c.credits) >= 3.5;
This query shows only departments whose courses average 3.5 credits or more.
WHERE vs HAVING
Remember the execution order: WHERE filters rows ➜ GROUP BY creates groups ➜ HAVING filters groups ➜ ORDER BY sorts the final output.
-- Courses worth more than 3 credits, grouped by department
SELECT d.dept_name,
COUNT(*) AS course_count
FROM course AS c
JOIN department AS d ON d.dept_id = c.dept_id
WHERE c.credits > 3
GROUP BY d.dept_name;
-- Departments that teach more than 3 high-credit courses
SELECT d.dept_name,
COUNT(*) AS course_count
FROM course AS c
JOIN department AS d ON d.dept_id = c.dept_id
WHERE c.credits > 3
GROUP BY d.dept_name
HAVING COUNT(*) > 3;
Practical HAVING Examples
Filter by number of rows in each group
SELECT d.dept_name,
COUNT(*) AS total_courses
FROM course AS c
JOIN department AS d ON d.dept_id = c.dept_id
GROUP BY d.dept_name
HAVING COUNT(*) >= 4;
Filter by aggregate averages
SELECT e.semester,
AVG(c.credits) AS avg_credit_load
FROM enrollment AS e
JOIN course AS c ON c.course_id = e.course_id
GROUP BY e.semester
HAVING AVG(c.credits) >= 3.5;
Combine multiple aggregate conditions
SELECT d.dept_name,
e.semester,
COUNT(*) AS total_enrollments,
SUM(c.credits) AS credit_hours_awarded
FROM enrollment AS e
JOIN course AS c ON c.course_id = e.course_id
JOIN department AS d ON d.dept_id = c.dept_id
GROUP BY d.dept_name, e.semester
HAVING COUNT(*) >= 5
AND SUM(c.credits) >= 15;
Combining WHERE and HAVING
Strong queries filter out unnecessary rows early with WHERE, then use HAVING for final group-level checks.
SELECT d.dept_name,
COUNT(*) AS completed_courses,
COUNT(DISTINCT e.student_id) AS unique_students
FROM enrollment AS e
JOIN course AS c ON c.course_id = e.course_id
JOIN department AS d ON d.dept_id = c.dept_id
WHERE e.semester = 'Spring 2025' -- Row-level filter
AND e.grade IS NOT NULL -- Keep only finished classes
GROUP BY d.dept_name
HAVING COUNT(*) >= 3; -- Group-level filter
Common Pitfalls
- Aggregates in WHERE: Conditions like
WHERE COUNT(*) > 3fail becauseWHEREruns before grouping. - Missing GROUP BY:
HAVINGexpects grouped data. WithoutGROUP BY, the entire dataset is treated as one group. - Order matters: The correct order is
WHERE,GROUP BY,HAVING, thenORDER BY. - Too few row filters: Use
WHEREto shrink the data before grouping; it keepsHAVINGefficient. - Alias confusion: Some engines don't let you reference
SELECTaliases insideHAVING; repeat the expression if needed.
Summary
- Use
WHEREfor individual rows,HAVINGfor aggregated groups. - Combine both clauses to answer nuanced questions about the University dataset.
HAVINGshines when you need thresholds on counts, averages, or sums after grouping.
Quiz
Show quiz
-
Which clause filters rows before grouping occurs?
A)GROUP BY
B)WHERE
C)HAVING
D)ORDER BY -
How would you list departments that offer at least three four-credit courses?
A)WHERE COUNT(*) >= 3
B)HAVING COUNT(*) >= 3
C)HAVING credits >= 4
D)WHERE credits >= 4 -
What does
HAVING AVG(c.credits) > 3.5evaluate?
A) Each individual course row
B) Each group of rows created byGROUP BY
C) Only rows withcreditsgreater than 3.5
D) It causes an error becauseAVGmust be inSELECT -
True or False: You can use both
WHEREandHAVINGin the same query.
A) True
B) False
Answers
- B
- B
- B
- A