Today was one of those SQL days where things look simple… until you actually write the query.
I focused on GROUP BY, HAVING, and the basic aggregate functions: MIN, MAX, and AVG. These are things I’ve heard about for a long time, but I realised I didn’t truly understand them until I tried using them with real questions.
This post is me documenting what finally made sense, and what confused me at first.
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 conditions.
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.
Example:
SELECT country, COUNT(*)
FROM users
GROUP BY country;
This works because:
-
country→ used for grouping -
COUNT(*)→ summarizes each group
But this does NOT work:
SELECT country, email
FROM users
GROUP BY country;
Because now SQL is like:
“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 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
Nothing fancy but seeing real numbers come out per group made SQL feel more real-world.
HAVING (The Part I Confused with WHERE)
This is where I stumbled a bit.
At first, I kept writing queries like this:
SELECT department, AVG(salary)
FROM employees
WHERE AVG(salary) > 50000
GROUP BY department;
And SQL basically said nope.
The Difference That Finally Stuck:
WHERE filters rows before grouping
HAVING filters groups after grouping
So the correct version was supposed to be:
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 though.
Order of Execution
I then learnt the order of execution of SQL queries and understanding the order made everything clearer:
FROM-
WHERE(filter rows) -
GROUP BY(create groups) -
HAVING(filter groups) SELECTORDER 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 mentally 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.”
But as a beginner, this is a mental shift, not just syntax.
If you’re also learning SQL and GROUP BY felt confusing at first, same here.
What’s Next
I’m learning SQL slowly, properly, and honestly and I’m 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.
Top comments (1)
This is well written and very descriptive. Thanks for sharing your journey. You're doing really well