Lesson 6 of 12

SQL Aggregate Functions

Many questions about data need one number, not a list: how many customers do we have? What is the average product price? When was the latest order? SQL aggregate functions answer these by taking a whole column of values and returning a single result. They are the base for the reports and dashboards you will build with GROUP BY in the next lesson.

The five common SQL aggregate functions

FunctionReturnsWorks on
COUNT()How many rows or valuesAny column, or *
SUM()The totalNumbers
AVG()The average (mean)Numbers
MIN()The smallest valueNumbers, text, dates
MAX()The largest valueNumbers, text, dates

All five work the same way in SQLite, MySQL, PostgreSQL and SQL Server, so what you learn here carries over directly.

COUNT: how many?

COUNT comes in three forms, and the difference matters:

  • COUNT(*) counts rows, whatever they contain.
  • COUNT(column) counts only rows where that column is not NULL.
  • COUNT(DISTINCT column) counts the different non-NULL values.
SELECT COUNT(*)                AS TotalCustomers,
       COUNT(State)            AS WithState,
       COUNT(DISTINCT Country) AS Countries
FROM Customers;

Returns 1 row: 12 customers, 10 with a state, and 3 different countries (India, UAE, Singapore).

Two customers have no state, so COUNT(State) gives 10, not 12. This makes COUNT(column) a quick way to check how complete your data is.

SUM and AVG

SUM adds up a numeric column and AVG calculates its mean. Combine them with WHERE to summarise just part of the table – the filter runs first, then the function works on the rows that are left.

SELECT SUM(Salary) AS TotalSalary,
       AVG(Salary) AS AverageSalary,
       COUNT(*)    AS People
FROM Employees
WHERE Department = 'Development';

Returns 1 row: TotalSalary 225000, AverageSalary 75000.0, People 3.

AVG always returns a decimal number in SQLite, even when the inputs are whole numbers. Averages often have long decimal tails, so wrap them in ROUND(value, 2) before showing them to people.

SUM is just as useful for quantities. The OrderDetails table has one row per product per order, so adding up its Quantity column for one product tells you how many packs of it have been ordered in total. Here product 1 is Filter Coffee Powder:

SELECT SUM(Quantity) AS PacksOrdered,
       COUNT(*)      AS OrderLines
FROM OrderDetails
WHERE ProductID = 1;

Returns 1 row: PacksOrdered 16 across 2 order lines (orders 1001 and 1008).

Note that AVG(x) is the same as SUM(x) / COUNT(x). Both ignore NULL values, so the average is taken over the rows that actually have a value.

MIN and MAX

MIN and MAX find the smallest and largest values. They work on text (alphabetical order) and on our YYYY-MM-DD dates too, which makes them handy for finding the first and last order.

SELECT MIN(Price)           AS Cheapest,
       MAX(Price)           AS Dearest,
       ROUND(AVG(Price), 2) AS AveragePrice
FROM Products;

Returns 1 row: Cheapest 54, Dearest 420, AveragePrice 161.93.

SELECT MIN(OrderDate) AS FirstOrder,
       MAX(OrderDate) AS LatestOrder
FROM Orders;

Returns 1 row: FirstOrder 2026-01-05, LatestOrder 2026-03-21.

How aggregates handle NULL

Apart from COUNT(*), every aggregate function simply skips NULL values. Usually that is what you want, but be careful with averages: if some salaries were missing, AVG(Salary) would average only the known ones, not treat the missing ones as zero.

When no rows match at all, COUNT returns 0 but SUM, AVG, MIN and MAX return NULL. To show 0 instead, use COALESCE(SUM(Quantity), 0). COALESCE returns the first value in its list that is not NULL and works in all major databases. SQLite also has its own TOTAL() function, which returns 0.0 instead of NULL, but it does not exist in MySQL.

Watch out: Do not mix aggregate functions with ordinary columns, as in SELECT ProductName, MAX(Price) FROM Products;. SQLite allows it and has a special rule that happens to return the matching product for MAX, but MySQL in its default strict mode (ONLY_FULL_GROUP_BY) rejects the query, and other databases do too. To get “the row with the highest price”, use ORDER BY Price DESC LIMIT 1, or group your rows as shown in the next lesson.

Where aggregates are used

On a website, aggregate queries fill the numbers in an admin dashboard, the “(24)” next to a category name, or the average star rating on a product page. Because the database does the counting, your PHP or JavaScript code receives one small row instead of thousands. Next, SQL GROUP BY and HAVING shows how to get one result per department, category or customer. To revisit filtering, see SQL WHERE.

Further reading

Key takeaways

  • Aggregate functions turn many rows into one value: COUNT, SUM, AVG, MIN and MAX.
  • COUNT(*) counts rows; COUNT(column) skips NULLs; COUNT(DISTINCT column) counts different values.
  • WHERE filters rows before the aggregate is calculated.
  • Use ROUND(AVG(x), 2) for readable averages and COALESCE(SUM(x), 0) to avoid NULL totals.
  • Do not mix plain columns with aggregates unless you use GROUP BY.

Quick quiz

Check what you learned – pick an answer.

1. The Customers table has 12 rows, and exactly one of them has a NULL PostalCode. What does COUNT(PostalCode) return?

2. Which function would you use to find the date of the latest order?

3. What does SELECT SUM(Quantity) FROM OrderDetails WHERE ProductID = 99; return, given no such product exists?

Practice exercise

Find how many orders have the status 'Delivered'. Name the result column DeliveredOrders.

Show a hint

Use COUNT(*) with a WHERE clause.

Show the solution
SELECT COUNT(*) AS DeliveredOrders
FROM Orders
WHERE Status = 'Delivered';

Progress is saved in this browser only.