Aggregate Functions
Use COUNT, SUM, AVG, MIN, and MAX to analyze data.
Introduction to Aggregate Functions
So far, you've worked with individual rows - selecting them, filtering them, sorting them. But what if you want to answer questions like:
- "How many products do we have?"
- "What's the average price?"
- "What's our total revenue?"
- "Who's the highest-paid employee?"
You need to calculate statistics across multiple rows. That's what aggregate functions do - they take multiple values and return a single summary result.
A function that performs a calculation on a set of rows and returns a single value. Examples: COUNT, SUM, AVG, MIN, MAX.
Function | Purpose | Example Result |
|---|---|---|
| COUNT() | Counts the number of rows | 100 |
| SUM() | Adds up numeric values | 15000.50 |
| AVG() | Calculates average | 75.25 |
| MIN() | Finds the minimum value | 10 |
| MAX() | Finds the maximum value | 999 |
Let's work with our products and orders tables:
id | product_name | category | price | stock |
|---|---|---|---|---|
| 1 | Laptop | Electronics | 999.99 | 45 |
| 2 | Mouse | Electronics | 29.99 | 120 |
| 3 | Desk Chair | Furniture | 199.99 | 30 |
| 4 | Monitor | Electronics | 349.99 | 60 |
| 5 | Desk Lamp | Furniture | 39.99 | 85 |
| 6 | Keyboard | Electronics | 79.99 | 95 |
COUNT - Counting Rows
COUNT() returns the number of rows that match a criteria.
COUNT(*) - Count all rows:
-- How many products do we have?
SELECT COUNT(*) AS total_products
FROM products;COUNT(*) counts every row, including those with NULL values.
COUNT with WHERE - Conditional counting:
-- How many Electronics products?
SELECT COUNT(*) AS electronics_count
FROM products
WHERE category = 'Electronics';Only rows matching the WHERE condition are counted.
COUNT(column) - Count non-NULL values:
id | product_name | price | discount |
|---|---|---|---|
| 1 | Laptop | 999.99 | NULL |
| 2 | Mouse | 29.99 | 5.00 |
| 3 | Desk Chair | 199.99 | NULL |
| 4 | Monitor | 349.99 | 20.00 |
| 5 | Keyboard | 79.99 | 10.00 |
-- How many products have a discount?
SELECT COUNT(discount) AS products_with_discount
FROM products_with_discount;COUNT(discount) only counts rows where discount is not NULL. Out of 5 products, only 3 have discounts.
Comparison:
-- Compare COUNT(*) vs COUNT(column)
SELECT
COUNT(*) AS total_rows,
COUNT(discount) AS rows_with_discount,
COUNT(*) - COUNT(discount) AS rows_without_discount
FROM products_with_discount;COUNT(DISTINCT) - Counting Unique Values
COUNT(DISTINCT column) counts the number of unique (different) values, excluding duplicates.
-- How many different categories?
SELECT COUNT(DISTINCT category) AS category_count
FROM products;We have 6 products but only 2 unique categories (Electronics and Furniture).
COUNT vs COUNT(DISTINCT):
SELECT
COUNT(category) AS total_categories,
COUNT(DISTINCT category) AS unique_categories
FROM products;SUM - Adding Up Values
SUM() adds up all numeric values in a column.
-- Total value of all inventory
SELECT SUM(price * stock) AS total_inventory_value
FROM products;This calculates: (999.99 × 45) + (29.99 × 120) + (199.99 × 30) + (349.99 × 60) + (39.99 × 85) + (79.99 × 95) = 78,449.20
SUM with WHERE:
-- Total stock quantity for Electronics
SELECT SUM(stock) AS total_electronics_stock
FROM products
WHERE category = 'Electronics';45 + 120 + 60 + 95 = 320 units of Electronics in stock.
SUM ignores NULL values:
-- Sum of discounts (NULLs are skipped)
SELECT SUM(discount) AS total_discounts
FROM products_with_discount;5.00 + 20.00 + 10.00 = 35.00. The two NULL discounts are ignored.
AVG - Calculating Averages
AVG() calculates the average (mean) of numeric values.
-- Average product price
SELECT AVG(price) AS average_price
FROM products;(999.99 + 29.99 + 199.99 + 349.99 + 39.99 + 79.99) ÷ 6 = 283.32
AVG with WHERE:
-- Average price of Furniture
SELECT AVG(price) AS avg_furniture_price
FROM products
WHERE category = 'Furniture';(199.99 + 39.99) ÷ 2 = 119.99
AVG ignores NULL values:
-- Average discount (only counts non-NULL)
SELECT AVG(discount) AS avg_discount
FROM products_with_discount;(5.00 + 20.00 + 10.00) ÷ 3 = 11.67
Note: It divides by 3 (number of non-NULL values), not 5 (total rows)!
MIN and MAX - Finding Extremes
MIN() returns the smallest value, MAX() returns the largest value.
-- Cheapest and most expensive products
SELECT
MIN(price) AS cheapest,
MAX(price) AS most_expensive
FROM products;MIN/MAX work with text and dates too:
-- First and last product alphabetically
SELECT
MIN(product_name) AS first_alphabetically,
MAX(product_name) AS last_alphabetically
FROM products;id | name | department | salary | hire_date |
|---|---|---|---|---|
| 1 | Alice | Engineering | 85000 | 2020-03-15 |
| 2 | Bob | Marketing | 65000 | 2021-07-22 |
| 3 | Charlie | Engineering | 95000 | 2019-11-03 |
| 4 | Diana | Sales | 70000 | 2022-01-10 |
-- Earliest and most recent hire dates
SELECT
MIN(hire_date) AS oldest_employee,
MAX(hire_date) AS newest_employee
FROM employees;Charlie was hired earliest (2019), Diana most recently (2022).
MIN/MAX with WHERE:
-- Salary range for Engineering department
SELECT
MIN(salary) AS min_eng_salary,
MAX(salary) AS max_eng_salary
FROM employees
WHERE department = 'Engineering';Combining Multiple Aggregates
You can use multiple aggregate functions in the same query:
-- Complete product statistics
SELECT
COUNT(*) AS total_products,
SUM(stock) AS total_units,
AVG(price) AS avg_price,
MIN(price) AS cheapest,
MAX(price) AS most_expensive,
SUM(price * stock) AS total_value
FROM products;Department salary analysis:
-- Engineering department salary stats
SELECT
COUNT(*) AS employees,
MIN(salary) AS min_salary,
MAX(salary) AS max_salary,
AVG(salary) AS avg_salary,
SUM(salary) AS total_payroll
FROM employees
WHERE department = 'Engineering';Aggregate Functions with Expressions
You can use calculations inside aggregate functions:
-- Total revenue if we sold all inventory
SELECT
SUM(price * stock) AS potential_revenue,
AVG(price * stock) AS avg_product_value
FROM products;-- Annual salary costs
SELECT
SUM(salary * 12) AS annual_payroll,
AVG(salary * 12) AS avg_annual_salary
FROM employees;ROUND - Formatting Aggregate Results
Aggregate functions often return many decimal places. Use ROUND() to format results:
-- Round average price to 2 decimal places
SELECT
ROUND(AVG(price), 2) AS avg_price,
ROUND(AVG(stock), 0) AS avg_stock
FROM products;ROUND(value, decimals):
ROUND(AVG(price), 2)rounds to 2 decimal placesROUND(AVG(stock), 0)rounds to nearest integer
Aggregates Return NULL on Empty Sets
What happens when there are no matching rows?
-- No products in "Clothing" category
SELECT
COUNT(*) AS count,
SUM(price) AS sum_price,
AVG(price) AS avg_price,
MIN(price) AS min_price,
MAX(price) AS max_price
FROM products
WHERE category = 'Clothing';Important:
- COUNT returns 0 (there are zero rows)
- SUM, AVG, MIN, MAX return NULL (there's no data to aggregate)
Use COALESCE to provide default values for NULL results:
SELECT COALESCE(SUM(price), 0) AS total
FROM products
WHERE category = 'Clothing';
This returns 0 instead of NULL when there are no matching rows.
Common Mistakes to Avoid
Don't make these mistakes:
-
Mixing aggregate and non-aggregate columns: ❌
- Wrong:
SELECT product_name, AVG(price) FROM products; - This fails because product_name is a single row, AVG(price) is one value
- Fix: Use GROUP BY (covered in next lesson!)
- Wrong:
-
Using aggregate functions in WHERE: ❌
- Wrong:
WHERE AVG(price) > 100 - WHERE filters rows BEFORE aggregation
- Fix: Use HAVING clause (covered later)
- Wrong:
-
Forgetting COUNT(*) vs COUNT(column): ❌
COUNT(*)counts all rows (including NULL)COUNT(column)counts only non-NULL values- Know which one you need!
-
Assuming AVG handles NULLs like zeros: ❌
- AVG ignores NULL, doesn't treat it as 0
- If you want NULLs as zeros, use:
AVG(COALESCE(column, 0))
-
Using SUM/AVG on text columns: ❌
SUM(product_name)is invalid- Only use on numeric columns
-
Not rounding results: Not an error, but messy
AVG(price)might return 283.32333333333- Use
ROUND(AVG(price), 2)for cleaner output
-
Expecting aggregates in ORDER BY without GROUP BY: ❌
SELECT * FROM products ORDER BY AVG(price);doesn't make sense- AVG returns one value for the whole table
Practical Examples
Example 1: Inventory report
-- Overall inventory statistics
SELECT
COUNT(*) AS total_products,
COUNT(DISTINCT category) AS categories,
SUM(stock) AS total_units_in_stock,
ROUND(AVG(price), 2) AS avg_price,
MIN(stock) AS lowest_stock,
MAX(stock) AS highest_stock
FROM products
WHERE stock > 0;Example 2: Sales performance
id | product_id | quantity | order_date |
|---|---|---|---|
| 1 | 2 | 3 | 2024-02-01 |
| 2 | 1 | 1 | 2024-02-01 |
| 3 | 4 | 2 | 2024-02-02 |
| 4 | 2 | 5 | 2024-02-03 |
| 5 | 3 | 1 | 2024-02-03 |
-- Order summary
SELECT
COUNT(*) AS total_orders,
SUM(quantity) AS total_items_sold,
ROUND(AVG(quantity), 2) AS avg_items_per_order,
MIN(quantity) AS min_order_size,
MAX(quantity) AS max_order_size
FROM orders;Example 3: HR analytics
-- Company-wide salary statistics
SELECT
COUNT(*) AS total_employees,
ROUND(AVG(salary), 2) AS avg_salary,
MIN(salary) AS min_salary,
MAX(salary) AS max_salary,
MAX(salary) - MIN(salary) AS salary_range
FROM employees;Key Takeaways
What you learned:
✅ Aggregate functions calculate statistics across multiple rows ✅ COUNT(*) counts all rows, COUNT(column) counts non-NULL values ✅ COUNT(DISTINCT column) counts unique values ✅ SUM() adds up numeric values ✅ AVG() calculates the mean (average) ✅ MIN() and MAX() find smallest and largest values ✅ Aggregate functions ignore NULL values (except COUNT(*)) ✅ Use ROUND() to format decimal results ✅ You can combine multiple aggregates in one query ✅ Aggregates return NULL when there are no rows (except COUNT returns 0)
Coming up next: We'll learn about GROUP BY to calculate aggregates for different groups of data (e.g., average price per category, total sales per product)!
Practice Exercise: Try these queries:
- Count how many employees are in the Engineering department
- Find the total, average, min, and max salary across all employees
- Calculate the total value of all products (price × stock)
- Find the average stock level for Electronics products
- Count how many distinct departments exist