HAVING Clause
BeginnerHAVING filters groups after aggregation (unlike WHERE which filters rows before); it can reference aggregate expressions that are not valid in WHERE.
Overview
HAVING is used with GROUP BY to filter groups based on aggregate values (COUNT, SUM, AVG, MAX, MIN). It applies after aggregation — unlike WHERE which filters individual rows before grouping. HAVING can reference aggregate expressions that are not valid in WHERE. In MySQL 5.7+ you can reference SELECT aliases in HAVING (non-standard but supported). Always push row-level filters into WHERE before GROUP BY for performance — filtering rows early reduces the amount of data aggregated.
HAVING with Aggregates
GROUP BY groups rows; aggregate functions compute per-group statistics; HAVING filters the groups. Without GROUP BY, HAVING applies to the single group formed by all rows.
-- Find customers with more than 5 orders and total spend > $1000
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total) AS total_spent,
AVG(total) AS avg_order_value
FROM orders
WHERE status <> 'CANCELLED' -- ✓ filter rows BEFORE grouping (WHERE)
GROUP BY customer_id
HAVING order_count > 5 -- ✓ filter groups AFTER aggregating (HAVING)
AND total_spent > 1000
ORDER BY total_spent DESC;
-- Products with average rating below 3 and at least 10 reviews
SELECT
product_id,
AVG(rating) AS avg_rating,
COUNT(*) AS review_count
FROM reviews
GROUP BY product_id
HAVING avg_rating < 3.0 AND review_count >= 10;
-- HAVING without GROUP BY — rare but valid (entire table is one group)
SELECT COUNT(*) AS total FROM orders HAVING total > 1000;WHERE vs HAVING — Choosing the Right Clause
A common interview question: use WHERE for row-level conditions (before aggregation), HAVING for group-level conditions (after aggregation). Using HAVING instead of WHERE where possible is a performance anti-pattern.
-- ✗ Anti-pattern: HAVING instead of WHERE for non-aggregate filter
SELECT customer_id, COUNT(*) AS cnt
FROM orders
GROUP BY customer_id
HAVING customer_id > 100; -- scans ALL groups then filters — slow
-- ✓ Correct: use WHERE to push row filter before GROUP BY
SELECT customer_id, COUNT(*) AS cnt
FROM orders
WHERE customer_id > 100 -- filters rows first — fewer rows to aggregate
GROUP BY customer_id;
-- Both clauses together — very common pattern
SELECT
department_id,
COUNT(*) AS headcount,
AVG(salary) AS avg_salary
FROM employees
WHERE hire_date >= '2020-01-01' -- only recent hires (WHERE — row filter)
GROUP BY department_id
HAVING avg_salary > 60000 -- only high-paying depts (HAVING — group filter)
ORDER BY avg_salary DESC;Aggregate Functions Cheat Sheet
Core aggregate functions: COUNT, SUM, AVG, MIN, MAX. COUNT(*) counts all rows; COUNT(col) excludes NULLs. GROUP_CONCAT concatenates values within a group — useful for pivot-like results.
-- Aggregate functions overview
SELECT
category,
COUNT(*) AS total_products,
COUNT(discount_pct) AS products_with_discount, -- NULLs excluded
SUM(stock) AS total_stock,
AVG(price) AS avg_price,
MIN(price) AS cheapest,
MAX(price) AS most_expensive,
GROUP_CONCAT(name ORDER BY name SEPARATOR ', ')
AS product_names -- "Gadget, Widget, ..."
FROM products
GROUP BY category;
-- ROLLUP — adds subtotals and grand total
SELECT
COALESCE(category, 'ALL') AS category,
SUM(total) AS revenue
FROM orders
GROUP BY category WITH ROLLUP;
-- Outputs: Electronics | 50000
-- Books | 12000
-- ALL | 62000 ← ROLLUP-generated grand total
-- DISTINCT inside aggregate
SELECT COUNT(DISTINCT customer_id) AS unique_customers FROM orders;Key Points to Remember
- 1WHERE filters rows before GROUP BY; HAVING filters groups after aggregation.
- 2Always push non-aggregate conditions into WHERE for better performance.
- 3HAVING can reference aggregate expressions (COUNT(*), SUM()) that are invalid in WHERE.
- 4COUNT(*) includes NULL rows; COUNT(column) excludes NULLs.
- 5GROUP_CONCAT concatenates values within a group — useful for producing comma-separated lists.
- 6WITH ROLLUP adds subtotals and grand total rows to GROUP BY results.
Interview Questions
Sign in to ask AriaWhat is the difference between WHERE and HAVING?
Can you use HAVING without GROUP BY?
Why is HAVING COUNT(*) > 5 more appropriate than WHERE COUNT(*) > 5?
What is the performance difference between filtering with WHERE vs HAVING?
What does COUNT(*) vs COUNT(column) return differently for NULL values?
Ask Aria about HAVING Clause
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.