One of the most essential concepts in SQL query design is understanding why HAVING exists alongside WHERE. Because WHERE executes before data is grouped into summary buckets, it cannot filter aggregate values like COUNT(*) or AVG(salary). The HAVING clause filters group buckets after aggregation is performed.
| Criteria | WHERE Clause | HAVING Clause |
|---|---|---|
| Execution Step | Step 2 (Filters raw table rows before grouping). | Step 4 (Filters group summary buckets after grouping). |
| Aggregate Functions | Never allowed (e.g., WHERE AVG(gpa) > 3.5 throws syntax error). |
Allowed & Expected (e.g., HAVING AVG(gpa) > 3.5). |
| Works With | Can be used with SELECT, UPDATE, DELETE. |
Can only be used with SELECT queries containing GROUP BY. |
| Performance | Fast (can leverage B-Tree indexes to discard rows early). | Evaluated after grouping in memory or temporary disk buffers. |
Consider this real-world university reporting problem: "Find all courses with more than 1 student enrolled, but consider only active students with GPA >= 3.50":
SELECT
course,
COUNT(student_id) AS eligible_students,
ROUND(AVG(gpa), 2) AS course_avg_gpa
FROM students
WHERE gpa >= 3.50 -- 1. Filter raw rows first
GROUP BY course -- 2. Group into buckets
HAVING COUNT(student_id) > 1 -- 3. Filter group summary buckets
ORDER BY course_avg_gpa DESC; -- 4. Sort final result set
Congratulations! You have completed Module 5. Now let us advance to Module 6 and master multi-table relational queries starting with SQL Joins Overview!