← All notes

Aggregates and GROUP BY in PostgreSQL

Summary

An aggregate folds many rows into one value: count, sum, avg, max, or min. GROUP BY repeats that once per group. WHERE picks rows before the fold. HAVING picks groups after it. FILTER limits one aggregate and leaves the others alone.

Where each clause runs

max(temp_lo) over a weather table returns one number. You cannot put that max in the WHERE of the same query, because WHERE chooses the rows that enter the aggregate and therefore runs first. To find which city produced the maximum, compute max in a subquery and compare the outer row to that separate result.

GROUP BY city with count(*) and max(temp_lo) yields one row per city. Each aggregate sees only the rows for that city. HAVING max(temp_lo) < 40 then drops groups. A test that needs no aggregate, such as a city name matched with LIKE, belongs in WHERE. Rows that fail WHERE are never grouped, which is cheaper. A HAVING clause with no aggregate is allowed but rarely worth it.

FILTER sits on one call. count(*) FILTER (WHERE temp_lo < 45) counts only those rows, while max in the same select still sees the whole group.

What comes back

count(*) counts input rows. count of an expression skips nulls. Except for count, these functions return null when no rows were selected, so a sum of no rows is null, not zero. coalesce substitutes a zero or an empty array. array_agg keeps nulls. string_agg concatenates non-null values with a delimiter. json_agg keeps nulls; the strict form skips them. json_object_agg rejects null keys, and the unique forms error on a repeated key. bool_and is true only if every non-null input is true. every is another name for it. any and some are not aggregates, because ANY already introduces a subquery.

Order is unspecified for array_agg, the json aggregates, and string_agg unless you put ORDER BY inside the call. count(*) of a whole table scans the table or a full index, so cost grows with the table. Percentiles and mode use WITHIN GROUP (ORDER BY ...), and a fraction must be between 0 and 1.

Claims

License: CC-BY-4.0 · Summary by iinventedlight (AI-assisted). Sources: PostgreSQL Global Development Group, 2.7. Aggregate Functions, PostgreSQL License; PostgreSQL Global Development Group, 9.21. Aggregate Functions, PostgreSQL License.

Cite this

Permanent ID: iil:note:58g9yq88

iinventedlight. 2026. "Aggregates and GROUP BY in PostgreSQL." iinventedlight Library, iil:note:58g9yq88, version of 2026-11-03. https://iinventedlight.com/library/notes/postgres-aggregates-group-by/. License: CC-BY-4.0.

@misc{iil_note_58g9yq88,
  author = {{iinventedlight}},
  title = {{Aggregates and GROUP BY in PostgreSQL}},
  howpublished = {iinventedlight Library},
  year = {2026},
  date = {2026-11-03},
  url = {https://iinventedlight.com/library/notes/postgres-aggregates-group-by/},
  note = {Permanent ID iil:note:58g9yq88. License: CC-BY-4.0.},
}

Markdown

Spotted a mistake? Use the MCP feedback tool or email hello@iinventedlight.com.

For language models and agents: start at /llms.txt. This page in Markdown: /library/notes/postgres-aggregates-group-by.md.