Data and Business Intelligence Glossary Terms

What is the HAVING clause?

HAVING is the SQL clause that filters the results of a GROUP BY query after aggregation, keeping only the groups that meet a condition. It exists because WHERE can’t do this job: by the time an aggregate like SUM or COUNT has a value, the WHERE clause has already run.

HAVING vs. WHERE

This is the whole story of the clause, so it’s worth getting precise. A query with grouping runs in a fixed order: WHERE filters individual rows first, then GROUP BY collapses the survivors into groups, then aggregates are computed, and only then does HAVING filter the groups. WHERE asks “should this row participate at all?” and HAVING asks “should this group make it into the results?”

Say you want to find your repeat customers — anyone with more than five orders this year:

SELECT
  customer_id,
  COUNT(*) AS order_count
FROM orders
WHERE order_date >= '2026-01-01'
GROUP BY customer_id
HAVING COUNT(*) > 5;

The WHERE clause throws out last year’s orders before anything is counted. The HAVING clause then keeps only the customers whose remaining orders number more than five. Try to move that condition into WHERE and the database will reject it — WHERE COUNT(*) > 5 is asking about an aggregate before it exists.

The reverse mistake is subtler and won’t produce an error: putting a plain row condition in HAVING when it belongs in WHERE. Most databases will let you write HAVING conditions on grouped columns, but you’ve now made the database group and count rows it was going to discard anyway. As a rule of thumb, filter as early as you can — row conditions in WHERE, aggregate conditions in HAVING.

A few practical notes

HAVING only makes sense with grouping, and its predicates almost always involve an aggregate: HAVING SUM(amount) > 10000, HAVING COUNT(DISTINCT product_id) >= 3. In most databases you repeat the aggregate expression rather than the column alias — HAVING order_count > 5 works in MySQL but not in PostgreSQL or SQL Server, so repeating COUNT(*) is the portable habit.

If a condition gets too gnarly for HAVING, wrapping the grouped query in a CTE and filtering the outer query with a plain WHERE does the same thing and often reads better.

HAVING in Metabase

If you build the same question in Metabase’s query builder — summarize by count, group by customer, then add a filter on the count — Metabase generates the post-aggregation filtering for you; you never type HAVING. The distinction still matters, though: a filter added on a raw column before summarizing corresponds to WHERE, and a filter on the summarized value corresponds to HAVING. In the native editor, you write the clause yourself, and it’s one of the first things to check when a grouped query returns fewer rows than you expected.

Was this helpful?