DEV Community

Solomon
Solomon

Posted on

WHERE vs HAVING in SQL: A Simple Way to Understand the Difference

If you’ve ever mixed up WHERE and HAVING, you’re not alone. They both filter data, so it feels like you should be able to use either one. But they filter at different moments, and once you get that, the confusion goes away.

The one-line answer
WHERE filters individual rows before any grouping happens.
HAVING filters groups after the grouping and math (SUM, COUNT, AVG) is done.

A Quick Analogy
Suppose there is an event in which the individuals attending are divided into tables:
WHERE is the bouncer at the door. Allows entry on a first-come, first-served basis, according to the individual person profile, i.e ('no under-18s').
HAVING is the host verifying the tables after everyone has been seated. If a table has less than 4 people, it is merged with another table. The host can only evaluate a table after it is created.

The bouncer can't tell if the table is good or bad, since there are no tables at the door. Because of this, aggregate functions such as SUM() and COUNT() cannot be used in WHERE.
We will use a small orders table to make it easy to verify the results in pgAdmin.

Step 1: WHERE filters rows
If we just want orders worth more than 100:

SELECT * FROM orders
WHERE amount > 100;
Enter fullscreen mode Exit fullscreen mode

You'll get 4 rows (500, 300, 120, 700). Postgres iterated through each row and asked, "Is this over 100?" Simple.

Step 2: GROUP BY

Now, let's take a look at the total amount spent by each customer so we have something to filter later.

SELECT customer, SUM(amount) AS total_spent
FROM orders
GROUP BY customer;
Enter fullscreen mode Exit fullscreen mode

You should see:

Step 3: HAVING filters groups
What if we just want the customers who spent more than 200 in total?

SELECT customer, SUM(amount) AS total_spent
FROM orders
GROUP BY customer
HAVING SUM(amount) > 200;
Enter fullscreen mode Exit fullscreen mode

Chloe (180) disappears. In this case, we are filtering the totals, not the rows.

Step 4: Using both together
This is where it comes home to me. Let's say we want the total money spent per customer, but only those that have completed orders and only those whose total money spent is greater than 200:

SELECT customer, SUM(amount) AS total_spent
FROM orders
WHERE status = 'completed'
GROUP BY customer
HAVING SUM(amount) > 200;
Enter fullscreen mode Exit fullscreen mode

Results:

Notice what changed compared to Step 3. David is gone, because his big 700 order was cancelled, so WHERE threw it out before the math happened. His completed total is only 60. Brian’s total also dropped from 380 to 300 because his cancelled order was removed.

Why the order matters

SQL doesn’t run your query in the order you write it. It runs roughly like this:

FROM (pick the table)
WHERE (filter rows)
GROUP BY (make the groups)
HAVING (filter groups)
SELECT (pick the columns)
ORDER BY (sort)

WHERE comes before GROUP BY, so it can only see raw rows. HAVING comes after, so it can see the group totals. That one fact explains everything.

Top comments (0)