Skip to content
Laviyx Learn

Try

SQL course menu

SQL Aggregate Functions: COUNT, SUM, AVG, MIN, MAX and More

A complete guide to SQL aggregate functions: how COUNT, SUM, AVG, MIN and MAX work together, how they treat NULLs and empty results, string aggregation, and the rules for combining aggregates with GROUP BY.

Intermediate8 min readUpdated
On this page

An aggregate function takes many values and returns one: a count, a total, an average. You have met each of them individually. This lesson puts them together and covers the rules that apply to all of them.

The example tables#

employees

namedepartmentsalary
AshaSales52000
RaviEngineering78000
MeeraEngineering81000
JohnMarketing46000
PriyaSales61000

products

namecategorypricestock
LaptopElectronics5500012
HeadphonesElectronics250040
Desk ChairFurniture70008
NotebookStationery60500
MonitorElectronics120000
BookshelfFurniture45005

The five core aggregates#

FunctionReturnsWorks on
COUNTnumber of rows or valuesany type
SUMtotalnumbers
AVGmeannumbers
MINsmallestnumbers, dates, text
MAXlargestnumbers, dates, text

A complete summary in one query#

Use several aggregates side by side to build a summary report:

SQLRun
SELECT department,
       COUNT(*)              AS people,
       SUM(salary)           AS payroll,
       ROUND(AVG(salary), 0) AS avg_salary,
       MIN(salary)           AS lowest,
       MAX(salary)           AS highest
FROM employees
GROUP BY department
ORDER BY department;
Output
department  | people | payroll | avg_salary | lowest | highest
------------+--------+---------+------------+--------+--------
Engineering | 2      | 159000  | 79500      | 78000  | 81000
Marketing   | 1      | 46000   | 46000      | 46000  | 46000
Sales       | 2      | 113000  | 56500      | 52000  | 61000

All five functions see the same groups, so the numbers are consistent with each other.

Rule 1: aggregates collapse rows#

Without GROUP BY, the whole table becomes one group and you get exactly one row. With GROUP BY, you get one row per group. Every column you select must therefore be either inside an aggregate or listed in GROUP BY.

SQLRun
-- Valid: department is grouped, everything else is aggregated
SELECT department, MAX(salary) FROM employees GROUP BY department;

-- Invalid in most databases: name is neither grouped nor aggregated
SELECT department, name, MAX(salary) FROM employees GROUP BY department;

Rule 2: aggregates skip NULLs#

All aggregates except COUNT(*) ignore NULL values. That makes AVG an average of known values, and COUNT(column) a count of filled-in values.

Rule 3: empty input gives NULL (except COUNT)#

When no rows reach the aggregate:

SQLRun
SELECT COUNT(*)    AS n,
       SUM(amount) AS total,
       AVG(amount) AS average
FROM orders
WHERE amount > 10000;
Output
n | total | average
--+-------+--------
0 | NULL  | NULL

COUNT returns 0; the others return NULL. Wrap them in COALESCE(..., 0) if your report needs zeros.

Rule 4: aggregates cannot go in WHERE#

Use HAVING to filter on an aggregate result, or a subquery to compare rows with one. See HAVING and Subqueries.

Aggregating expressions#

The argument can be any expression, calculated per row before aggregating:

SQLRun
SELECT category,
       COUNT(*)   AS products,
       SUM(stock) AS units,
       MIN(price) AS min_price,
       MAX(price) AS max_price
FROM products
GROUP BY category
ORDER BY units DESC;
Output
category    | products | units | min_price | max_price
------------+----------+-------+-----------+----------
Stationery  | 1        | 500   | 60        | 60
Electronics | 3        | 52    | 2500      | 55000
Furniture   | 2        | 13    | 4500      | 7000

DISTINCT inside aggregates#

COUNT(DISTINCT x), SUM(DISTINCT x) and AVG(DISTINCT x) consider each different value only once. COUNT(DISTINCT customer_id) is very common ("how many different customers ordered?"); the others are rarely what you want.

Joining text: string aggregation#

Most databases can also combine text from many rows into one string:

SQLRun
-- SQLite (GROUP_CONCAT also works in MySQL)
SELECT department,
       GROUP_CONCAT(name, ', ') AS members
FROM employees
GROUP BY department
ORDER BY department;
Output
department  | members
------------+------------
Engineering | Ravi, Meera
Marketing   | John
Sales       | Asha, Priya
DatabaseFunction
PostgreSQL, SQL Server 2017+STRING_AGG(name, ', ')
MySQLGROUP_CONCAT(name SEPARATOR ', ')
SQLiteGROUP_CONCAT(name, ', ')
OracleLISTAGG(name, ', ') WITHIN GROUP (ORDER BY name)

Statistical aggregates#

Many databases also provide STDDEV (standard deviation), VARIANCE, and percentile functions such as PERCENTILE_CONT for medians. Names vary; check your database's documentation.

Aggregates vs window functions#

Aggregates collapse rows: ten employees become three department rows. Sometimes you want the department total next to each employee instead. That is what window functions do: SUM(salary) OVER (PARTITION BY department).

Common mistakes#

Practice#

Practice exercise

The monthly_sales table has columns month, region and revenue:

monthregionrevenue
2026-01North120
2026-02North150
2026-03North110
2026-04North170
2026-01South90
2026-02South90
2026-03South130
2026-04South125

For each region, return the total revenue, the average monthly revenue rounded to one decimal place, and the best month's revenue.

Show hint

One GROUP BY region with SUM, ROUND(AVG(...), 1) and MAX.

Show solution
SQLRun
SELECT region,
       SUM(revenue)           AS total,
       ROUND(AVG(revenue), 1) AS avg_month,
       MAX(revenue)           AS best_month
FROM monthly_sales
GROUP BY region;
Output
region | total | avg_month | best_month
-------+-------+-----------+-----------
North  | 550   | 137.5     | 170
South  | 435   | 108.8     | 130

South's average is 435 ÷ 4 = 108.75, rounded to 108.8.

Key takeaway#

Finished this lesson?