Photo by Rubaitul Azad / Unsplash

🧠 SQL: WHERE vs HAVING — And Why One Is Seriously Underrated

Database Jul 25, 2025

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 and HAVING
  • 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_ids 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 using MAX() (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?WHEREIndividual rows (raw data)Before GROUP BY❌ NoHAVINGAggregated 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 month
  • HAVING 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.

Tags