GROUP BY with HAVING: Filter Aggregated Results in SQL

โฑ๏ธ 28 sec read ๐Ÿ“Š SQL

Use GROUP BY to collapse rows into groups for COUNT, SUM, or AVG. Add HAVING only when you need to filter those groups by an aggregate value. WHERE is different: it filters individual rows before any grouping happens.

The Key Difference

WHERE filters rows before grouping:

SELECT department, COUNT(*) as emp_count
FROM employees
WHERE salary > 50000  -- Filters before grouping
GROUP BY department;

HAVING filters groups after aggregation:

SELECT department, COUNT(*) as emp_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 10;  -- Filters after grouping

When to Use Each

Use WHERE for conditions on individual row values and HAVING for conditions on aggregate values.

Combining Both

A query can use both. Clause order is fixed: WHERE comes before GROUP BY, and HAVING comes after it. WHERE filters rows, GROUP BY groups what is left, and HAVING filters the groups.

-- Find departments with avg salary > 75k among employees making > 40k
SELECT department, AVG(salary) as avg_salary
FROM employees
WHERE salary > 40000        -- Filter rows first
GROUP BY department
HAVING AVG(salary) > 75000; -- Filter groups second

Common Use Cases

A typical HAVING filter compares an aggregate to a threshold, such as a minimum order count, or a count greater than 1 to find duplicates.

Find High-Volume Customers

SELECT customer_id, COUNT(*) as order_count, SUM(total) as revenue
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY customer_id
HAVING COUNT(*) >= 5 OR SUM(total) >= 10000;

Identify Duplicate Records

SELECT email, COUNT(*) as duplicate_count
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

Performance Tip: Use WHERE for row-level conditions so fewer rows reach GROUP BY. As written, a non-aggregate condition in HAVING is applied only after every row has been grouped.

โ† Back to SQL Tips