Chapter 39: PostgreSQL HAVING Clause

The HAVING clause.

You already know:

  • WHERE filters individual rows before grouping
  • GROUP BY creates groups and lets you calculate aggregates per group (COUNT, AVG, SUM…)
  • But what if you want to filter the groups themselves — keep only those groups that satisfy a condition?

That’s exactly what HAVING is for.

1. What does HAVING actually do? (Teacher’s plain & honest explanation)

HAVING filters groups (after grouping and aggregation has happened).

Think of the query execution order in PostgreSQL:

  1. FROM + JOINs → get all candidate rows
  2. WHERE → filter individual rows
  3. GROUP BY → split remaining rows into groups
  4. Aggregates (COUNT, AVG…) → calculated for each group
  5. HAVING → throw away entire groups that don’t satisfy the condition
  6. SELECT → choose which columns/expressions to show
  7. ORDER BY / LIMIT → final sorting & limiting

HAVING can only use:

  • Columns that appear in the GROUP BY clause
  • Aggregate functions (COUNT, SUM, AVG, MIN, MAX…)
  • Expressions based on the above

HAVING cannot see individual row columns that are not grouped (this is the #1 confusion point).

2. Basic syntax skeleton (memorize this flow)

SQL

3. Real, detailed examples (using our students table)

Current data (with some added rows for better examples):

id first_name city enrollment_year gpa fees_paid courses_enrolled
1 Aarav Hyderabad 2025 8.95 TRUE 2
2 Priya Secunderabad 2024 9.60 FALSE 1
3 Rahul Hyderabad 2025 7.85 FALSE 0
4 Sneha Hyderabad 2025 9.20 TRUE 1
5 Vikram Hyderabad 2025 8.40 TRUE 1
6 Meera Secunderabad 2025 10.00 TRUE 3
7 Kiran Hyderabad 2024 8.10 TRUE 2

Example 1: HAVING vs WHERE – classic confusion demo

SQL

Correct → move the aggregate condition to HAVING:

SQL

Result:

text

→ Only cities where average GPA is ≥ 9.0 are kept

Example 2: HAVING with COUNT (most common real-world use)

SQL

Result (example):

text

Example 3: HAVING + multiple aggregates + multiple grouping columns

SQL

Result (example):

text

→ Only groups (city + fees_paid combinations) that meet all three conditions

Example 4: HAVING with JOIN (very real reporting pattern)

SQL

→ Very typical “course performance report”

4. HAVING vs WHERE – side-by-side comparison (very important!)

Clause Filters… Can use aggregates? Runs… Typical use case example
WHERE Individual rows No Before GROUP BY WHERE enrollment_year = 2025
HAVING Groups (after aggregation) Yes After GROUP BY HAVING COUNT(*) >= 5 or HAVING AVG(gpa) > 9.0

Very common mistake:

SQL

→ Fix: move to HAVING

5. Quick cheat-sheet (keep in your notes)

Goal Typical HAVING condition Equivalent WHERE (if possible)
Groups with enough rows HAVING COUNT(*) >= 10 — (cannot)
Groups with good average HAVING AVG(salary) > 50000 — (cannot)
Groups with at least one special member HAVING SUM(CASE WHEN is_active THEN 1 ELSE 0 END) >= 1 — (cannot directly)
Exclude small/empty groups HAVING COUNT(*) > 0 — (but LEFT JOIN + WHERE IS NULL can simulate)

Your mini homework right now

  1. Group students by city — show count + avg GPA
  2. Add HAVING to keep only cities with average GPA ≥ 9.0
  3. Group by enrollment_year and fees_paid — keep only combinations with at least 2 students
  4. Try writing a query with HAVING on a non-grouped column → see the error

Next class?

Tell me:

  • Want HAVING vs WHERE more tricky examples?
  • GROUP BY + JOIN + HAVING real reporting query?
  • Advanced grouping: GROUPING SETS, ROLLUP, CUBE?
  • Aggregates without GROUP BY vs with GROUP BY?

Your teacher is ready — what’s the next topic? 🚀

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *