How to Use HAVING in SQL
Use HAVING when you need to filter grouped results after aggregation. It works like WHERE, but instead of filtering individual rows, it filters the summary groups created by GROUP BY.
What you’ll build or solve
You’ll learn how to use HAVING in SQL to filter aggregated groups. You’ll also know when to use WHERE first and HAVING after grouping.
Learn SQL on Mimo
When this approach works best
This approach is the right choice when grouped totals, averages, or counts should meet a condition.
Common real-world scenarios include:
- Countries with 100+ users
- Products with average rating above 4
- Plans with more than 50 signups
- Classes with low pass rates
- Categories with total revenue thresholds
This is a bad idea when the filter applies to individual rows before grouping. In that case, use WHERE.
Prerequisites
You only need:
- A SQL table with rows
- Basic
GROUP BYand aggregate knowledge
Step-by-step instructions
Step 1: Filter grouped results after GROUP BY
Group the rows first.
SQL
SELECT country, COUNT(*)
FROM users
GROUP BY country
HAVING COUNT(*) > 100;
This returns only countries with more than 100 users.
This is different from WHERE, which filters rows before grouping.
You can combine both.
SQL
SELECT status, SUM(total)
FROM orders
WHERE created_at >= '2026-01-01'
GROUP BY status
HAVING SUM(total) > 10000;
What to look for:
HAVINGfilters groups, not raw rows- Use it after
GROUP BY - Great for count and revenue thresholds
WHEREstill runs before grouping- Works with all aggregate functions
Examples you can copy
Popular plans
SQL
SELECT plan, COUNT(*)
FROM users
GROUP BY plan
HAVING COUNT(*) > 50;
High-revenue countries
SQL
SELECT country, SUM(total)
FROM orders
GROUP BY country
HAVING SUM(total) > 5000;
Top-rated products
SQL
SELECT product_id, AVG(rating)
FROM reviews
GROUP BY product_id
HAVING AVG(rating) >= 4.5;
Common mistakes and how to fix them
Mistake 1: Using HAVING instead of WHERE for row filters
What the reader might do:
SQL
SELECT status, COUNT(*)
FROM orders
GROUP BY status
HAVING created_at >= '2026-01-01';
Why it breaks: created_at is a row-level filter.
Corrected approach:
Use WHERE before grouping.
Mistake 2: Forgetting GROUP BY
What the reader might do:
SQL
SELECT COUNT(*)
FROM users
HAVING COUNT(*) > 100;
Why it breaks: some databases allow this, but the intent is less clear.
Corrected approach:
Use GROUP BY when filtering grouped dimensions.
Mistake 3: Overusing aggregates in both SELECT and HAVING
What the reader might do:
SQL
HAVING SUM(total) > 100
AND AVG(total) > 50
Why it breaks: this may be correct, but can become hard to reason about.
Corrected approach:
Use aliases where supported or keep threshold logic focused.
Troubleshooting
If the filter should apply before grouping, switch to WHERE.
If SQL errors, confirm aggregates are valid in HAVING.
If too many groups appear, tighten the threshold.
If the grouped dimension is missing, add GROUP BY.
Quick recap
- Use
HAVINGto filter grouped results - Place it after
GROUP BY - Use
WHEREfor row-level filters - Great for count and revenue thresholds
- Works with aggregate functions
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot