The Hidden Danger of GROUP BY in MySQL: How PostgreSQL Saves You

I’m a software developer passionate about exploring new technologies and continuously learning. On my blog, I share what I discover—whether it’s cool tricks, coding solutions, or interesting tools. My goal is to document my journey and help others by sharing insights that I find useful along the way. Join me as I write about: Programming tips & tricks Lessons from everyday coding challenges Interesting tools & frameworks I come across Always curious, always learning—let’s grow together! 🚀
Have you ever written a GROUP BY query, got results, and thought,
"Hmm... something feels weird, but hey! it runs right?"
If yes congratulations, you’ve stumbled upon one of SQL’s most interesting (and dangerous) traps: loose GROUP BY behavior especially in MySQL.
Today, let's explore why MySQL’s GROUP BY can behave differently from PostgreSQL (and even from what SQL standards expect), and why understanding this could save your server / app from subtle and really hard-to-detect bugs.
🧠 SQL Standards: What Should Happen
According to the SQL Standard (aka ANSI SQL):
If you
GROUP BYsome columns,Then every column you select must either:
Appear in the
GROUP BY, orBe wrapped inside an aggregate function like
SUM(),MIN(),MAX(), etc.
This rule makes a lot of sense.
When you’re grouping rows, you’re collapsing them together.
You can’t just pluck random values from them without a clear instruction.
for example:
let’s imagine a simple table called world where a country is mapped to it’s continent’s name
Table:
| country | continent |
| India | Asia |
| Germany | Europe |
| Belgium | Europe |
| … | … |
🔥 Enter: MySQL’s Loose Mode
Historically, MySQL had different priorities:
Focus on speed ✅
Make things easy for beginners ✅
Allow queries to work even if they technically violate standards 😬
In older MySQL versions (pre-5.7), you could write queries that didn’t strictly follow the rules and MySQL would just... do something. 😅
Example:
SELECT country FROM world GROUP BY continent;
👉 What happens here?
MySQL groups the rows by continent (fine).
Then, for the
countryfield (which isn’t aggregated),
it picks a random value from the group!
Yes!! totally random.
Depending on indexes, engine decisions, or even sheer luck, you could get any country’s name for that continent.
🎯 Real-Life Danger Example
Suppose you’re showing "representative countries" on a dashboard.
You write:
SELECT continent, country FROM world GROUP BY continent;
One day, Europe shows "Germany."
Next week, Europe shows "Belgium."
After a migration, Europe shows "Croatia."
The code didn’t change. The query didn’t change. The data didn't change. Yet your users see different results. 🤯
⚡ PostgreSQL’s Strict Mode (and New MySQL Strict Mode)
PostgreSQL said: Nope. Not allowed.
If you try that loose query, PostgreSQL immediately errors out:
ERROR: column "world.country" must appear in the GROUP BY clause or be used in an aggregate function LINE 1: SELECT country FROM world GROUP BY continent;
✅ PostgreSQL forces you to either:
Add
countryinto theGROUP BY, orApply an aggregation like
MIN(country),MAX(country), etc.
MySQL also eventually realized the dangers of loose mode.
In MySQL 5.7 and 8.0, strict SQL compliance is now possible with this setting:
SET sql_mode = 'ONLY_FULL_GROUP_BY';
When ONLY_FULL_GROUP_BY is enabled:
MySQL behaves like PostgreSQL.
It rejects bad queries that select non-grouped, non-aggregated fields.
Your queries become predictable, correct, and portable.
(And yes, modern MySQL has this strict mode ON by default from MySQL 8.0 but always double-check it.)
🛠️ How to Fix It: Proper GROUP BY Queries
Here’s how you write that dashboard query properly:
SELECT continent, MIN(country) as first_country
FROM world
GROUP BY continent;
Now, you explicitly tell SQL: Pick the lexicographically smallest country name from each continent.
Deterministic ✅
Standards-compliant ✅
Works reliably across MySQL, PostgreSQL, and others ✅
🧩 Quick Checklist: GROUP BY Best Practices
✅ Every non-aggregated column in SELECT must appear in GROUP BY.
✅ Always think: "Am I grouping this value explicitly?"
✅ Turn on ONLY_FULL_GROUP_BY in MySQL (especially for production).
✅ Test your queries on strict SQL databases like PostgreSQL to catch hidden bugs early.
📚 References and Further Reading
Loose GROUP BY might seem like a harmless shortcut...
Until one day it quietly ships wrong data to production. 😅
So next time you group rows, remember:
"Group your columns wisely or risk grouping your bugs unknowingly." 🚀



