group by in group by and average
ML System Design practice on Codemia
Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.
Introduction
Questions about "group by in group by and average" usually come from mixing two different levels of aggregation. You may want to aggregate rows once, then compute an average over those grouped results. SQL can do that, but not in a single flat GROUP BY clause.
The usual solution is a subquery or common table expression. First produce the grouped totals you care about, then run a second query that averages those totals.
Think in Terms of Data Grain
Before writing SQL, ask what one row means at each step.
Suppose you have an orders table:
If you write:
you get the average order amount per customer. That is a single level of grouping, and it is valid.
But what if you want the average daily total per customer? That is different. You must first compute each customer's total for each day, then average those daily totals.
Use a Subquery or CTE for Nested Aggregation
Here is the correct pattern with a CTE:
The first query changes the grain from "one row per order" to "one row per customer per day." The outer query then changes the grain again to "one row per customer" and averages the grouped totals.
That is the core idea behind "group by inside group by."
Why AVG(SUM(...)) Usually Fails
Many people try something like this:
That is not valid in normal SQL because aggregate functions such as SUM and AVG cannot usually be nested at the same query level. SQL needs the first aggregation to finish before the second one begins.
A subquery gives the database that separation:
This version asks a slightly different question: the average of total daily sales across the whole business, not per customer.
Another Example: Average of Category Totals
Imagine a sales table with region, product_category, and revenue. If you want average category revenue per region, you can do the same two-step pattern:
Again, the first step defines category-level totals, and the second step averages them within each region.
WHERE Versus HAVING
When building these queries, keep filtering stages straight:
- '
WHEREfilters raw rows before grouping' - '
HAVINGfilters groups after aggregation'
For example, if you want to average only daily totals above 100, the filter belongs after the first grouping:
Common Pitfalls
- Forgetting that aggregation happens at a specific grain. The wrong grouping columns change the question you are answering.
- Trying to nest
AVG(SUM(...))in a single query block. - Using
AVG(amount)when the real requirement is average of grouped totals. - Filtering grouped values with
WHEREinstead ofHAVING. - Ignoring
NULLhandling.AVGskipsNULL, which can change your results if the grouped expression sometimes returnsNULL.
Summary
- Use one query level for each aggregation level.
- First group the detailed rows, then average the grouped results in an outer query.
- Think about row grain before you think about syntax.
- Use
HAVINGwhen you need to filter groups after aggregation. - If your SQL feels like it needs
GROUP BYinsideGROUP BY, you probably need a subquery or CTE.
Related reading
- Group by month and year in MySQL
- Group by with multiple columns using lambda
- Group detection in data sets
- Group n points in k clusters of equal size
- GROUP_CONCAT comma separator
- GROUP_CONCAT ORDER BY
- GroupBy pandas DataFrame and select most common value
- Grouping functions (tapply, by, aggregate) and the *apply family

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
ML System Design practice on Codemia
Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.