Home/Learn/MySQL/HAVING Clause

HAVING Clause

Beginner
Joins & Set Ops

HAVING 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.

SQL — HAVING with GROUP BY and aggregates
-- 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.

SQL — WHERE vs HAVING best practices
-- ✗ 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.

SQL — aggregate functions and ROLLUP
-- 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 Aria
1

What is the difference between WHERE and HAVING?

EasyInfosys
2

Can you use HAVING without GROUP BY?

MediumTCS
3

Why is HAVING COUNT(*) > 5 more appropriate than WHERE COUNT(*) > 5?

EasyWipro
4

What is the performance difference between filtering with WHERE vs HAVING?

MediumAmazon
5

What does COUNT(*) vs COUNT(column) return differently for NULL values?

MediumFlipkart

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.

Loading discussion…