GROUP BY is the pivot point of this entire module. Everything before it (COUNT, SUM, AVG, MIN, MAX) computed a single value across the whole table. GROUP BY lets you compute those same aggregates per category — per department, per city, per month — which is what turns a single number into an actual report.
GROUP BY with any aggregate functionSELECT alongside GROUP BYGROUP BY splits the result set into buckets based on one or more columns, then applies any aggregate functions in the SELECT list separately within each bucket.
Almost no real business report is “one number for the whole company.” It’s “one number per department,” “one number per month,” “one number per region.” GROUP BY is what makes that possible in a single query instead of running the same query manually for every category.
| Column | Description |
|---|---|
| emp_id | Employee ID |
| emp_name | Employee Name |
| dept_id | Department ID |
| manager_id | Manager ID |
| salary | Employee salary |
| Column | Description |
|---|---|
| dept_id | Department ID |
| dept_name | Department Name |
| location_id | Location ID |
| Column | Description |
|---|---|
| location_id | Location ID |
| city | City name |
SELECT column_name, aggregate_function(column_name)
FROM table_name
GROUP BY column_name;
FROM employes
│
▼
WHERE (filters ROWS, before grouping)
│
▼
GROUP BY dept_id -- collapse rows sharing dept_id into one bucket per value
│
▼
HAVING (filters GROUPS, after aggregation — see 06_HAVING.md)
│
▼
SELECT dept_id, COUNT(*) -- one output row per group
Before GROUP BY: After GROUP BY dept_id:
emp_id dept_id dept_id employee_count
1 10 10 2
2 20 ──────► 20 2
3 10
4 20
SELECT list must either (a) appear in the GROUP BY clause, or (b) be wrapped inside an aggregate function. MySQL will silently allow violations of this rule in non-strict SQL mode and return an arbitrary row’s value — this is a well-known footgun. PostgreSQL enforces the rule strictly and will refuse to run the query. Always write GROUP BY queries as if strict mode is on, regardless of which engine you’re targeting.GROUP BY executes conceptually after WHERE and before HAVING/SELECT — see the execution flow above. This ordering is why you cannot reference a SELECT-list alias inside a WHERE clause in most engines (the alias doesn’t exist yet when WHERE runs), but you generally can reference it inside GROUP BY/HAVING/ORDER BY in PostgreSQL and MySQL.GROUP BY dept_id, manager_id groups by (dept, manager) pairs.PostgreSQL supports GROUP BY ALL in newer versions of some compatible engines, but in standard PostgreSQL you must list every non-aggregated column explicitly, or use GROUP BY 1, 2 (ordinal position) as shorthand.
Avoid relying on MySQL’s permissive ONLY_FULL_GROUP_BY being disabled — production MySQL instances (8.0+) enable it by default, so non-aggregated, non-grouped columns will raise an error just like PostgreSQL.
NULL value in the GROUP BY column forms its own group (all NULLs group together) — it is not silently dropped.emp_id itself) produces one group per row, which is usually a sign the query should not be using GROUP BY at all.Wrong:
SELECT emp_name, COUNT(*)
FROM employes
GROUP BY dept_id;
emp_name is neither aggregated nor part of GROUP BY — invalid under strict SQL, and undefined/arbitrary under lenient MySQL settings.
Correct:
SELECT dept_id, COUNT(*)
FROM employes
GROUP BY dept_id;
SELECT alongside GROUP BY?WHERE, GROUP BY, HAVING, and SELECT conceptually execute?GROUP BY dept_id, manager_id, how many groups do you get for 3 departments each with 2 distinct managers?AS employee_count), never leave bare COUNT(*) in output meant for humans or downstream tools.INNER JOIN (drops unmatched rows before grouping) and LEFT JOIN (keeps them, usually producing a NULL group).GROUP BY collapses rows sharing common column values into buckets, letting aggregate functions operate per-category instead of over the whole table. Every non-aggregated SELECT column must be in the GROUP BY list — treat this as a hard rule regardless of engine leniency.
dept_id and manager_id simultaneously, counting employees in each pair.Related Topics: COUNT() · HAVING · SUM() · Conditional Aggregation
| ← Previous Lesson | ↑ Module README | Next Lesson → |