SQL-Engineering-Handbook

GROUP BY

Introduction

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.

Learning Objectives

Concept Overview

GROUP 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.

Business Context

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.

Schema Used

employes

Column Description
emp_id Employee ID
emp_name Employee Name
dept_id Department ID
manager_id Manager ID
salary Employee salary

departments

Column Description
dept_id Department ID
dept_name Department Name
location_id Location ID

locations

Column Description
location_id Location ID
city City name

Syntax

SELECT column_name, aggregate_function(column_name)
FROM table_name
GROUP BY column_name;

Execution Flow

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

Step-by-Step Walkthrough

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

GROUP BY collapses rows into per-category buckets

Engineering Notes

PostgreSQL Notes

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.

MySQL Notes

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.

Edge Cases

Common Mistakes

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;

Interview Questions

  1. What’s the rule governing which columns can appear in SELECT alongside GROUP BY?
  2. In what order do WHERE, GROUP BY, HAVING, and SELECT conceptually execute?
  3. If you GROUP BY dept_id, manager_id, how many groups do you get for 3 departments each with 2 distinct managers?
  4. Why does MySQL sometimes “allow” invalid GROUP BY queries that PostgreSQL rejects, and why is relying on that behavior dangerous?

Business Use Cases

Best Practices

Summary

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.

Practice Challenges

  1. Write a query for department name and average salary, for departments located in Nagpur only.
  2. Write a query grouping employees by both dept_id and manager_id simultaneously, counting employees in each pair.

Further Reading


Related Topics: COUNT() · HAVING · SUM() · Conditional Aggregation


← Previous Lesson ↑ Module README Next Lesson →