SQL Aggregations Finally Made Sense: GROUP BY, HAVING, MIN, MAX, AVG

Published: (January 13, 2026 at 06:02 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

The Mental Shift: From Rows to Groups

Up until now, most of my SQL queries felt like this:

“Show me rows that match X condition”

But GROUP BY changes the game. Instead of thinking about rows, you start thinking about groups of rows. That was the first uncomfortable part, so let me show you what I did.

What I Practiced Today

I worked with queries that asked questions like:

  • How many users per country?
  • What’s the average salary per department?
  • Which department has the highest average score?

All of these queries required grouping—not a simple “get rows that fulfil this condition”.

GROUP BY (What Finally Clicked)

At first, I kept getting errors like:

column must appear in the GROUP BY clause or be used in an aggregate function

That error message annoyed me… until I started thinking more and trying to understand the message and task better.

What helped me:
If a column is not inside an aggregate function, it must be in the GROUP BY.

SELECT country, COUNT(*)
FROM users
GROUP BY country;
  • country → used for grouping
  • COUNT(*) → summarizes each group

But this does not work:

SELECT country, email
FROM users
GROUP BY country;

Because SQL now asks: “Which email do you want me to pick for each country?”
That was my first real “okay… fair enough” moment.

MIN, MAX, and AVG (Straightforward but Powerful)

Once grouping made sense, these functions felt more natural.

MIN

SELECT department, MIN(salary)
FROM employees
GROUP BY department;

lowest salary per department

MAX

SELECT department, MAX(salary)
FROM employees
GROUP BY department;

highest salary per department

AVG

SELECT department, AVG(salary)
FROM employees
GROUP BY department;

average salary per department

Seeing real numbers come out per group made SQL feel more real‑world.

HAVING (The Part I Confused with WHERE)

At first, I kept writing queries like this:

SELECT department, AVG(salary)
FROM employees
WHERE AVG(salary) > 50000
GROUP BY department;

SQL basically said no.

The Difference That Finally Stuck

  • WHERE filters rows before grouping.
  • HAVING filters groups after grouping.

The correct version:

SELECT department, AVG(salary)
FROM employees
GROUP BY department
HAVING AVG(salary) > 50000;

Once I saw HAVING as “WHERE for grouped data”, it stopped feeling magical.

Order of Execution

Understanding the order of execution clarified everything:

  1. FROM
  2. WHERE – filter rows
  3. GROUP BY – create groups
  4. HAVING – filter groups
  5. SELECT
  6. ORDER BY

This explains why HAVING exists; WHERE simply runs too early.

What Still Feels Weird

I’m still adjusting to:

  • Thinking in groups instead of rows
  • Knowing when I actually need GROUP BY
  • Reading grouped queries without getting lost

But compared to yesterday? This is progress.

Why I’m Sharing This

GROUP BY and HAVING are usually taught quickly, like:

“Here’s the syntax, move on.”

As a beginner, this is a mental shift, not just syntax. If you’re also learning SQL and found GROUP BY confusing at first, you’re not alone.

What’s Next

I’m learning SQL slowly, properly, and honestly, documenting the process so other beginners don’t feel like they’re the only ones struggling. If you’re on the same path, you’re not behind—you’re just learning it the right way.

Back to Blog

Related posts

Read more »

How To Solve LeetCode 1193

Problem Description The table Transactions has the following columns: - id primary key - country - state enumeration: 'approved' or 'declined' - amount - trans...