Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 24

Filtering Grouped Data with HAVING

Recommended resources

Hello! Welcome to your next lesson on SQL.

In our previous lessons, we've built a solid foundation. You learned to group data using GROUP BY to perform calculations with aggregate functions like COUNT() and SUM(), and how to use DISTINCT to remove duplicate results.

As we previewed last time, a key question remained: how do you filter the results of an aggregation? For instance, how do you find all departments with an average salary over $50,000, or all product categories that have fewer than 10 items? The WHERE clause can't help here, as it filters individual rows before they are grouped and aggregated.

Today, we'll answer that question by introducing the HAVING clause. Your learning outcome is to: Filter grouped data using the HAVING clause. We'll explore its syntax, see how it works, and most importantly, understand why it works by looking at the logical order of how a SQL query is executed. This "under the hood" view aligns with your interest in system-level logic.

The Problem: Filtering on Aggregated Values

Let's start with a common business request: "Show me a list of all countries where we have more than 5 customers."

You know how to count customers per country:

SELECT
  country,
  COUNT(*) AS customer_count
FROM customers
GROUP BY country;

A natural first attempt to filter this might be to use a WHERE clause, like this:

-- This query will fail!
SELECT
  country,
  COUNT(*) AS customer_count
FROM customers
WHERE COUNT(*) > 5 -- You cannot use an aggregate function here
GROUP BY country;

This query will produce an error. The reason is fundamental to how SQL works: the WHERE clause is processed before the GROUP BY clause and the aggregate functions are calculated. At the moment WHERE is doing its job, the database is looking at individual rows and has not yet calculated COUNT(*).


The Solution: The HAVING Clause

This is precisely the problem the HAVING clause was designed to solve. HAVING is used to filter data after it has been grouped by GROUP BY and the aggregate functions have been calculated.

The correct query for our request would be:

SELECT
  country,
  COUNT(*) AS customer_count
FROM customers
GROUP BY country
HAVING COUNT(*) > 5; -- The condition is applied to the grouped results

This query works because the HAVING clause is evaluated after the rows have been grouped by country and the COUNT(*) for each group has been computed.

Let's see a practical demonstration. This video provides a clear business scenario for using HAVING and shows the error that occurs when you try to use an aggregate function in WHERE.

Advanced Aggregate Functions in SQL (GROUP BY, HAVING vs. WHERE)

Watch this segment from the video 'Advanced Aggregate Functions in SQL' by Becoming a Data Scientist. It introduces the HAVING clause and contrasts it with WHERE.

Watch from 02:40 to 04:29. Pay close attention to the business logic for the filter (sending salespeople to cities with multiple customers) and the error message that appears when trying to put COUNT() in the WHERE clause.


The "Why": Understanding the Logical Order of SQL Operations

Your interest in low-level systems and computational thinking makes this the perfect time to go deeper. The reason WHERE and HAVING behave so differently comes down to the logical order of operations in a SQL query, which is not the same as the order in which you write the clauses (the lexical order).

For a developer, understanding this logical sequence is a high-ROI skill that demystifies many seemingly strange SQL behaviors.

A Beginner's Guide to the True Order of SQL Operations

The article 'A Beginner's Guide to the True Order of SQL Operations' from the jOOQ blog provides an excellent explanation of this concept. It breaks down the logical processing steps of a SQL query.

Please read the section that begins with the heading 'Can this ever be understood?'. Focus on the bulleted list describing the logical order, from FROM to HAVING.

As the article explains, the simplified logical order is:

  1. FROM: Load the tables.
  2. WHERE: Filter individual rows based on conditions.
  3. GROUP BY: Arrange the filtered rows into groups.
  4. aggregations: Compute aggregate functions (like COUNT(), SUM()) for each group.
  5. HAVING: Filter the groups themselves based on the results of the aggregations.
  6. SELECT: Determine the final columns to output.
  7. ORDER BY: Sort the final result set.

This logical flow makes it clear: WHERE finishes its work at step 2, long before the aggregates are computed in step 4. HAVING, on the other hand, operates at step 5, so it has full access to the aggregated values.

This flowchart provides a great visual summary of the process:

SQL Query Execution Order
This flowchart illustrates the logical sequence of operations in a SQL query, showing that `WHERE` filtering happens before `GROUP BY`, and `HAVING` filtering happens after.

Using WHERE and HAVING Together

You don't have to choose between WHERE and HAVING; you can, and often will, use them together in the same query. This allows you to perform two distinct levels of filtering:

  • WHERE pre-filters the individual rows that go into the aggregation.
  • HAVING post-filters the final groups based on the aggregated results.

Example Scenario: "From all sales transactions in 'Canada', find the cities where the total sales amount is greater than $1,000."

SELECT
  city,
  SUM(sale_amount) AS total_sales
FROM sales
WHERE country = 'Canada'    -- Step 1: Filter for rows from Canada only.
GROUP BY city               -- Step 2: Group the Canadian sales by city.
HAVING SUM(sale_amount) > 1000; -- Step 3: Filter for groups (cities) with total sales > 1000.

The next video demonstrates this combined usage effectively.

Advanced Aggregate Functions in SQL (GROUP BY, HAVING vs. WHERE)

Let's return to the 'Advanced Aggregate Functions in SQL' video to see how WHERE and HAVING can be combined in a single query.

Watch from 04:29 to 05:19. The query in the video filters for countries containing the letter 'a' (WHERE) and then filters for groups that have less than three customers (HAVING).

Test your understanding!

You have a service_tickets table with columns: ticket_id, agent_name, priority ('Low', 'Medium', 'High'), and customer_id.

Your task is to find the names of support agents who have handled more than 10 'High' priority tickets.

How would you write this query? Think about which clause filters the rows first and which clause filters the final groups.

Show answer
SELECT
  agent_name
FROM service_tickets
WHERE priority = 'High'
GROUP BY agent_name
HAVING COUNT(ticket_id) > 10;

Explanation:

  1. WHERE priority = 'High' first selects only the rows for high-priority tickets.
  2. GROUP BY agent_name then groups these rows by agent.
  3. HAVING COUNT(ticket_id) > 10 finally filters these groups, keeping only the agents who have a count of more than 10 in their group.

Summary: WHERE vs. HAVING

To solidify your understanding, here is a concise breakdown of the key differences. This is a very common topic in technical interviews, so mastering this distinction has a high ROI for your job preparation goal.

Feature WHERE HAVING
Purpose Filters individual rows Filters groups of rows
When it runs Before GROUP BY After GROUP BY
Use with aggregates? No (e.g., COUNT(), SUM()) Yes
Primary Use Case Filtering data based on the value in a row (e.g., country = 'USA') Filtering data based on an aggregate property of a group (e.g., AVG(price) > 100)

Conclusion

You have now learned how to use the HAVING clause, a critical tool for any meaningful data analysis in SQL. More importantly, you've learned why it's necessary by understanding the logical order of query execution.

Key Takeaways:

  • The HAVING clause is used to filter query results based on aggregate functions.
  • The logical order of a query is FROM -> WHERE -> GROUP BY -> HAVING.
  • Because WHERE is processed before GROUP BY, it cannot be used with aggregate functions.
  • Because HAVING is processed after GROUP BY, it can be used with aggregate functions.
  • WHERE and HAVING can be used together to apply both row-level and group-level filtering in a single query.

Preview of the Next Lesson:
Today, you learned the mechanics of HAVING and its relationship to WHERE. The next lesson in the syllabus is dedicated to mastering the distinction between them. Since this is such a crucial concept for both practical use and job interviews, we will dedicate the next session to reinforcing this knowledge with a variety of practice problems and scenarios. This will ensure you can confidently decide when to use WHERE, when to use HAVING, and when to use both.