SQL
database queries
WHERE clause
HAVING clause
data filtering

WHERE vs HAVING

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

WHERE and HAVING both filter data in SQL, but they operate at different stages of a query. WHERE filters rows before grouping and aggregation, while HAVING filters groups after aggregation has already happened.

The Core Difference

Think about the query pipeline this way:

  1. rows are read from tables
  2. WHERE removes rows you do not want
  3. remaining rows may be grouped
  4. aggregates such as COUNT or SUM are computed
  5. HAVING removes groups you do not want

That is why the two clauses are not interchangeable even though they both feel like filters.

WHERE Filters Individual Rows

Use WHERE when the condition is about raw row values.

sql
SELECT employee_id, department, salary
FROM employees
WHERE department = 'Sales';

This filters rows before any grouping takes place.

Another example:

sql
SELECT order_id, customer_id, total_amount
FROM orders
WHERE total_amount > 100;

Again, the condition applies to each row individually.

HAVING Filters Aggregated Groups

Use HAVING when the condition depends on aggregate results.

sql
1SELECT department, COUNT(*) AS employee_count
2FROM employees
3GROUP BY department
4HAVING COUNT(*) >= 10;

This does not ask whether an individual row has count >= 10. It asks whether the group formed by each department has at least ten rows.

That is why HAVING comes after GROUP BY.

A Query That Uses Both

Many real queries use both clauses together.

sql
1SELECT department, AVG(salary) AS avg_salary
2FROM employees
3WHERE active = 1
4GROUP BY department
5HAVING AVG(salary) > 50000;

Interpretation:

  • 'WHERE active = 1 keeps only active employees'
  • 'GROUP BY department builds department groups'
  • 'HAVING AVG(salary) > 50000 keeps only departments whose average salary is above the threshold'

This is a good example because it shows each clause doing a different job.

Why WHERE Is Usually Better For Non-Aggregate Conditions

If a condition can be written in WHERE, it usually should be. That lets the database reduce the number of rows earlier in the execution pipeline, which is often better for performance and clarity.

For example, this is usually the right form:

sql
1SELECT department, COUNT(*) AS employee_count
2FROM employees
3WHERE active = 1
4GROUP BY department;

Instead of trying to push the row-level condition into HAVING unnecessarily.

Filtering earlier means fewer rows need to be grouped and aggregated.

Common Beginner Mistake

A very common mistake is trying to write aggregate conditions in WHERE.

sql
1-- wrong
2SELECT department, COUNT(*)
3FROM employees
4WHERE COUNT(*) > 10
5GROUP BY department;

That fails because COUNT(*) does not exist at the row-filtering stage.

The correct version is:

sql
1SELECT department, COUNT(*)
2FROM employees
3GROUP BY department
4HAVING COUNT(*) > 10;

HAVING Without GROUP BY

Some SQL dialects allow HAVING without an explicit GROUP BY, especially when the whole result is treated as one aggregate group.

Example:

sql
SELECT COUNT(*) AS total_employees
FROM employees
HAVING COUNT(*) > 100;

This is valid in some systems, but conceptually it is still filtering an aggregate result, not row data.

That is why the main rule still holds: HAVING is about aggregates.

Performance Perspective

A simple rule of thumb is:

  • put row predicates in WHERE
  • put aggregate predicates in HAVING

This is not just style. It aligns with how the database engine can optimize work.

If you move a row predicate into HAVING, the database may have to group more rows than necessary before discarding them.

Common Pitfalls

  • Using HAVING when the condition is really about raw rows and belongs in WHERE.
  • Trying to use aggregate functions such as COUNT() directly in WHERE.
  • Thinking WHERE and HAVING are interchangeable because both filter results.
  • Forgetting that HAVING runs after grouping, so it cannot reduce pre-group row count the same way WHERE can.
  • Writing harder-to-read queries by mixing row-level and group-level logic in the wrong clause.

Summary

  • 'WHERE filters rows before grouping and aggregation.'
  • 'HAVING filters groups after aggregation.'
  • Use WHERE for row-level conditions and HAVING for aggregate conditions.
  • Queries often use both clauses together for different parts of the logic.
  • If a predicate can be applied before grouping, WHERE is usually the better place for it.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.