Aggregation

Aggregating and shaping query output in PostgreSQL

By Owen Middleton · Updated September 2026 · Examples run on PostgreSQL 17

PostgreSQL renames most of the functions that collect many rows into one value. Some replacements need an argument your old engine let you leave out. A few run without complaint and give you the wrong number.

Find what you typed in the table below. Each row gives the error PostgreSQL raises, the call that replaces it, and a guide.

What to write instead, by engine

You wrote PostgreSQL says Write instead
GROUP_CONCAT(city) function group_concat(text) does not exist string_agg(city, ', ')
The delimiter is a required argument in PostgreSQL. MySQL defaults it to a comma.
STRING_AGG(city) function string_agg(text) does not exist string_agg(city, ', ')
The function exists, but no overload takes one argument. PostgreSQL names the argument types in this error whether or not the name exists at all, so it reads like an absence.
LISTAGG(city, ',') function listagg(text, unknown) does not exist string_agg(city, ',')
Keep the delimiter the reader wrote. A space after the comma is a different string.
SELECT name FROM customers FOR JSON PATH syntax error at or near "JSON" jsonb_agg(jsonb_build_object('name', name))
Two functions, not a clause: one shapes each object, the other collects them. SQL Server drops null fields from its output by default and this keeps them, so the documents are not the same shape.
JSON_OBJECTAGG(city, id) syntax error at or near "," jsonb_object_agg(city, id)
PostgreSQL 16 added the SQL standard JSON_OBJECTAGG too, but it takes its pair as key VALUE value. The comma form is MySQL’s. Either way the key may not be null, and 9 of these cities are.
PIVOT (count(id) FOR status IN ('pending')) syntax error at or near "(" count(*) FILTER (WHERE status = 'pending')
One aggregate per column, and no extension to install.

One entry is missing from that table on purpose. crosstab, the function most answers reach for when pivoting, lives in the tablefunc extension, which ships with PostgreSQL and is switched on per database. Whether you have it depends on your server, not on PostgreSQL, so the pivot guide covers it separately.

When the function has a different name here

You used another engine's function name and PostgreSQL rejected it.

When the query runs and the answer is wrong

The query runs and PostgreSQL does not complain. The number or the list is quietly not what you meant.

The concepts these guides build on

Each of these is a lesson in the free course, with its own practice problems. A guide answers one question; a lesson teaches the mechanism underneath it.

Where to practise this

Every guide in this cluster ends in two graded exercises that run in your browser, and every concept page carries the practice problems for its own lesson. No account, nothing to install, nothing to pay.

If you would rather work through aggregation in order than answer one question, the library index lists all 66 concepts, and the course itself picks the next one for you and schedules review as you go.