📊 PostgreSQL – Filtering Groups with HAVING
As a former 3D Animator with more than 12 years of experience, I have always been fascinated by the intersection of technology and creativity. That's why I recently shifted my career towards MERN stack development and software engineering, where I have been serving since 2021.
With my background in 3D animation, I bring a unique perspective to software development, combining creativity and technical expertise to build innovative and visually engaging applications. I have a passion for learning and staying up-to-date with the latest technologies and best practices, and I enjoy collaborating with cross-functional teams to solve complex problems and create seamless user experiences.
In my current role as a MERN stack developer, I have been responsible for developing and implementing web applications using MongoDB, Express, React, and Node.js. I have also gained experience in Agile development methodologies, version control with Git, and cloud-based deployment using platforms like Heroku and AWS.
I am committed to delivering high-quality work that meets the needs of both clients and end-users, and I am always seeking new challenges and opportunities to grow both personally and professionally.
So far in SQL, we’ve learned these keywords:
FROM
JOIN
WHERE
GROUP BY
Now we’re introducing a new keyword:
🔥 HAVING
At first, HAVING looks very similar to WHERE.
But there is one very important difference.
🧠 WHERE vs HAVING (Very Important)
| Keyword | Filters What? |
|---|---|
| WHERE | Filters rows |
| HAVING | Filters groups |
This is the most important rule.
🧩 Order of SQL Execution
When writing grouped queries, SQL follows this order:
SELECT
FROM
JOIN
WHERE
GROUP BY
HAVING
Notice something important:
👉 HAVING always comes after GROUP BY.
And here’s another rule:
You will never see HAVING without GROUP BY.
You can use GROUP BY without HAVING.
But if you want to filter groups, you must use HAVING.
🎯 Practical Example
Let’s solve a real problem.
🧠 Goal:
Find the number of comments for each photo
Where:
The photo_id is less than 3
The photo has more than 2 comments
Notice something interesting:
There are two different filters here.
🔎 First Filter: photo_id < 3
This filter checks a condition on individual rows.
That means we use:
WHERE
Because WHERE filters rows.
🔎 Second Filter: Photo has more than 2 comments
This requires:
GROUP BY photo_id
COUNT(*)
Filtering based on COUNT
Since COUNT is an aggregate function, this filter must use:
HAVING
Because HAVING filters groups after aggregation.
🧠 Step-by-Step Mental Model
Imagine our comments table:
| id | photo_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 3 |
| 6 | 4 |
Step 1️⃣ WHERE Phase (Row Filtering)
We apply:
WHERE photo_id < 3
Now we remove rows where photo_id is 3 or 4.
Remaining rows:
| id | photo_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
Step 2️⃣ GROUP BY Phase
We group by photo_id.
Group 1:
- 3 comments
Group 2:
- 1 comment
Step 3️⃣ HAVING Phase (Group Filtering)
We apply:
HAVING COUNT(*) > 2
Group 1 → 3 comments ✅ keep
Group 2 → 1 comment ❌ remove
Final result:
| photo_id | count |
|---|---|
| 1 | 3 |
🧾 Final Query
Here is the complete SQL query:
SELECT photo_id, COUNT(*)
FROM comments
WHERE photo_id < 3
GROUP BY photo_id
HAVING COUNT(*) > 2;
🧠 Why Not Use WHERE Instead of HAVING?
This will NOT work:
WHERE COUNT(*) > 2
Because:
WHERE runs before GROUP BY
COUNT() doesn’t exist yet at that stage
Aggregation happens after grouping.
That’s why HAVING exists.
🏗 Execution Flow (Super Important Concept)
Here’s how PostgreSQL processes it internally:
FROM → get table
WHERE → filter rows
GROUP BY → create groups
COUNT/SUM/etc → calculate aggregates
HAVING → filter groups
SELECT → return final result
🔥 When Do We Use HAVING?
Use HAVING when:
You are using GROUP BY
You want to filter based on an aggregate function
You need conditions like:
COUNT(*) > 5
SUM(amount) > 1000
AVG(score) > 70
🧠 Real-World Business Examples
🛒 Find customers who placed more than 5 orders:
SELECT user_id, COUNT(*)
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 5;
💰 Find products generating more than $10,000 revenue:
SELECT product_id, SUM(price)
FROM sales
GROUP BY product_id
HAVING SUM(price) > 10000;
🎯 Final Summary
WHERE:
Filters individual rows.
GROUP BY:
Creates groups.
HAVING:
Filters groups after aggregation.
🧪 Practice Questions for Students
Find departments with more than 10 employees.
Find users whose total spending is greater than 5000.
Find cities where average salary is above 100,000.
Find products sold more than 50 times.