N015 Tier 2 · Core SQL

HAVING in SQL

The HAVING clause filters groups after aggregation has occurred. It operates on the output of GROUP BY, applying conditions to aggregate values that do not exist until grouping is complete.

Before this GROUP BY

HAVING filters groups after aggregation is complete. It decides which grouped results survive into the output.

You're querying a database with 62 customers and several hundred orders. The product team wants to identify repeat buyers — anyone who placed more than three orders. Getting there takes two steps: count orders per customer with GROUP BY, then keep only the groups where that count exceeds three. That second step is HAVING's job. You can't do it with WHERE, because WHERE runs before grouping starts, before any counts exist. HAVING runs after GROUP BY, when the aggregate values are ready to filter on.

The division is: WHERE filters individual rows going into the aggregation; HAVING filters groups coming out of it.

Here's what the repeat-buyer query looks like:

SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 3

SQL groups orders by customer, counts the rows in each group, and HAVING removes any group where the count is 3 or under. Only repeat buyers reach the output.

HAVING works with any aggregate function. Products that generated more than $2,000 in total revenue:

SELECT product_id, SUM(quantity * unit_price) AS total_revenue
FROM order_items
GROUP BY product_id
HAVING SUM(quantity * unit_price) > 2000

Notice that the aggregate expression appears twice — once in SELECT and again in HAVING. That's required. SQL resolves aliases after HAVING has already run, which means you can't write HAVING total_revenue > 2000 even though you defined that alias in SELECT. Writing the full expression in both places is standard practice, and once you know why, it stops feeling strange.

WHERE and HAVING work together when you need to filter at both stages:

WHERE removes non-delivered orders before any grouping happens. GROUP BY forms the groups. HAVING keeps only customers with more than two delivered orders. That sequence is also the order SQL evaluates the query.

The one thing that trips people up: putting aggregate conditions in WHERE instead of HAVING.

A filter like COUNT(*) > 3 or SUM(amount) > 1000 belongs in HAVING, not WHERE. When WHERE runs, there are no groups yet and no aggregate values. SQL raises an error.

The inverse mistake is less common but worth knowing: putting non-aggregate conditions in HAVING when they belong in WHERE. Something like HAVING status = 'delivered' is technically valid, but it forces SQL to group every row first and then discard the ones that don't match. Running that filter through WHERE before grouping is more efficient, and on large tables the difference shows.

Check your understanding

You want to keep only groups where SUM(total_amount) > 500. Where does that condition go?

Practice

10 HAVING practice problems

These problems are part of the HAVING lesson in SQLMaxx, with instant grading and a worked solution on each.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.

Practice, feedback, mastery, review. That's the loop that turns reading into real skill.

Start free

No account, no credit card. Start solving in under a minute.