Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

Β© 2026 Fun with Learning TechnologyRSS
Homeβ€ΊCoursesβ€ΊSQLβ€ΊGROUP BY and HAVING

Aggregation

GROUP BY and HAVING

GROUP BY and HAVING are SQL clauses used to transform raw, individual rows of data into summarized, meaningful insights. These tools allow you to categorize data based on shared attributes and filter those results based on calculated metrics rather than raw values. You reach for these when you need to answer analytical questions, such as identifying trends or finding outliers within specific segments of your database.

Understanding the GROUP BY Mechanism

The GROUP BY clause is fundamentally about collapsing individual rows into buckets. Imagine you have a table of sales; each row is a separate transaction. When you execute a query with GROUP BY, the database engine scans the entire specified column and identifies all unique values present. It then creates a virtual 'group' for every unique value encountered. Once these buckets are established, any aggregate function you apply, such as SUM, COUNT, or AVG, is executed within the scope of these individual groups rather than across the entire dataset. This is why you must include all non-aggregated columns in your GROUP BY clauseβ€”the engine needs a deterministic way to label the resulting rows. Without this grouping, the database has no reference point to calculate values for specific categories, turning a distinct set of rows into a single summary entry for every identified key category.

-- Calculate total sales amount per product category
SELECT category, SUM(amount) AS total_revenue
FROM sales
GROUP BY category;

Applying Aggregates to Groups

Once the data is segmented into buckets, aggregate functions act as the mathematical bridge between raw individual data points and the summary values you intend to report. It is critical to grasp that aggregates like COUNT, MIN, MAX, SUM, and AVG ignore NULL values by default, except for COUNT(*), which tallies every row within the group. The logic here is that the database evaluates the entire contents of a group before producing a single result per category. If you attempt to reference a column in your SELECT list that is not part of an aggregate function and is not present in the GROUP BY clause, the database will raise an error because it cannot mathematically determine which individual row's value to represent for that group. By using aggregates, you effectively condense the vertical depth of your table, transforming potentially millions of rows into a succinct summary that reflects the core metrics of your defined segments.

-- Calculate the average age and count of users per city
SELECT city, AVG(age) AS avg_age, COUNT(user_id) AS total_users
FROM users
GROUP BY city;

The Role of HAVING in Post-Aggregation Filtering

Many beginners struggle to differentiate between WHERE and HAVING. The key distinction lies in the timing of the filtration process relative to the grouping operation. The WHERE clause acts as a gatekeeper, filtering individual rows before they ever enter the grouping phase; it is entirely unaware of aggregates. Conversely, the HAVING clause acts as a filter for the groups themselves. Once the database has processed the GROUP BY clause and computed the aggregate values for each category, it evaluates the conditions specified in the HAVING clause to determine which groups should be included in the final output. Think of it as a secondary audit: you group the data to see the 'big picture' of each category, then use HAVING to discard any categories that fail to meet your performance thresholds, such as a minimum revenue requirement or a maximum count limit.

-- Show only departments with a total salary expenditure exceeding $50,000
SELECT department_id, SUM(salary) AS total_payroll
FROM employees
GROUP BY department_id
HAVING SUM(salary) > 50000;

Handling Multiple Grouping Keys

You are not limited to grouping by a single column. Grouping by multiple columns creates a hierarchical classification system. When you specify multiple columns in your GROUP BY clause, the database considers the combination of those values as a unique identifier for a group. For instance, grouping by 'region' and 'month' means the database will create a unique bucket for every distinct pair of region and month. Every row that shares that specific combination is rolled into that bucket. This is incredibly powerful for time-series analysis or regional performance tracking. By expanding the number of columns in your grouping, you increase the granularity of your reports. Always ensure that the order of the columns in your SELECT clause matches the logical structure of your investigation, as this dictates how the result set is organized, helping you visualize the data in a hierarchical format that is easy to read and interpret for stakeholders.

-- Total orders by region and status
SELECT region, status, COUNT(order_id) AS count
FROM orders
GROUP BY region, status;

Ordering Aggregated Results

After performing grouping and filtering, the resulting dataset is often disorganized. To make the findings actionable, you need to apply an ORDER BY clause. Crucially, the ORDER BY clause is evaluated after both the GROUP BY and the HAVING clauses. This means you can order your final results using the aliases created in your SELECT clause or the results of the aggregate functions themselves. For example, if you want to see the highest-performing categories first, you can sort by the sum calculated during the aggregation. Because the database has already completed the heavy lifting of grouping and filtering, the sorting operation is straightforward and efficient. This final step allows you to rank, compare, and prioritize your findings, turning a large list of summarized data into an ordered report that clearly highlights the most important segments within your dataset, ensuring your SQL queries provide immediate, sorted value.

-- Get top 3 categories by sales volume
SELECT category, SUM(amount) AS total_sales
FROM sales
GROUP BY category
HAVING SUM(amount) > 1000
ORDER BY total_sales DESC
LIMIT 3;

Key points

  • GROUP BY organizes raw table rows into distinct categories based on shared values.
  • Aggregate functions like SUM and COUNT are performed independently within each defined group.
  • Columns present in the SELECT list that are not aggregated must be included in the GROUP BY clause.
  • The WHERE clause filters rows before grouping occurs, while HAVING filters groups after they are formed.
  • Multiple grouping keys allow for hierarchical, multi-dimensional analysis of your data segments.
  • Aggregate functions automatically ignore NULL values, except for the COUNT(*) function.
  • The ORDER BY clause is applied last, allowing you to rank your aggregated output effectively.
  • Having a clear understanding of the logical query processing order is essential for writing correct SQL.

Common mistakes

  • Mistake: Including non-aggregated columns in the SELECT clause that are not in the GROUP BY clause. Why it's wrong: SQL engines cannot determine which value to display for a non-aggregated column if multiple rows share the same group. Fix: Ensure every column in the SELECT clause is either wrapped in an aggregate function or listed in the GROUP BY clause.
  • Mistake: Using aggregate functions like SUM or COUNT in the WHERE clause. Why it's wrong: The WHERE clause filters rows *before* the aggregation process occurs. Fix: Use the HAVING clause to filter based on the results of aggregate functions.
  • Mistake: Confusing the order of operations by placing HAVING before GROUP BY. Why it's wrong: The database needs to organize data into groups before it can evaluate conditions on those specific groups. Fix: Follow the logical order of operations: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY.
  • Mistake: Thinking that GROUP BY is required whenever an aggregate function is used. Why it's wrong: Aggregate functions can be used on an entire table (e.g., SELECT COUNT(*) FROM table) without grouping. Fix: Only use GROUP BY when you need to perform calculations on subsets of the data defined by specific column values.
  • Mistake: Filtering rows with WHERE that could be filtered with HAVING (or vice versa). Why it's wrong: While sometimes interchangeable, using WHERE is more efficient as it reduces the data processed by the aggregation engine. Fix: Use WHERE for row-level filtering and HAVING only for filtering based on aggregate outcomes.

Interview questions

What is the fundamental difference between the WHERE clause and the HAVING clause in SQL?

The WHERE clause is used to filter individual rows before any grouping occurs, acting as a filter for the raw data source. In contrast, the HAVING clause is specifically designed to filter the results after the GROUP BY operation has aggregated the data. Think of WHERE as a pre-filter for your table, and HAVING as a post-filter for your aggregated summaries. For example, if you want to find employees with a salary over 50,000, you use WHERE. If you want to find departments with an average salary over 50,000, you must use HAVING.

Can you explain why we need to use a GROUP BY clause when using aggregate functions?

When you use aggregate functions like COUNT, SUM, or AVG, SQL needs to know how to bundle the rows together to produce a single result. Without a GROUP BY clause, these functions operate on the entire result set, returning one single row. When you include a GROUP BY clause, you are telling the database to partition the data into subsets based on the unique values in the specified columns. Every non-aggregated column in your SELECT statement must be present in the GROUP BY clause to ensure that the database knows exactly which value to display for each specific group.

If I want to count the number of orders per customer, how would I structure that query?

To achieve this, you would select the customer identifier and the COUNT of the order primary key. You would then need to group by the customer identifier. The query would look like this: SELECT customer_id, COUNT(order_id) AS total_orders FROM orders GROUP BY customer_id; This works because the GROUP BY clause gathers all records belonging to a single customer ID into one bucket, allowing the COUNT function to calculate the number of orders associated with that specific ID, effectively returning a list of customers alongside their respective order counts.

Is it possible to use both a WHERE clause and a HAVING clause in the same query, and if so, how does that work?

Yes, it is entirely possible and often necessary to use both. The order of execution is critical here. First, the WHERE clause filters the rows from the source tables. Second, the remaining rows are passed to the GROUP BY clause to be aggregated. Finally, the HAVING clause filters those groups based on the aggregated results. An example would be: SELECT department, AVG(salary) FROM employees WHERE status = 'Active' GROUP BY department HAVING AVG(salary) > 60000; Here, we ignore inactive employees first, group the active ones by department, and then finally exclude departments that do not meet our average salary threshold.

Compare the performance and usage of filtering rows using WHERE versus HAVING. Is one always better than the other?

You should prioritize the WHERE clause whenever possible because it reduces the number of rows being processed before the computationally expensive grouping operation occurs. Filtering early is a performance best practice. HAVING should only be used when you need to filter based on aggregated data, such as counts or averages, because those calculations do not exist until after the GROUP BY has been performed. If you can answer your business question using only WHERE, do not use HAVING, as filtering after aggregation is always more resource-intensive for the database engine.

How does the grouping process handle NULL values in the columns specified in the GROUP BY clause?

In SQL, NULL values are treated as a distinct group. If you perform a GROUP BY on a column that contains multiple NULL values, the database will collect all of those rows into one single group labeled as NULL. This is important to remember because it differs from how aggregate functions typically ignore NULLs inside them. For example, if you run a GROUP BY on a 'category' column, the final output will include a specific row for the NULL category containing all the records that did not have a valid category assigned, which can lead to unexpected results if your data cleanup is incomplete.

All SQL interview questions β†’

Check yourself

1. Which of the following scenarios best justifies the use of a HAVING clause instead of a WHERE clause?

  • A.Removing rows where the user's age is less than 18.
  • B.Filtering results to show only departments with a total salary expenditure exceeding $100,000.
  • C.Limiting the result set to rows created within the current calendar year.
  • D.Excluding records that contain NULL values in a specific column.
Show answer

B. Filtering results to show only departments with a total salary expenditure exceeding $100,000.
Filtering by total salary (an aggregate) requires HAVING because the total is calculated after grouping. The other options are row-level filters that should be handled by WHERE for better performance.

2. Consider a table of sales. Which query will successfully identify products that have been sold more than 50 times in total?

  • A.SELECT product_id FROM sales WHERE COUNT(product_id) > 50 GROUP BY product_id
  • B.SELECT product_id FROM sales GROUP BY product_id HAVING COUNT(product_id) > 50
  • C.SELECT product_id FROM sales HAVING COUNT(product_id) > 50
  • D.SELECT product_id FROM sales GROUP BY product_id WHERE COUNT(product_id) > 50
Show answer

B. SELECT product_id FROM sales GROUP BY product_id HAVING COUNT(product_id) > 50
Correct structure is GROUP BY followed by HAVING for the aggregate condition. The first is wrong because WHERE cannot contain aggregates; the third is wrong because HAVING needs a GROUP BY; the fourth has the clauses in the incorrect order.

3. What happens if you run: SELECT category, SUM(price) FROM products GROUP BY category WHERE price > 10?

  • A.It returns the sum of prices for each category for products priced over 10.
  • B.It returns the sum of prices for categories where the total sum is over 10.
  • C.It generates a syntax error because WHERE must appear before GROUP BY.
  • D.It returns an error because price is not an aggregate function.
Show answer

C. It generates a syntax error because WHERE must appear before GROUP BY.
SQL requires the WHERE clause to come before the GROUP BY clause. The others describe logical outcomes that won't happen because the query is syntactically invalid.

4. If you want to group by 'department' and display the average salary, which SELECT statement is valid?

  • A.SELECT department, name, AVG(salary) FROM employees GROUP BY department
  • B.SELECT department, AVG(salary) FROM employees GROUP BY department
  • C.SELECT department, AVG(salary) FROM employees
  • D.SELECT department, AVG(salary) FROM employees GROUP BY salary
Show answer

B. SELECT department, AVG(salary) FROM employees GROUP BY department
Option 2 is valid because every non-aggregated column in the SELECT is included in the GROUP BY. Option 1 is invalid due to 'name', 3 is invalid due to missing GROUP BY, and 4 groups by the wrong column.

5. When is an alias defined in the SELECT clause accessible in the HAVING clause?

  • A.Always, because the SELECT clause is processed before the HAVING clause.
  • B.Only when using specific database systems that support alias recognition in HAVING.
  • C.Never, because HAVING is logically evaluated before the SELECT clause in SQL.
  • D.Only if the alias is used inside an aggregate function.
Show answer

C. Never, because HAVING is logically evaluated before the SELECT clause in SQL.
Standard SQL evaluates SELECT after HAVING, so aliases are typically not available. While some engines allow it as an extension, it is not standard, making 'Only when using specific databases' the accurate technical assessment.

Take the full SQL quiz β†’

← PreviousAggregation Functions β€” COUNT, SUM, AVG, MIN, MAXNext β†’DISTINCT β€” Removing Duplicates

SQL

32 lessons, free to read.

All lessons β†’

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app