PostgreSQL Aggregate Functions

Aggregate functions collapse many rows into a single summary value, such as a total, an average, or a count.

What is an aggregate function?

An aggregate function takes a set of rows as input and returns a single row as output. Instead of showing you every individual value in a column, PostgreSQL reduces them down to one number that describes the whole group, or each group if you use GROUP BY.

Aggregate functions are the backbone of reporting queries. Whenever you need a total, an average, a headcount, or a highest or lowest value, you reach for an aggregate function instead of pulling every row back to your application and doing the math yourself.

The core aggregate functions

PostgreSQL ships with a full set of built-in aggregates. The five you will use constantly are:

  • COUNT() - counts the number of rows
  • SUM() - adds up numeric values
  • AVG() - computes the arithmetic mean
  • MIN() - finds the smallest value
  • MAX() - finds the largest value
FunctionPurposeIgnores NULL?
COUNT(column)Number of non-null rowsYes
COUNT(*)Number of rows, including nullsNo
SUM(column)Total of all valuesYes
AVG(column)Mean of all valuesYes
MIN(column) / MAX(column)Smallest / largest valueYes

Count all rows in a table

SELECT COUNT(*) AS total_employees
FROM employees;
Note: COUNT(*) counts every row regardless of NULL values, while COUNT(column_name) only counts rows where that specific column is not NULL. This distinction trips up a lot of beginners.

Aggregating with GROUP BY

Aggregate functions become far more powerful when paired with GROUP BY. Instead of one summary value for the entire table, GROUP BY splits the rows into buckets and computes the aggregate separately for each bucket.

Average salary per department

SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
ORDER BY avg_salary DESC;

Filter groups with HAVING

SELECT department_id, COUNT(*) AS headcount
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 5;
Note: Use WHERE to filter individual rows before grouping, and HAVING to filter groups after aggregation. WHERE cannot reference an aggregate function directly, but HAVING can.
Note: Every column in the SELECT list that is not wrapped in an aggregate function must appear in the GROUP BY clause, or PostgreSQL will raise an error.

Exercise: PostgreSQL Aggregate Functions

What is the difference between COUNT(*) and COUNT(column_name)?