MySQL Aggregate & Grouping Functions — Complete Guide 2026
Advertisement
Introduction
Why This Matters
Aggregate functions are the backbone of data analysis in SQL. Every time you need to answer a business question — "What is our total revenue by city?" or "Which product category has the most items?" — you reach for aggregate functions. Without them, you would need to pull every row into application code and compute totals manually, which is slow and error-prone.
Understanding GROUP BY and HAVING alongside aggregate functions lets you write concise, performant queries that databases execute in optimised execution plans. This knowledge is tested in virtually every SQL interview and used daily by analysts, back-end engineers, and data scientists.
The Sample Table: shoppe
All examples in this guide use the following table:
CREATE TABLE shoppe (
product_id INT PRIMARY KEY,
product_name VARCHAR(30),
price DECIMAL(8,2),
qty INT,
city VARCHAR(30),
company VARCHAR(20)
);| product_id | product_name | price | qty | city | company |
|---|---|---|---|---|---|
| 1 | Laptop | 500.00 | 10 | New York | HP |
| 2 | Smartphone | 300.00 | 20 | Los Angeles | Apple |
| 3 | TV | 400.00 | 5 | Chicago | Samsung |
| 4 | Tablet | 200.00 | 50 | New York | Apple |
| 5 | Camera | 100.00 | 56 | Chicago | Canon |
| 6 | Refrigerator | 600.00 | 30 | Los Angeles | LG |
| 7 | Microwave | 200.00 | 25 | Chicago | LG |
Core Aggregate Functions
COUNT — Counting Rows and Values
COUNT(*) counts every row regardless of NULL values. COUNT(fieldname) counts only non-NULL values in that column. COUNT(DISTINCT fieldname) counts unique non-NULL values.
-- Total number of rows
SELECT COUNT(*) FROM shoppe;
-- Output: 7
-- Non-NULL prices
SELECT COUNT(price) FROM shoppe;
-- Output: 6 (assumes one row has NULL price)
-- Distinct cities
SELECT COUNT(DISTINCT city) FROM shoppe;
-- Output: 3SUM and AVG — Totals and Averages
SUM and AVG work only on numeric columns (INT, DECIMAL). AVG excludes NULL values from both the numerator and denominator.
-- Total price across all products
SELECT SUM(price) FROM shoppe;
-- Output: 2300.00
-- Average quantity (NULLs excluded automatically)
SELECT AVG(qty) FROM shoppe;
-- Output: 28.00MIN and MAX — Extremes
MIN and MAX work on numeric, date, and string columns. On strings they use alphabetical ordering.
SELECT MAX(price) FROM shoppe;
-- Output: 600.00
SELECT MIN(qty) FROM shoppe;
-- Output: 5
-- Works on strings too
SELECT MIN(product_name) FROM shoppe;
-- Output: Camera (alphabetically first)GROUP BY — Grouping Records
GROUP BY partitions all rows into groups based on one or more column values, then applies the aggregate function to each group independently.
Rule: Every column in the SELECT list that is not inside an aggregate function must appear in the GROUP BY clause.
-- Total sales revenue per city
SELECT city, SUM(price * qty) AS total_sales
FROM shoppe
GROUP BY city;| city | total_sales |
|---|---|
| New York | 8000.00 |
| Los Angeles | 18000.00 |
| Chicago | 15600.00 |
-- Average product price per company
SELECT company, AVG(price) AS avg_price
FROM shoppe
GROUP BY company;| company | avg_price |
|---|---|
| Apple | 250.00 |
| Canon | 100.00 |
| HP | 500.00 |
| LG | 400.00 |
| Samsung | 400.00 |
-- Count of products per city
SELECT city, COUNT(*) AS city_count
FROM shoppe
GROUP BY city;| city | city_count |
|---|---|
| New York | 2 |
| Los Angeles | 2 |
| Chicago | 3 |
HAVING — Filtering Groups
HAVING filters the result of GROUP BY the same way WHERE filters individual rows. The critical distinction: WHERE runs before grouping; HAVING runs after.
-- Cities with total sales above 10000
SELECT city, SUM(price * qty) AS total_sales
FROM shoppe
GROUP BY city
HAVING SUM(price * qty) > 10000;| city | total_sales |
|---|---|
| Los Angeles | 18000.00 |
| Chicago | 15600.00 |
-- Companies that supply more than one product
SELECT company, COUNT(*) AS product_count
FROM shoppe
GROUP BY company
HAVING COUNT(*) > 1;| company | product_count |
|---|---|
| Apple | 2 |
| LG | 2 |
WHERE vs HAVING — Key Difference
-- WHERE filters rows BEFORE grouping
SELECT city, SUM(price * qty) AS total_sales
FROM shoppe
WHERE price > 200 -- only rows with price > 200 are grouped
GROUP BY city
HAVING SUM(price * qty) > 5000; -- then filter the groupsUsing HAVING without GROUP BY applies the filter to the entire table as a single group, which is rarely useful.
Common Mistakes
- Selecting non-aggregated columns without GROUP BY. MySQL in strict mode raises an error; in lenient mode it returns an arbitrary value.
- Using WHERE on aggregate results. You cannot write
WHERE SUM(qty) > 50— useHAVINGinstead. - Confusing COUNT(*) and COUNT(column).
COUNT(*)never ignores rows;COUNT(column)skips NULLs. - Forgetting that AVG ignores NULLs. A column with 5 values and 2 NULLs averages over 5, not 7.
- Expecting ORDER in GROUP BY output. Results are unordered unless you add
ORDER BY.
Best Practices
- Always add an
ORDER BYclause afterGROUP BYfor deterministic output. - Use column aliases in
HAVINGonly when the database supports it; prefer repeating the expression for portability. - Index columns that appear in
GROUP BYto allow the query optimiser to use a sort-free grouping strategy. - Combine
WHEREandHAVING: filter rows cheaply withWHEREfirst, then filter groups withHAVING. - Prefer
COUNT(*)overCOUNT(1)— both are equivalent, butCOUNT(*)is the SQL standard.
Key Takeaways
COUNT(*)counts all rows including those with NULLs;COUNT(column)skips NULLs.SUM()andAVG()operate only on numeric data types;MIN()andMAX()work on any comparable type.GROUP BYdivides rows into partitions before applying aggregate functions to each partition.HAVINGfilters groups produced byGROUP BY, whileWHEREfilters individual rows before grouping.- Every non-aggregate column in
SELECTmust appear inGROUP BY(SQL standard rule). - Combining
WHEREandHAVINGin one query allows both row-level and group-level filtering in a single pass. COUNT(DISTINCT column)counts unique non-NULL values and is useful for finding cardinality.- Aggregate queries without
GROUP BYtreat the entire table as one group and return a single result row.
Advertisement