Functions2026-08-28

SQL String Aggregation: GROUP_CONCAT, STRING_AGG & LISTAGG Compared

Concatenate multiple rows into one string in SQL: MySQL GROUP_CONCAT, PostgreSQL STRING_AGG, SQL Server STRING_AGG, Oracle LISTAGG — with separators and ordering.

The "Rows Into One Cell" Problem

You have an orders table and a tags table, and you want one row per order with all its tags in a single comma-separated cell. This is string aggregation, and every database has its own function with slightly different syntax. I'll show the four dialects side by side because I keep needing this on client projects.

MySQL: GROUP_CONCAT

SELECT o.id, GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ', ') AS tags FROM orders o LEFT JOIN order_tags ot ON ot.order_id = o.id LEFT JOIN tags t ON t.id = ot.tag_id GROUP BY o.id;

Default separator is a comma with no space; use SEPARATOR to change it. ORDER BY inside GROUP_CONCAT controls the output order — MySQL also supports DISTINCT inside it.

PostgreSQL & SQL Server: STRING_AGG

SELECT o.id, STRING_AGG(t.name, ', ' ORDER BY t.name) AS tags FROM orders o LEFT JOIN order_tags ot ON ot.order_id = o.id LEFT JOIN tags t ON t.id = ot.tag_id GROUP BY o.id;

Same function name in both, same signature. PostgreSQL 14+ lets you add DISTINCT: STRING_AGG(DISTINCT t.name, ', ').

Oracle: LISTAGG

SELECT o.id, LISTAGG(t.name, ', ') WITHIN GROUP (ORDER BY t.name) AS tags FROM orders o JOIN order_tags ot ON ot.order_id = o.id JOIN tags t ON t.id = ot.tag_id GROUP BY o.id;

Oracle puts the ORDER BY in a WITHIN GROUP clause — that ordering syntax trips up everyone migrating from MySQL.

The Length Trap

MySQL GROUP_CONCAT silently truncates at group_concat_max_len (default 1024 bytes) — your "full list" comes back cut off with no error. Raise it: SET SESSION group_concat_max_len = 65535; Oracle LISTAGG throws ORA-01489 when the result exceeds 4000 chars, so you'll need the ON OVERFLOW TRUNCATE clause (12c+) or a CLOB workaround.

Deduplication & Filtering

To skip NULL tags: MySQL GROUP_CONCAT ignores NULLs automatically; PostgreSQL STRING_AGG doesn't — filter with a WHERE clause or STRING_AGG(t.name, ', ') FILTER (WHERE t.name IS NOT NULL).

Try It Yourself

Type "concat all tags for each order into one column" into our free SQL generator and select your database — you'll get the right function with the right syntax.