What Are Aggregate Functions?
Aggregate functions collapse multiple rows into a single value. Instead of returning one row per employee, they return one number that summarises all (or a filtered set of) employees.
-- Without aggregate — returns 8 rows
SELECT salary FROM employees;
-- With aggregate — returns 1 row
SELECT AVG(salary) FROM employees;
The five core aggregate functions:
| Function | What it computes |
|---|---|
COUNT() | Number of rows / non-NULL values |
SUM() | Total of numeric values |
AVG() | Arithmetic mean |
MIN() | Smallest value |
MAX() | Largest value |
Sample Data
CREATE TABLE employees (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
department TEXT NOT NULL,
salary REAL NOT NULL,
hire_date TEXT NOT NULL,
bonus REAL -- nullable
);
INSERT INTO employees VALUES
(1, 'Priya Sharma', 'Finance', 75000, '2022-03-15', 8000),
(2, 'Raj Patel', 'Technology', 92000, '2021-07-01', 12000),
(3, 'Meera Singh', 'Marketing', 68000, '2023-01-10', NULL),
(4, 'Arjun Nair', 'Finance', 81000, '2020-11-22', 9500),
(5, 'Sunita Rao', 'Technology', 88000, '2022-09-05', 11000),
(6, 'Dev Kumar', 'Marketing', 72000, '2021-04-18', 5000),
(7, 'Kiran Mehta', 'Finance', 95000, '2019-06-30', 15000),
(8, 'Ananya Das', 'Technology', 63000, '2024-02-14', NULL);
COUNT
COUNT counts rows. It comes in three forms:
-- COUNT(*) — counts all rows, including those with NULLs
SELECT COUNT(*) AS total_employees FROM employees;
-- Result: 8
-- COUNT(column) — counts non-NULL values in that column
SELECT COUNT(bonus) AS employees_with_bonus FROM employees;
-- Result: 6 (Meera and Ananya have NULL bonus)
-- COUNT(DISTINCT column) — counts unique non-NULL values
SELECT COUNT(DISTINCT department) AS num_departments FROM employees;
-- Result: 3
When to Use Each Form
| Form | Use When |
|---|---|
COUNT(*) | Counting total rows (including NULLs) |
COUNT(col) | Counting how many rows have a value |
COUNT(DISTINCT col) | Counting unique values |
SUM
SUM adds up all numeric values in a column. It ignores NULL values:
-- Total salary bill
SELECT SUM(salary) AS total_salary_bill FROM employees;
-- Result: 634000
-- Total bonuses paid (NULLs excluded automatically)
SELECT SUM(bonus) AS total_bonuses FROM employees;
-- Result: 60500 (only 6 employees have bonuses)
-- Total compensation (salary + bonus, treating NULL bonus as 0)
SELECT SUM(salary + COALESCE(bonus, 0)) AS total_compensation
FROM employees;
AVG
AVG computes the arithmetic mean. It also ignores NULLs:
-- Average salary
SELECT ROUND(AVG(salary), 2) AS avg_salary FROM employees;
-- Result: 79250.00
-- Average bonus — ONLY of those who have one
SELECT ROUND(AVG(bonus), 2) AS avg_bonus FROM employees;
-- Result: 10083.33 (6 employees, not 8)
-- Average including non-bonus employees (treat NULL as 0)
SELECT ROUND(AVG(COALESCE(bonus, 0)), 2) AS avg_bonus_all
FROM employees;
-- Result: 7562.50 (60500 / 8)
Key distinction:
AVG(bonus)computes the average over 6 rows (ignoring NULLs).AVG(COALESCE(bonus, 0))computes over all 8 rows. Choose based on your business question.
MIN and MAX
MIN and MAX find the smallest and largest values. They work on numbers, text (alphabetical), and dates:
-- Salary range
SELECT
MIN(salary) AS lowest_salary,
MAX(salary) AS highest_salary,
MAX(salary) - MIN(salary) AS salary_range
FROM employees;
Result:
lowest_salary | highest_salary | salary_range
--------------+----------------+-------------
63000 | 95000 | 32000
-- Earliest and latest hire dates
SELECT
MIN(hire_date) AS oldest_hire,
MAX(hire_date) AS newest_hire
FROM employees;
-- Alphabetically first and last employee name
SELECT
MIN(name) AS first_alpha,
MAX(name) AS last_alpha
FROM employees;
Multiple Aggregates in One Query
You can use multiple aggregate functions in a single SELECT:
SELECT
COUNT(*) AS headcount,
COUNT(DISTINCT department) AS departments,
SUM(salary) AS payroll,
ROUND(AVG(salary), 0) AS avg_salary,
MIN(salary) AS min_salary,
MAX(salary) AS max_salary,
MAX(salary) - MIN(salary) AS range
FROM employees;
Result:
headcount | departments | payroll | avg_salary | min_salary | max_salary | range
----------+-------------+---------+------------+------------+------------+-------
8 | 3 | 634000 | 79250 | 63000 | 95000 | 32000
Aggregates with WHERE
WHERE filters rows before aggregation:
-- Stats for Finance department only
SELECT
COUNT(*) AS headcount,
SUM(salary) AS payroll,
ROUND(AVG(salary), 0) AS avg_salary
FROM employees
WHERE department = 'Finance';
headcount | payroll | avg_salary
----------+---------+-----------
3 | 251000 | 83667
-- Stats for employees hired after 2021
SELECT
COUNT(*) AS headcount,
ROUND(AVG(salary), 0) AS avg_salary
FROM employees
WHERE hire_date > '2021-12-31';
Aggregate Functions and NULL
All aggregate functions (except COUNT(*)) ignore NULL values:
-- Only 6 employees have bonuses; SUM, AVG, MIN, MAX ignore the 2 NULLs
SELECT
COUNT(*) AS total_rows, -- 8
COUNT(bonus) AS has_bonus, -- 6
SUM(bonus) AS sum_bonus, -- 60500 (6 employees)
AVG(bonus) AS avg_bonus -- 10083.33 (60500 / 6)
FROM employees;
This is often the right behaviour — but always be clear about whether NULLs should be treated as zero or excluded.
String Aggregation
Some databases let you concatenate strings across rows:
-- PostgreSQL / SQLite 3.44+
SELECT STRING_AGG(name, ', ' ORDER BY name) AS all_employees
FROM employees;
-- MySQL
SELECT GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') AS all_employees
FROM employees;
Result: Ananya Das, Arjun Nair, Dev Kumar, Kiran Mehta, Meera Singh, Priya Sharma, Raj Patel, Sunita Rao
Practical Examples
Example 1: Company-Wide Compensation Summary
SELECT
COUNT(*) AS total_employees,
ROUND(SUM(salary), 0) AS total_payroll,
ROUND(AVG(salary), 0) AS avg_salary,
MIN(salary) AS min_salary,
MAX(salary) AS max_salary,
ROUND(SUM(COALESCE(bonus, 0)), 0) AS total_bonuses
FROM employees;
Example 2: Technology Department Metrics
SELECT
COUNT(*) AS headcount,
ROUND(AVG(salary), 0) AS avg_salary,
MIN(hire_date) AS first_hire,
MAX(hire_date) AS latest_hire
FROM employees
WHERE department = 'Technology';
Example 3: Above-Average Earners Count
-- Count employees earning above the average salary
SELECT COUNT(*) AS above_avg_earners
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
Example 4: Bonus Statistics
SELECT
COUNT(bonus) AS employees_with_bonus,
COUNT(*) - COUNT(bonus) AS employees_without_bonus,
ROUND(AVG(bonus), 0) AS avg_bonus_for_recipients,
ROUND(AVG(COALESCE(bonus, 0)), 0) AS avg_bonus_all_employees,
MAX(bonus) AS highest_bonus
FROM employees;
Common Mistakes
1. Mixing aggregate and non-aggregate columns
-- Wrong — name is not in GROUP BY and not aggregated
SELECT name, AVG(salary)
FROM employees;
-- Error in PostgreSQL/MySQL (SQLite may return an arbitrary name)
-- Correct — either group by name or use only aggregates
SELECT AVG(salary) FROM employees;
-- OR
SELECT name, salary FROM employees;
2. Forgetting that AVG ignores NULLs
-- If you want to include NULLs as 0:
SELECT AVG(COALESCE(bonus, 0)) FROM employees;
-- If you want to count only non-NULL values:
SELECT AVG(bonus) FROM employees; -- already ignores NULLs
3. Using WHERE to filter aggregates
-- Wrong — cannot filter on aggregate result with WHERE
SELECT department, AVG(salary)
FROM employees
WHERE AVG(salary) > 80000; -- Error!
-- Correct — use HAVING (covered in the next chapter)
SELECT department, AVG(salary)
FROM employees
GROUP BY department
HAVING AVG(salary) > 80000;
Practice Exercises
- Find the total payroll cost for the Marketing department.
- Calculate the average, minimum, and maximum salary for the entire company.
- Count how many employees were hired after 2022.
- Find the highest salary among employees who have a bonus (
bonus IS NOT NULL). - What is the difference between
COUNT(*)andCOUNT(bonus)for the sample data? Calculate both.
Summary
In this chapter you learned:
COUNT(*)— counts all rows;COUNT(col)— counts non-NULL valuesSUM(col)— total; ignores NULLsAVG(col)— mean; ignores NULLs (denominator = non-NULL row count)MIN(col)/MAX(col)— extremes; work on numbers, text, dates- All aggregate functions except
COUNT(*)ignore NULL values - Combine multiple aggregates in one
SELECT - Use
WHEREto filter rows before aggregation - Use
COALESCE(col, 0)when you want NULLs treated as zero - Cannot mix non-aggregated columns with aggregates without
GROUP BY - Cannot filter on aggregate results with
WHERE— useHAVINGinstead
Next up: GROUP BY & HAVING — run aggregate functions on groups of rows and filter those groups.