What aggregate functions do
An aggregate function collapses many rows into a single value per group — a sum,
an average, a row count, a concatenated string. They pair with GROUP BY to
produce one result row per group, or run over the whole table with no GROUP BY.
This reference lists the common aggregates, their NULL behaviour, and which of
MySQL, PostgreSQL, SQLite and SQL Server support each name.
How it works
Each aggregate scans the rows of a group and folds them into one value, ignoring
NULL inputs (except COUNT(*)):
SELECT department,
COUNT(*) AS headcount,
AVG(salary) AS avg_salary,
SUM(DISTINCT bonus) AS unique_bonus_total,
STRING_AGG(name, ', ') AS roster
FROM employees
GROUP BY department;
DISTINCT inside an aggregate restricts it to unique values. String aggregation
is the least portable area: MySQL/SQLite spell it GROUP_CONCAT while
PostgreSQL/SQL Server use STRING_AGG. The tool filters the table as you type
and flags which databases provide each function.
String aggregation across dialects
Concatenating grouped strings is the most common portability trap in aggregate SQL. The four major dialects each have their own spelling:
| Database | Function | Separator syntax |
|---|---|---|
| MySQL | GROUP_CONCAT(col SEPARATOR ', ') | Inside the call |
| SQLite | GROUP_CONCAT(col, ', ') | As a second argument |
| PostgreSQL | STRING_AGG(col, ', ') | As a second argument |
| SQL Server | STRING_AGG(col, ', ') | As a second argument |
| Oracle | LISTAGG(col, ', ') WITHIN GROUP (ORDER BY col) | WITHIN GROUP clause |
MySQL and SQLite also accept ORDER BY inside GROUP_CONCAT, while PostgreSQL’s STRING_AGG takes an ORDER BY clause in WITHIN GROUP. Getting this right matters whenever you move SQL between databases.
NULL handling — the one exception you must know
Every standard aggregate except COUNT(*) ignores NULL rows silently. This surprises developers when an expected sum or average returns NULL rather than zero: if every value in the group is NULL, the result is NULL. Wrap with COALESCE(SUM(col), 0) whenever a zero result is more appropriate than NULL.
COUNT(*) counts every row, even those where every column is NULL. COUNT(col) counts only the non-NULL values in that column. The two return different numbers whenever NULLs are present.
Tips and notes
COUNT(*)never returns NULL; the all-NULL group still counts its rows.AVGof an empty or all-NULL set isNULL, not0— wrap inCOALESCEif needed.- Mixing aggregated and non-aggregated columns requires every plain column in
GROUP BY. FILTER (WHERE ...)(Postgres/SQLite) is a cleaner alternative toSUM(CASE WHEN ...).SUM(DISTINCT col)sums only unique values — useful for de-duplicating joins before aggregating.