GROUP BY
GROUP BY city collapses rows that share a city. Then you use aggregates: SELECT city, COUNT(*), AVG(marks) FROM students GROUP BY city;
Every selected column must be in GROUP BY or inside COUNT/SUM/AVG/MIN/MAX (ONLY_FULL_GROUP_BY). SELECT name, AVG(marks) GROUP BY city is illegal — which name?
WHERE filters rows before groups. HAVING filters groups after: HAVING AVG(marks) < 40.
Trap — SELECT * with GROUP BY. Trap: using WHERE AVG(marks) < 40.
On the example next to this theory — GROUP BY aggregates; HAVING filters groups; ORDER BY sorts the result.
Exam tip
SELECT city, AVG(marks) FROM students GROUP BY city;