SQL HAVING
Filter groups after aggregation — the WHERE clause for GROUP BY results.
Why HAVING exists
WHERE filters individual rows before grouping. Once groups are formed, WHERE can't filter them because the aggregate values (SUM, COUNT…) don't exist yet. HAVING filters groups after they've been computed.
Basic example
SQL — customers with more than 2 orders
SELECT customer_id,
COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 2
ORDER BY order_count DESC;
WHERE vs HAVING
| Aspect | WHERE | HAVING |
|---|---|---|
| Runs when? | Before GROUP BY | After GROUP BY |
| Filters | Individual rows | Groups (aggregate results) |
| Can use aggregates? | ❌ No | ✅ Yes |
| Typical use | Filter raw data | Filter summarized data |
Combining WHERE and HAVING
SQL — UK customers with total spend > 500
SELECT c.name,
SUM(o.amount) AS total_spent
FROM customers c
JOIN orders o ON c.id = o.customer_id
WHERE c.country = 'UK' -- 1. Filter rows BEFORE grouping
GROUP BY c.name -- 2. Group remaining rows
HAVING SUM(o.amount) > 500 -- 3. Filter groups AFTER aggregation
ORDER BY total_spent DESC;
More HAVING examples
SQL — products sold in more than 3 cities
SELECT o.product,
COUNT(DISTINCT c.city) AS city_count
FROM orders o
JOIN customers c ON o.customer_id = c.id
GROUP BY o.product
HAVING COUNT(DISTINCT c.city) > 3;
SQL — find duplicate emails
SELECT email, COUNT(*) AS occurrences
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;
Full execution order
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.
Put it on a sticky note. Interviewers love asking about this.