Aggregates, GROUP BY & HAVING
COUNT, SUM, AVG, MIN, MAX, grouping rows, filtering groups and WITH ROLLUP.
So far every query has returned individual rows. Real questions are often about summaries: How much did we sell per month? What's the average order? Which city has the most customers? That's the job of aggregate functions and GROUP BY.
Sample data#
The aggregate functions#
An aggregate takes many rows and returns one value:
Aggregates ignore NULLs (except COUNT(*)). So AVG(discount) averages only the four rows that have a discount. If missing values should count as zero, write AVG(COALESCE(discount, 0)).
GROUP BY: one result row per group#
GROUP BY splits the rows into groups that share a value, then computes the aggregates per group:
Group by several columns to get one row per combination:
You can group by expressions too. Here is monthly revenue:
The ONLY_FULL_GROUP_BY rule#
Every column in the SELECT list must either be in the GROUP BY or inside an aggregate. Otherwise MySQL 8 refuses to guess:
Each category has several products, so which one should be shown? Fix it by grouping by product as well, or by aggregating it:
(ANY_VALUE(product) also silences the error and means "any one product is fine". Use it only when that's really true.) Columns that are functionally dependent on the group, such as other columns of a table whose primary key you grouped by, are allowed.
WHERE vs HAVING#
WHEREfilters rows before grouping. It can't see aggregates.HAVINGfilters groups after aggregation. It can use aggregates.
If a condition doesn't involve an aggregate, put it in WHERE. Filtering rows early is faster than building groups and throwing them away.
Conditional aggregation#
Combine CASE (or IF, or a plain boolean) with aggregates to build pivot-style reports in a single pass:
SUM(discount IS NOT NULL) works because a true comparison is 1 and a false one is 0 in MySQL.
Subtotals with WITH ROLLUP#
WITH ROLLUP adds subtotal rows and a grand total. GROUPING() tells you which rows are the extra ones:
The order of a grouped query#
MySQL lets you use SELECT aliases in GROUP BY, HAVING and ORDER BY, but not in WHERE.
Common mistakes#
- Putting aggregates in WHERE.
WHERE SUM(amount) > 100is an error ("Invalid use of group function"). UseHAVING. - Forgetting NULLs.
COUNT(col)andAVG(col)skip NULLs, which may or may not be what you want. - Averages of averages. Averaging per-month averages isn't the overall average when months have different numbers of sales. Aggregate from the raw rows.
- Counting after a join. Joining orders to order items multiplies rows. Use
COUNT(DISTINCT o.id)(more in JOINs). - GROUP_CONCAT truncation. The result is cut at
group_concat_max_len(1,024 bytes by default). Raise it withSET SESSION group_concat_max_len = 100000;if needed.
What's next#
You can query and summarise a single table. Next you'll make tables trustworthy with constraints (primary keys, foreign keys, UNIQUE, CHECK and DEFAULT) before learning to combine tables with JOINs.
Check your understanding
Quick quiz
1.What is the difference between
COUNT(*)andCOUNT(discount)?2.You want only categories whose total sales exceed 1000. Where does the condition
SUM(amount) > 1000go?3.With MySQL 8's default ONLY_FULL_GROUP_BY mode, why does
SELECT category, product, SUM(amount) FROM sales GROUP BY category;fail?
Finished reading?
Mark this lesson complete to track your progress.