Lesson 17 — Filtering Groups
HAVING
HAVING filters the result of GROUP BY. It is WHERE for groups. Without it you cannot filter on aggregated values like 'courses with average mark above 70'.
SQL — Basic HAVING
-- HAVING filters groups (after GROUP BY):
SELECT
course,
COUNT(*) AS learner_count,
AVG(mark) AS avg_mark
FROM learners
GROUP BY course
HAVING AVG(mark) > 70; -- only courses averaging above 70%
-- HAVING COUNT — groups with more than 5 learners:
SELECT
city,
COUNT(*) AS learner_count
FROM learners
GROUP BY city
HAVING COUNT(*) > 5
ORDER BY learner_count DESC;
SQL — WHERE + GROUP BY + HAVING
-- WHERE + GROUP BY + HAVING (all three together):
SELECT
course,
COUNT(*) AS active_learner_count,
ROUND(AVG(mark), 1) AS avg_mark
FROM learners
WHERE is_active = TRUE -- 1: filter rows (active only)
GROUP BY course -- 2: group the active rows
HAVING COUNT(*) >= 3 -- 3: only courses with 3+ active learners
AND AVG(mark) >= 50 -- 4: AND average pass rate
ORDER BY avg_mark DESC; -- 5: sort the groups
SQL — Complex HAVING
-- HAVING with CASE — complex group filter:
SELECT
course,
COUNT(*) AS total,
COUNT(CASE WHEN mark >= 50 THEN 1 END) AS passed,
ROUND(
COUNT(CASE WHEN mark >= 50 THEN 1 END) * 100.0 / COUNT(*)
, 1) AS pass_rate
FROM learners
WHERE mark IS NOT NULL
GROUP BY course
HAVING COUNT(*) >= 5 -- only courses with at least 5 graded learners
AND COUNT(CASE WHEN mark >= 50 THEN 1 END) * 1.0 / COUNT(*) < 0.6
-- pass rate below 60% — courses that may need facilitator attention
ORDER BY pass_rate ASC;
Practice Task
Your Turn
Write queries: (1) cities with more than 3 learners; (2) courses where at least one learner scored above 90; (3) courses with average mark between 60 and 75 and at least 4 learners; (4) show only courses where the number of failing learners (mark < 50) exceeds the number of passing learners — these courses need intervention.
Common Mistakes
- Using WHERE instead of HAVING for aggregate conditions —
WHERE AVG(mark) > 70is a syntax error; aggregates go in HAVING. - Using HAVING without GROUP BY — technically allowed in some databases but usually wrong.
- HAVING can reference aliases in GROUP BY in some engines (MySQL) but not in standard SQL — use the expression.
- HAVING condition on a column not in GROUP BY — may work but produces confusing semantics.
Professional Tip
The order of clause execution is: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. Knowing this helps you understand which values are available at each step.
Mini Quiz
Why can't you use WHERE AVG(mark) > 70?
WHERE filters individual rows before GROUP BY and aggregation occur. At the WHERE stage, there is no AVG value yet. Use HAVING to filter after aggregation.