🧠SQL: WHERE vs HAVING — And Why One Is Seriously Underrated
When we start writing SQL, most of us quickly get comfortable with SELECT
, FROM
, and of course, the beloved WHERE
clause. It's like the default filter button for any query.
But there’s another clause that often hides in plain sight: HAVING
.
At first glance, it looks similar to WHERE
. But the moment you start working with grouped or aggregated data — think COUNT
, SUM
, AVG
, etc. — HAVING
becomes your best friend. The problem? Most people don’t even realize they need it until something breaks.
In this post, let’s break down:
- The exact difference between
WHERE
andHAVING
- Real-world use cases from SaaS, FinTech, EdTech
- A developer case study to show where
HAVING
is essential - And how combining both can unlock serious SQL power
💡 A Realistic Use Case — Grouping + Filtering for Pending Assignment
Imagine you’re building a system that manages shift assignments for tasks. Each task can require multiple people to complete it — let’s say, 5 drivers per delivery task.
So, your table might look like this:
task_iddriver_idstatusrequired_slots1011approved51012approved51013pending51024approved21025approved21036approved3
Now, here’s the real-world question:
🔍 How do we find all task_id
s that haven’t yet received enough approved assignments?
Here’s how we solve it using the HAVING
clause:
SELECT task_id,
COUNT(*) FILTER (WHERE status = 'approved') AS approved_count,
MAX(required_slots) AS required
FROM assignments
GROUP BY task_id
HAVING COUNT(*) FILTER (WHERE status = 'approved') < MAX(required_slots);
🧠What’s happening here:
- We
GROUP BY task_id
to collect bookings under the same task. - We count only the approved bookings.
- We pull
required_slots
usingMAX()
(since it’s same for all rows under a task). - Then we filter only those groups where approved bookings < required slots.
âś… This is exactly the kind of thing HAVING
is meant for — filtering after aggregation.
⚔️ WHERE
vs HAVING
: What's the Real Difference?
Let’s clarify it visually:
ClauseFilters…When It RunsWorks With Aggregates?WHERE
Individual rows (raw data)Before GROUP BY
❌ NoHAVING
Aggregated groupsAfter GROUP BY
âś… Yes
🛍️ Use Case 1: E-commerce Orders
Let’s say you’re building a report that shows all recent orders placed in the last 30 days.
SELECT *
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days';
âś… You use WHERE
here because you're filtering raw row data — nothing's grouped yet.
🙌 Use Case 2: Find “Power Users”
Now let’s say your marketing team wants to run a campaign for users who’ve placed more than 10 orders.
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 10;
✅ Here’s where HAVING
shines. You’re using COUNT(*)
(an aggregate), so filtering after grouping is a must.
đź’Ľ Real-World Industry Use Cases
🔹 SaaS Dashboard: Teams With Growth
Goal: Show all teams that added more than 50 users this month.
SELECT team_id, COUNT(user_id) AS new_users
FROM users
WHERE created_at >= DATE_TRUNC('month', CURRENT_DATE)
GROUP BY team_id
HAVING COUNT(user_id) > 50;
📊 You’d use this in analytics dashboards, usage billing, or customer health scoring.
🔹 FinTech: High-Spending Customers
Goal: Identify customers who spent more than ₹1L (₹100,000) in the last 3 months.
SELECT customer_id, SUM(amount) AS total_spent
FROM transactions
WHERE created_at >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY customer_id
HAVING SUM(amount) > 100000;
đź’ł This is huge for loyalty programs, cashback eligibility, or even fraud detection alerts.
🔹 EdTech: Students Crushing It
Goal: Find students with an average quiz score greater than 80.
SELECT student_id, AVG(score) AS avg_score
FROM quiz_submissions
GROUP BY student_id
HAVING AVG(score) > 80;
🎓 This works great for performance insights, awarding badges, or triggering motivational emails.
đź§Ş Bonus Tip: Using WHERE
and HAVING
Together
They’re not mutually exclusive. In fact, they often work beautifully together.
SELECT product_id, COUNT(*) AS review_count
FROM reviews
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY product_id
HAVING COUNT(*) > 20;
WHERE
filters reviews submitted this monthHAVING
keeps only products with at least 20 reviews
Perfect for generating “Trending Products” or “Top Reviewed” lists.
đź§ Final Thoughts
Here’s the mindset shift:
- Use
WHERE
to filter before aggregation - Use
HAVING
to filter after aggregation - Use both together to write smarter, tighter, production-grade SQL
And yes — HAVING
is underrated, but once you start building real analytics, dashboards, and decision tools, it becomes a must-have.
💬 If this helped you rethink your SQL approach, give it a clap (or 5) and feel free to share it with your backend or data team. They’ll thank you for it.