Skip to content
Feb 27

SQL Aggregation with GROUP BY and HAVING

MT
Mindli Team

AI-Generated Content

SQL Aggregation with GROUP BY and HAVING

Turning raw data into actionable insights is the core of analysis, and SQL's aggregation capabilities are your most powerful tool for this task. Mastering GROUP BY and HAVING allows you to summarize vast datasets into meaningful trends—calculating totals, averages, and counts for distinct categories while filtering results based on the summaries themselves. This skill is fundamental for everything from basic reporting to advanced data science pipelines.

Understanding Aggregate Functions

Before you can group data, you must understand the functions that perform the summarization. Aggregate functions operate on a set of rows and return a single computed value. The five core functions form the foundation of numerical and categorical analysis. The COUNT() function tallies the number of rows. When used as COUNT(*), it counts all rows, including those with NULL values. COUNT(column_name) counts only non-NULL values in that specific column.

For numerical data, SUM() calculates the total of a column, AVG() computes the arithmetic mean, and MIN() and MAX() find the smallest and largest values, respectively. A crucial point is that these functions, except for COUNT(*), ignore NULL values in their calculations. For example, AVG(salary) sums all non-NULL salaries and divides by the count of non-NULL salaries, which is typically the desired behavior.

Consider a table named sales with columns sale_id, product_category, region, and amount. A simple aggregation query without grouping might be:

SELECT
  COUNT(*) AS total_transactions,
  SUM(amount) AS total_revenue,
  AVG(amount) AS average_order_value,
  MIN(amount) AS smallest_sale,
  MAX(amount) AS largest_sale
FROM sales;

This query returns one row with five columns, each containing a single aggregate value summarizing the entire table.

Creating Groups with GROUP BY

The GROUP BY clause is what transforms aggregation from a whole-table summary into a categorical breakdown. It divides the rows in a table into groups where the values in the specified column(s) are identical, and then applies aggregate functions to each group independently. The result set contains one row per unique group.

Using our sales table, to find total revenue per product category, you would write:

SELECT
  product_category,
  SUM(amount) AS category_revenue
FROM sales
GROUP BY product_category;

The database engine first groups all rows that share the same product_category. Then, for each group, it calculates the sum of the amount column. The SELECT list can only contain columns that are part of the GROUP BY clause or columns wrapped in an aggregate function. This is logical: for a row representing the "Electronics" group, it can show "Electronics" and the sum of all electronics sales, but it cannot arbitrarily show a single sale_id from that group, as there are many.

You can group by multiple columns to create more granular summaries. The database creates groups for each unique combination of the listed columns. For instance, to see revenue by both category and region:

SELECT
  product_category,
  region,
  SUM(amount) AS total_revenue
FROM sales
GROUP BY product_category, region;

This would produce one row for each distinct (product_category, region) pair, such as (Electronics, North), (Electronics, South), (Clothing, North), etc.

Filtering Groups with the HAVING Clause

The WHERE clause filters individual rows before they are aggregated. The HAVING clause filters groups of rows after aggregation has occurred. This distinction is critical. You use HAVING to apply conditions to the results of aggregate functions, which is not possible in a WHERE clause.

The execution order clarifies this: WHERE runs first, filtering rows from the raw table. GROUP BY then organizes the remaining rows. Aggregate functions (SUM, AVG, etc.) are calculated for each group. Finally, HAVING filters out entire groups based on these aggregate results.

For example, to find only those product categories with total revenue exceeding $10,000:

SELECT
  product_category,
  SUM(amount) AS category_revenue
FROM sales
GROUP BY product_category
HAVING SUM(amount) > 10000;

You cannot write WHERE SUM(amount) > 10000 because the sum does not exist for individual rows. Common uses of HAVING include finding categories with a high average sale (HAVING AVG(amount) > 500), a minimum number of transactions (HAVING COUNT(*) >= 50), or a specific count of distinct items (HAVING COUNT(DISTINCT region) > 1). You can use any aggregate function in the HAVING condition, and you can combine multiple conditions with AND/OR.

Advanced Aggregation Techniques

Beyond the basics, two powerful techniques increase the precision of your summaries. First, you can combine aggregate functions with the DISTINCT keyword inside the function call. This is particularly useful with COUNT. COUNT(DISTINCT column_name) counts the number of unique, non-NULL values in a group. For instance, to find out how many unique regions made sales in each product category:

SELECT
  product_category,
  COUNT(DISTINCT region) AS regions_served
FROM sales
GROUP BY product_category;

This is different from COUNT(region), which would count every non-NULL sale record, even if multiple sales came from the same region.

Second, remember that WHERE and HAVING can be used together in a single query. This allows for a highly targeted analysis: filter raw rows first, then group and aggregate them, then filter the resulting groups. For example, to analyze high-value sales only, you might want to consider sales above 200:

SELECT
  product_category,
  AVG(amount) AS avg_high_value_sale
FROM sales
WHERE amount > 50 -- Filter individual rows first
GROUP BY product_category
HAVING AVG(amount) > 200; -- Filter groups after aggregation

Common Pitfalls

  1. Confusing WHERE and HAVING: The most frequent error is placing an aggregate function condition in the WHERE clause. Remember the order: WHERE filters rows, HAVING filters groups. If your filter condition uses SUM, AVG, COUNT, etc., it belongs in HAVING.
  1. Omitting Non-Aggregated Columns from GROUP BY: Every column in the SELECT list that is not inside an aggregate function must be listed in the GROUP BY clause. If you select SELECT region, product_category, SUM(amount) but only GROUP BY region, the query will fail or produce misleading results because product_category is ambiguous for each region group.
  1. Filtering on Aliases in HAVING: While some database systems allow it, it is not standard SQL to use a column alias defined in the SELECT clause within the HAVING clause. You should repeat the aggregate function. Use HAVING SUM(amount) > 10000, not HAVING category_revenue > 10000, even though category_revenue is defined as SUM(amount) in the select list.
  1. Misunderstanding COUNT: Treat COUNT(*), COUNT(column), and COUNT(DISTINCT column) as three different tools. COUNT(*) counts all rows in a group. COUNT(status) counts rows where status is not NULL. COUNT(DISTINCT customer_id) counts unique customers. Choose the one that matches your analytical intent.

Summary

  • Aggregate functions like COUNT(), SUM(), AVG(), MIN(), and MAX() condense many rows into single summary values and form the core of data summarization.
  • The GROUP BY clause segments your data into subsets based on identical values in one or more columns, allowing you to calculate aggregate metrics per category rather than for the entire dataset.
  • Use the HAVING clause to filter the results of a query based on conditions applied to aggregated data (e.g., total group revenue), which operates after the GROUP BY and aggregation.
  • The key difference between WHERE and HAVING is execution order: WHERE filters individual rows before grouping; HAVING filters entire groups after aggregation.
  • For more precise analysis, you can group by multiple columns for granular breakdowns and use aggregate functions with DISTINCT (e.g., COUNT(DISTINCT column)) to perform calculations on unique values only.

Write better notes with AI

Mindli helps you capture, organize, and master any subject with AI-powered summaries and flashcards.