HAVING Clause

Filter grouped results using the HAVING clause.

14 min read
Beginner

Introduction to HAVING

You've learned to use WHERE to filter individual rows and GROUP BY to create groups. But what if you want to filter groups based on aggregate calculations?

For example:

  • "Show categories with more than 5 products"
  • "Show departments with average salary > $80,000"
  • "Show customers who placed at least 3 orders"

You can't use WHERE for this because WHERE filters rows before grouping happens. You need HAVING to filter groups after aggregation!

Filters groups based on aggregate conditions. Works like WHERE, but operates on groups after GROUP BY, not on individual rows before grouping.

WHERE vs HAVING
Aspect
WHERE
HAVING
FiltersIndividual rowsGroups
WhenBefore GROUP BYAfter GROUP BY
Can use aggregates?NO ❌YES ✓
Example` WHERE price > 100 `` HAVING COUNT(*) > 5 `

Basic Syntax:

SELECT column, aggregate_function(column)
FROM table_name
GROUP BY column
HAVING aggregate_condition;

Let's work with our familiar tables:

products
id
product_name
category
price
stock
1LaptopElectronics999.9945
2MouseElectronics29.99120
3Desk ChairFurniture199.9930
4MonitorElectronics349.9960
5Desk LampFurniture39.9985
6KeyboardElectronics79.9995
7BookshelfFurniture159.9920
8Office ChairFurniture249.9915

Basic HAVING Examples

Problem: Show categories that have more than 3 products.

sql
-- Categories with more than 3 products
SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) > 3;

What happened:

  1. GROUP BY created two groups: Electronics (4 products) and Furniture (4 products)
  2. HAVING filtered to only groups where COUNT(*) > 3
  3. Both categories have exactly 4 products, but let's change the condition...
sql
-- Categories with at least 4 products
SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) >= 4;

Now both categories pass the filter!

Another example - average price:

sql
-- Categories with average price > $150
SELECT
  category,
  COUNT(*) AS products,
  ROUND(AVG(price), 2) AS avg_price
FROM products
GROUP BY category
HAVING AVG(price) > 150;

Electronics average is 364.99 (passes the filter). Furniture average is 162.49 (doesn't pass, so it's excluded).

WHERE vs HAVING - Understanding the Difference

Let's see the critical difference with a side-by-side comparison:

sql
-- WRONG: Can't use aggregate in WHERE
SELECT category, COUNT(*) AS product_count
FROM products
WHERE COUNT(*) > 3  -- ❌ ERROR!
GROUP BY category;
sql
-- CORRECT: Use HAVING for aggregate conditions
SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) > 3;  -- ✓ WORKS!

WHERE filters individual rows:

sql
-- WHERE: Filter rows where price > 100 (BEFORE grouping)
SELECT category, COUNT(*) AS product_count
FROM products
WHERE price > 100
GROUP BY category;

WHERE excluded Mouse (29.99) and Keyboard (79.99) and Desk Lamp (39.99) before grouping.

HAVING filters groups:

sql
-- HAVING: Filter groups with count > 2 (AFTER grouping)
SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) > 2;

HAVING excluded groups with 2 or fewer products (there are none in this case).

Combining WHERE and HAVING

You can use both WHERE and HAVING in the same query! WHERE filters rows first, then GROUP BY groups them, then HAVING filters the groups.

Execution order:

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
sql
-- Categories with >2 expensive products (price > 100)
SELECT category, COUNT(*) AS expensive_products
FROM products
WHERE price > 100        -- Filter: only expensive products
GROUP BY category        -- Group by category
HAVING COUNT(*) > 2;     -- Filter: categories with >2 products

Step-by-step:

  1. WHERE filters to products with price > 100: Laptop, Monitor, Desk Chair, Bookshelf, Office Chair
  2. GROUP BY groups these 5 products by category
    • Electronics: 2 products (Laptop, Monitor)
    • Furniture: 3 products (Desk Chair, Bookshelf, Office Chair)
  3. HAVING filters to groups with COUNT(*) > 2
    • Electronics (2) is excluded
    • Furniture (3) passes the filter

Another example:

employees
id
name
department
salary
years_experience
1AliceEngineering950005
2BobEngineering850003
3CharlieEngineering750002
4DianaSales700004
5EveSales750006
6FrankSales650001
7GraceMarketing800007
sql
-- Departments with 2+ experienced employees (3+ years) and avg salary > 70k
SELECT
  department,
  COUNT(*) AS experienced_employees,
  ROUND(AVG(salary), 2) AS avg_salary
FROM employees
WHERE years_experience >= 3  -- Only experienced employees
GROUP BY department
HAVING COUNT(*) >= 2         -- At least 2 employees
   AND AVG(salary) > 70000;  -- Average salary > 70k

WHERE filtered to 4 employees (Alice, Bob, Diana, Eve, Grace). Then grouped by department and filtered groups with HAVING.

HAVING with Multiple Conditions

Just like WHERE, you can use AND, OR, and multiple conditions in HAVING:

sql
-- Categories with 3+ products AND average price between 100-400
SELECT
  category,
  COUNT(*) AS product_count,
  ROUND(AVG(price), 2) AS avg_price,
  SUM(stock) AS total_stock
FROM products
GROUP BY category
HAVING COUNT(*) >= 3
   AND AVG(price) BETWEEN 100 AND 400;
sql
-- Categories with low count OR low total stock
SELECT
  category,
  COUNT(*) AS products,
  SUM(stock) AS total_stock
FROM products
GROUP BY category
HAVING COUNT(*) < 3
    OR SUM(stock) < 200;

Furniture has 4 products (doesn't meet COUNT(*) < 3) but total_stock is 150 (meets SUM(stock) < 200), so it passes the OR condition.

HAVING with ORDER BY

You can sort filtered groups with ORDER BY (which comes after HAVING):

sql
-- Departments with 2+ employees, sorted by average salary
SELECT
  department,
  COUNT(*) AS employees,
  ROUND(AVG(salary), 2) AS avg_salary
FROM employees
GROUP BY department
HAVING COUNT(*) >= 2
ORDER BY avg_salary DESC;

Full query order:

SELECT ...
FROM ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...
LIMIT ...

Using Column Aliases in HAVING

In some databases (like MySQL), you can use column aliases in HAVING:

sql
-- Using alias in HAVING (works in MySQL, not all databases)
SELECT
  category,
  COUNT(*) AS product_count,
  ROUND(AVG(price), 2) AS avg_price
FROM products
GROUP BY category
HAVING product_count > 3
   AND avg_price > 150;

Database compatibility: Some databases (PostgreSQL, Oracle) don't support aliases in HAVING. For maximum compatibility, use the full expression:

HAVING COUNT(*) > 3 AND AVG(price) > 150

Instead of:

HAVING product_count > 3 AND avg_price > 150

Practical Examples

Example 1: Find popular products

order_items
order_id
product_id
quantity
11012
21021
31013
41031
51011
61022
71045
sql
-- Products ordered more than 2 times
SELECT
  product_id,
  COUNT(*) AS times_ordered,
  SUM(quantity) AS total_quantity
FROM order_items
GROUP BY product_id
HAVING COUNT(*) > 2
ORDER BY times_ordered DESC;

Example 2: Active customers

orders
id
customer_id
order_date
total_amount
15012024-01-15150.00
25022024-01-20200.00
35012024-02-0175.00
45032024-02-05300.00
55012024-02-10125.00
sql
-- Customers with 2+ orders and total spending > $200
SELECT
  customer_id,
  COUNT(*) AS order_count,
  SUM(total_amount) AS total_spent,
  ROUND(AVG(total_amount), 2) AS avg_order
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 2
   AND SUM(total_amount) > 200
ORDER BY total_spent DESC;

Example 3: Department performance

sql
-- Large departments (5+ employees) with high avg salary (>75k)
SELECT
  department,
  COUNT(*) AS headcount,
  ROUND(AVG(salary), 2) AS avg_salary,
  MIN(salary) AS min_salary,
  MAX(salary) AS max_salary
FROM employees
WHERE salary >= 60000  -- Only consider employees earning 60k+
GROUP BY department
HAVING COUNT(*) >= 2
   AND AVG(salary) > 75000
ORDER BY avg_salary DESC;

Common Mistakes to Avoid

Don't make these mistakes:

  1. Using aggregates in WHERE: ❌

    • Wrong: WHERE COUNT(*) > 5
    • Right: HAVING COUNT(*) > 5
    • WHERE can't use aggregates!
  2. Using HAVING without GROUP BY: Usually wrong ❌

    • SELECT * FROM products HAVING price > 100
    • Should be: WHERE price > 100
    • HAVING is for filtering groups, not individual rows
  3. Filtering non-aggregates with HAVING: Works but confusing ❌

    • GROUP BY category HAVING category = 'Electronics'
    • Should be: WHERE category = 'Electronics' ... GROUP BY category
    • Use WHERE for row filters, HAVING for group filters
  4. Forgetting GROUP BY: ❌

    • SELECT category, COUNT(*) FROM products HAVING COUNT(*) > 3
    • Need GROUP BY before HAVING!
  5. Wrong execution order assumption: ❌

    • You can't reference a WHERE-filtered column in HAVING if it's been excluded
    • Understand: FROM → WHERE → GROUP BY → HAVING → SELECT
  6. Using SELECT aliases in HAVING (on some databases): ❌

    • Works in MySQL, fails in PostgreSQL
    • For portability, use full expression: HAVING AVG(price) > 100
  7. Confusing HAVING and WHERE: ❌

    • WHERE: "Filter rows before grouping"
    • HAVING: "Filter groups after aggregation"
    • Not interchangeable!

Decision Guide: WHERE vs HAVING

When to Use WHERE vs HAVING
Question
Use
Example
Filter individual rows?WHERE`WHERE price > 100`
Filter based on aggregates?HAVING`HAVING COUNT(*) > 5`
Filter before grouping?WHERE`WHERE status = 'active'`
Filter after grouping?HAVING`HAVING AVG(salary) > 75000`
No GROUP BY in query?WHERE`WHERE category = 'Electronics'`
Using COUNT, SUM, AVG, etc?HAVING`HAVING SUM(revenue) > 10000`

Key Takeaways

What you learned:

HAVING filters groups after GROUP BY and aggregation ✅ WHERE filters individual rows before grouping ✅ HAVING can use aggregate functions (COUNT, SUM, AVG, MIN, MAX) ✅ WHERE cannot use aggregate functions ✅ You can use both WHERE and HAVING in the same query ✅ Execution order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY ✅ HAVING supports multiple conditions with AND/OR ✅ Use WHERE for row filters, HAVING for group filters

Coming up next: We'll learn about JOINs to combine data from multiple tables!

Practice Exercise: Try these queries:

  1. Find categories with more than 5 products
  2. Find departments with average salary above $80,000
  3. Find customers who placed 3+ orders with total spending > $500
  4. Find products (grouped by category) where the most expensive item costs > $500
  5. Combine: Filter to active employees (WHERE), group by department, show departments with 3+ employees and avg salary > $75k (HAVING)