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.