MySQL Group By

GROUP BY collapses many rows sharing a common value into one summary row per group, usually paired with an aggregate function.

From Rows to Summaries

GROUP BY takes a result set and folds every row that shares the same value in one or more columns into a single row. On its own, grouping just deduplicates values, but it becomes powerful once paired with an aggregate function - COUNT, SUM, AVG, MIN, or MAX - which computes one number per group instead of per row. Where a customer_id column might have 40 order rows scattered across a table, GROUP BY customer_id turns that into one row per customer, and COUNT(*) tells you how many orders each one placed.

Number of orders placed by each customer

SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id;

Aggregate Functions You'll Use With GROUP BY

  • COUNT(*) counts rows in a group; COUNT(column) counts only the rows where that column isn't NULL.
  • SUM(column) adds up a numeric column across the group.
  • AVG(column) computes the mean of a numeric column across the group.
  • MIN(column) and MAX(column) return the smallest and largest values found in the group.

Salary summary per department

SELECT department_id,
       COUNT(*) AS employee_count,
       SUM(salary) AS total_salary,
       AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;

Filtering Groups with HAVING

WHERE filters individual rows before they're grouped, so it can't reference an aggregate like COUNT(*) - at the point WHERE runs, no aggregation has happened yet. HAVING runs after grouping and aggregation, so it can filter on the aggregate results themselves, such as keeping only the departments whose total salary exceeds a threshold. A single query can use both: WHERE to narrow the raw rows first, then HAVING to narrow the resulting groups.

Departments spending more than 500000 in total salary

SELECT department_id, SUM(salary) AS total_salary
FROM employees
GROUP BY department_id
HAVING SUM(salary) > 500000;
WHEREHAVING
RunsBefore groupingAfter grouping
FiltersIndividual rowsAggregated groups
Can reference aggregates?NoYes
Typical useExclude rows before summarizingExclude summary rows that don't meet a threshold
Note: GROUP BY can list more than one column - GROUP BY department_id, job_title creates one row per unique combination of the two, which is handy for a two-level summary.
Note: Every column in the SELECT list must either appear in the GROUP BY clause or be wrapped in an aggregate function. MySQL's default ONLY_FULL_GROUP_BY mode rejects a query like SELECT name, department_id, COUNT(*) FROM employees GROUP BY department_id, because name isn't grouped and isn't aggregated - MySQL has no single value of name to return for a group containing several employees.

Exercise: MySQL Group By

What is the main purpose of GROUP BY?