HAVING vs. WHERE Explained in Detail: the Difference for Aggregation
AI generated
SELECT
JOIN
SQL · Aggregation · Reporting
HAVING vs. WHERE in Detail
the difference that decides aggregations

Treating HAVING and WHERE as interchangeable filter clauses produces queries that either work by accident or run needlessly slow. The difference lies in execution order: WHERE filters before aggregation, HAVING after, and that is exactly what decides what can be filtered at all.

16 min read HAVING · WHERE · GROUP BY · aggregate functions PostgreSQL · MySQL · SQL Server · Oracle

1. Why HAVING vs. WHERE is not a style question

Many SQL introductions present the difference between HAVING vs. WHERE as a pure syntax rule: WHERE comes before GROUP BY, HAVING after. That is syntactically correct, but it obscures the actual reason two separate clauses exist. WHERE and HAVING operate at different stages of query processing, and that stage determines which expressions are even valid.

WHERE filters individual rows before any grouping or aggregation has taken place. HAVING filters groups after aggregate functions such as SUM, COUNT, or AVG have already been computed. This order is not an arbitrary language convention but reflects exactly how a relational database processes a query internally. Understanding HAVING vs. WHERE from this angle avoids both syntax errors and needlessly slow queries.

The practical difference becomes especially clear as soon as a filter should apply to an aggregate function, for instance all customers with more than ten orders. This filter cannot live in WHERE, because at the time WHERE is evaluated, orders per customer have not yet been counted. That is exactly the case HAVING exists for.

2. The logical execution order of SQL

SQL is written in a certain order, FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, but evaluated internally in a different order. First the data source is built in FROM, including all joins. Then WHERE filters individual rows out of that data source. Only after that does GROUP BY group the remaining rows, and HAVING filters the resulting groups. SELECT chooses the output columns, and ORDER BY sorts the final result.

This logical order explains why HAVING vs. WHERE are not interchangeable alternatives. An expression in WHERE can only reference columns of the base tables, never the result of an aggregate function, because that simply does not exist yet at the time WHERE is evaluated. HAVING, on the other hand, can access both aggregate functions and, in most databases, the grouping columns themselves.


-- Logical execution order (not the written order):
-- 1. FROM   (build the source, including joins)
-- 2. WHERE  (filter individual rows)
-- 3. GROUP BY (group remaining rows)
-- 4. HAVING (filter the resulting groups)
-- 5. SELECT (choose output columns)
-- 6. ORDER BY (sort the final result)

SELECT
    customer_id,
    COUNT(*) AS order_count,
    SUM(amount) AS total_revenue
FROM orders
WHERE order_date >= '2026-01-01'   -- filters rows before grouping
GROUP BY customer_id
HAVING COUNT(*) > 10                -- filters groups after aggregation
ORDER BY total_revenue DESC;

A common misunderstanding is the assumption that ORDER BY can reference an alias computed in HAVING directly, without the database re-evaluating it. In most databases this actually works, because SELECT aliases may be reused in ORDER BY, but not in WHERE or HAVING itself, since these clauses are logically evaluated before SELECT.

3. WHERE: what is possible before aggregation

WHERE operates exclusively at row level and knows nothing about groups. Every condition in WHERE must reference column values of a single row, as it comes directly from the table or a join. Typical WHERE conditions are date filters, status filters, or text comparisons that can be decided individually for each row regardless of a later grouping.

The decisive advantage of WHERE over HAVING: rows excluded by WHERE are never aggregated. That means less data volume for the subsequent GROUP BY processing and therefore usually a faster query. A WHERE filter on an indexed date can also use an index scan, which would no longer be possible for an equivalent HAVING condition on the same date, because by then the date is already part of an aggregated group.


-- WHERE filters rows BEFORE aggregation — cheap and index-friendly
SELECT
    product_category,
    COUNT(*) AS sale_count,
    SUM(quantity) AS total_quantity
FROM sales
WHERE sale_date >= '2026-06-01'
  AND status = 'completed'
GROUP BY product_category;

-- Cannot be done in WHERE, since COUNT(*) does not exist yet at this stage:
-- WHERE COUNT(*) > 100   -- syntax error in every major database

4. HAVING: filtering on aggregate functions

HAVING exists exactly for the case where a filter refers to the result of an aggregate function, not to individual row values. Typical examples are customers with more than a certain number of orders, product categories with revenue above a threshold, or regions with an average order size below a limit. All these conditions can only be formulated after SUM, COUNT, or AVG has been computed per group.

HAVING can reference both aggregate functions and, in most databases including PostgreSQL and MySQL, the grouping columns themselves. A filter like HAVING region = 'North' AND SUM(revenue) > 10000 is perfectly valid, even though the plain column condition region = 'North' could just as well, and more efficiently, live in WHERE, since it requires no aggregate function.


-- HAVING filters on the result of aggregate functions
SELECT
    region,
    category,
    SUM(revenue) AS total_revenue,
    AVG(order_value) AS avg_order_value
FROM sales
GROUP BY region, category
HAVING SUM(revenue) > 10000
   AND AVG(order_value) < 500;

-- HAVING can also reference the grouping column directly,
-- but a plain column condition belongs in WHERE for performance reasons

An often overlooked point: HAVING can reference a SELECT alias if the database supports it, for instance PostgreSQL and MySQL, while SQL Server and Oracle are stricter and require the full expression to be repeated in HAVING. This inconsistency between databases is a good reason to repeat the full aggregate expression in HAVING for portable SQL, rather than relying on alias support.

5. Combining WHERE and HAVING

In practice, WHERE and HAVING are almost always used together, each for the part of the problem it was built for. WHERE first reduces the data volume to the relevant rows, for instance a certain time range or a certain status. After that, GROUP BY groups the remaining rows, and HAVING filters the groups by the aggregated result. This combination is the normal case in nearly every reporting query.

A common mistake is accidentally placing a condition that actually belongs in WHERE into HAVING instead, because it sits in the same query next to a genuine HAVING condition. The result is often still correct, but performance suffers, since all rows are aggregated first before the actually early filter takes effect. The rule of thumb: anything that refers purely to row values belongs in WHERE, anything that needs an aggregate function belongs in HAVING.


-- WRONG: row-level condition placed in HAVING — filters after aggregation
SELECT region, SUM(revenue) AS total
FROM sales
GROUP BY region
HAVING region IN ('North', 'South') AND SUM(revenue) > 5000;

-- RIGHT: row-level condition moved to WHERE — filters before aggregation
SELECT region, SUM(revenue) AS total
FROM sales
WHERE region IN ('North', 'South')
GROUP BY region
HAVING SUM(revenue) > 5000;

6. Performance difference: early vs. late filtering

The performance difference between HAVING vs. WHERE follows directly from the execution order. A WHERE filter reduces the row count before the expensive aggregation even begins. A HAVING filter, in contrast, is only applied after all rows have been grouped and aggregated, even if many groups are discarded again at the end. On large tables, this difference can significantly change the execution plan.

Modern query optimizers can automatically rewrite some row-level conditions from HAVING into an equivalent WHERE, known as predicate pushdown. You should not rely on this, however, since this optimization does not apply in every database or for every expression. An explicit WHERE filter is always the safer and more readable choice, regardless of whether the optimizer would perform the rewrite anyway.

On very large fact tables in a reporting context, the difference between an early WHERE filter on an indexed date and a late, equivalent HAVING filter can mean several seconds of execution time, especially when the grouping itself is computationally expensive, for instance with many distinct grouping keys.

7. HAVING without GROUP BY: the special case

A lesser known case: HAVING can also be used without an explicit GROUP BY. In that case, the database treats the entire result set as a single group, and HAVING filters whether this one group is included in the result at all. This is useful for checking whether an aggregate condition is met across the entire table, for instance whether total revenue exceeds a threshold.


-- HAVING without GROUP BY: the whole result set is treated as one group
SELECT SUM(revenue) AS total_revenue
FROM sales
WHERE sale_date >= '2026-01-01'
HAVING SUM(revenue) > 1000000;

-- Returns either one row (condition met) or zero rows (condition not met)

This special case is used rarely, but is practical in monitoring or alerting queries, where an empty result serves as a signal that a threshold was not reached. Application code that checks for an empty result instead of comparing a numeric value can be built more simply this way, especially in systems that only evaluate the presence status of a query anyway.

8. Common mistakes in practice

The most common mistake with HAVING vs. WHERE is trying to use an aggregate function in WHERE, which leads to a syntax error in every relational database, since the aggregate function simply does not exist at the time WHERE is evaluated. The second common mistake is the opposite: a plain column condition is placed in HAVING, which is syntactically correct but costs unnecessary performance, since filtering only happens after aggregation.

A third, subtler mistake concerns NULL values in aggregate functions. HAVING SUM(column) > 0 behaves unexpectedly if all values in a group are NULL, since SUM returns NULL in that case, not 0, and a comparison with NULL in SQL is neither true nor false, but unknown. The group is then silently removed from the result, which leads to hard to trace missing rows without knowledge of NULL semantics.

Criterion WHERE HAVING
Level of effect Individual rows, before aggregation Groups, after aggregation
Aggregate functions allowed No Yes
Usable without GROUP BY Yes, standard case Yes, whole table as one group
Index usage Directly possible Not on aggregate result
Performance effect Reduces data early Filters after expensive aggregation

Mironsoft

SQL reporting, data modeling, and query optimization

Slow reporting queries with misplaced filters?

We review existing queries for misplaced WHERE and HAVING conditions and build reporting SQL that filters as early as possible and aggregates only afterward.

Query review

Check HAVING and WHERE conditions for efficiency

Performance tuning

Analyze execution plans and place filters early

Reporting setup

Design new dashboards with clean aggregation queries

10. Summary

The difference between HAVING vs. WHERE follows directly from the logical execution order of SQL: WHERE filters rows before GROUP BY groups them and aggregate functions are computed, HAVING filters the resulting groups afterward. Aggregate functions such as SUM, COUNT, and AVG can therefore only be referenced in HAVING, never in WHERE. Plain column conditions, on the other hand, always belong in WHERE, even if they could syntactically also live in HAVING, because there they delay aggregation unnecessarily.

Combining both clauses is the normal case in reporting queries: WHERE reduces the data volume early, HAVING filters the aggregated result by business criteria such as minimum revenue or minimum order count. Consistently maintaining this separation produces queries that are both correct and performant, regardless of the underlying database.

HAVING vs. WHERE — the essentials at a glance

WHERE

Filters individual rows before aggregation, no aggregate functions allowed, uses indexes directly.

HAVING

Filters groups after aggregation, aggregate functions and grouping columns allowed.

Rule of thumb

Plain column condition: WHERE. Condition on an aggregate function: HAVING.

NULL trap

SUM() over only NULL values returns NULL, not 0, a comparison in HAVING then fails silently.

11. FAQ: HAVING vs. WHERE

1Fundamental difference HAVING vs. WHERE?
WHERE filters rows before grouping, HAVING filters groups after the aggregate computation.
2Aggregate function in WHERE possible?
No, the aggregate function's result does not exist yet at the time of WHERE, causing a syntax error.
3Why is WHERE more performant?
WHERE reduces the row count before the expensive aggregation, HAVING is only applied afterward.
4HAVING without GROUP BY?
Yes, the whole result set is then treated as one group, and HAVING decides whether to include this single group.
5Alias referenceable in HAVING?
Often yes in PostgreSQL/MySQL, usually not in SQL Server/Oracle. Repeat the full expression for portable SQL.
6SUM() over only NULL values?
Returns NULL, not 0. A comparison with NULL is unknown, the group silently disappears from the HAVING result.
7Where should a plain column condition go?
In WHERE, even where HAVING would be syntactically allowed, because WHERE processes less data before aggregation.
8Does HAVING use indexes like WHERE?
Not for aggregate conditions, since aggregate results are only computed per group at runtime and are not indexed.
9Same order in all databases?
Yes, the logical order applies equally to PostgreSQL, MySQL, SQL Server, and Oracle.
10Combine WHERE and HAVING?
Yes, this is the normal case: WHERE reduces early, HAVING then filters the aggregated result.