(With diagrams, ilustrationsn and practice challenges)
(With diagrams, illustrations, and extra challenges)
This article explores how SQL filters data using WHERE and HAVING, how aggregation works, and when to use each clause. It also includes diagrams, examples, and practice challenges.
The WHERE clause filters individual rows before any grouping or aggregation happens.
Raw Table Rows
|
v
+-----------+
| WHERE | <-- filters rows BEFORE grouping
+-----------+
|
v
(Filtered Rows)
SELECT product_name, price
FROM products
WHERE price > 300;
Aggregation combines multiple rows into summary values (SUM, COUNT, AVG, etc.).
Filtered Rows
|
v
+-------------+
| GROUP BY |
+-------------+
|
v
Aggregated Results
WHERE cannot filter aggregated values. That’s where HAVING comes in.
Raw Rows
|
v
+--------+
| WHERE |
+--------+
|
v
Filtered Rows
|
v
+-----------+
| GROUP BY |
+-----------+
|
v
Aggregated Rows
|
v
+--------+
| HAVING | <-- filters AFTER aggregation
+--------+
SELECT customer_id, SUM(amount) AS total_spent
FROM sales
GROUP BY customer_id
HAVING SUM(amount) > 150;
+------------------------------------+
| WHERE CLAUSE |
|------------------------------------|
| Filters single rows |
| Cannot use aggregates |
| Runs BEFORE group-by |
+------------------------------------+
+------------------------------------+
| HAVING CLAUSE |
|------------------------------------|
| Filters aggregated data |
| Uses COUNT, SUM, etc |
| Runs AFTER group-by |
+------------------------------------+
| Situation | Use WHERE | Use HAVING |
|---|---|---|
| Filter rows by raw values | ✔ | ❌ |
| Filter groups by aggregated values | ❌ | ✔ |
| Exclude unnecessary rows early | ✔ | ❌ |
| Filter based on COUNT(), SUM() | ❌ | ✔ |
Table: orders(customer_id, order_amount, status)
Task: Show customers who:
SELECT customer_id,
COUNT(*) AS num_orders,
SUM(order_amount) AS total_spent
FROM orders
WHERE status = 'Completed'
GROUP BY customer_id
HAVING COUNT(*) > 3
AND SUM(order_amount) > 500;
Table: products(category, price)
Task: List categories whose average product price is between £200 and £500.
SELECT category, AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING AVG(price) BETWEEN 200 AND 500;
Table: sales(store, amount, date)
Task: Show stores that had at least 10 sales above £50 in the month of January.
SELECT store,
COUNT(*) AS count_sales
FROM sales
WHERE amount > 50
AND date BETWEEN '2025-01-01' AND '2025-01-31'
GROUP BY store
HAVING COUNT(*) >= 10;