String Aggregation
Concatenate string values from multiple rows into a single string, optionally with a delimiter and ordering.
STRING_AGG GROUP_CONCAT LISTAGG WM_CONCAT string aggregation concatenation aggregate
aggregate string GROUP_CONCAT STRING_AGG LISTAGG concatenation
| Database | Status | Syntax Family | Min Version |
|---|---|---|---|
| PostgreSQL | full | string-agg | - |
| MySQL | full | group-concat | - |
| MariaDB | full | string-agg | - |
| Oracle | full | listagg | - |
| SQL Server | full | string-agg | 2017 |
| SQLite | full | group-concat | - |
| Redshift | full | listagg | - |
| DB2 | full | listagg | - |
| Snowflake | full | listagg | - |
| BigQuery | full | string-agg | - |
Compare Databases
Database Details
PostgreSQL
full
-- Concatenate product names per category
SELECT
category,
STRING_AGG(name, ', ' ORDER BY name) AS product_list
FROM products
GROUP BY category
ORDER BY category;
-- With DISTINCT
SELECT STRING_AGG(DISTINCT status, ', ' ORDER BY status)
FROM orders;- ORDER BY clause inside STRING_AGG controls concatenation order.
- NULL values are automatically skipped.
- DISTINCT can be used to deduplicate values before aggregation.
- ARRAY_AGG is available for array output instead of string concatenation.
Docs: STRING_AGG
MySQL
full
-- Concatenate product names per category
SELECT
category,
GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') AS product_list
FROM products
GROUP BY category
ORDER BY category;
-- With DISTINCT
SELECT GROUP_CONCAT(DISTINCT status ORDER BY status SEPARATOR ', ')
FROM orders;- Default separator is comma; use SEPARATOR keyword for a custom delimiter.
- Result truncated at group_concat_max_len (default 1024 bytes); raises a warning, not an error.
- Increase with SET SESSION group_concat_max_len = 1000000 for larger results.
- DISTINCT supported for deduplication.
Docs: GROUP_CONCAT
MariaDB
full
-- GROUP_CONCAT (MySQL-compatible)
SELECT category,
GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') AS product_list
FROM products
GROUP BY category;
-- STRING_AGG (PostgreSQL/SQL Server compatible, MariaDB 10.3.3+)
SELECT category,
STRING_AGG(name, ', ' ORDER BY name) AS product_list
FROM products
GROUP BY category;- Supports both GROUP_CONCAT (MySQL-compatible) and STRING_AGG (added in 10.3.3).
- STRING_AGG syntax is compatible with PostgreSQL and SQL Server.
- group_concat_max_len system variable limits GROUP_CONCAT output length.
Docs: GROUP_CONCAT, STRING_AGG
Oracle
full
-- Basic LISTAGG
SELECT
category,
LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS product_list
FROM products
GROUP BY category
ORDER BY category;
-- With overflow handling (Oracle 12.2+)
SELECT LISTAGG(name, ', ' ON OVERFLOW TRUNCATE '...' WITH COUNT)
WITHIN GROUP (ORDER BY name)
FROM products;- LISTAGG added in Oracle 11.2.
- WITHIN GROUP (ORDER BY ...) is required syntax.
- Result limited to 4000 characters (VARCHAR2 limit) by default.
- ON OVERFLOW TRUNCATE (12.2+) handles graceful truncation with an indicator.
- LISTAGG also works as an analytic (window) function since Oracle 12.2.
Docs: LISTAGG
SQL Server
full
-- STRING_AGG (SQL Server 2017+)
SELECT
category,
STRING_AGG(name, ', ') WITHIN GROUP (ORDER BY name) AS product_list
FROM products
GROUP BY category
ORDER BY category;
-- Pre-2017 workaround using FOR XML PATH
SELECT category,
STUFF((
SELECT ', ' + name
FROM products p2
WHERE p2.category = p.category
ORDER BY name
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'), 1, 2, '') AS product_list
FROM products p
GROUP BY category;- STRING_AGG added in SQL Server 2017.
- WITHIN GROUP (ORDER BY ...) controls concatenation order.
- Pre-2017 workaround uses FOR XML PATH with STUFF() to remove the leading delimiter.
- No built-in length limit (up to NVARCHAR(MAX)).
Docs: STRING_AGG
SQLite
full
-- GROUP_CONCAT with custom separator
SELECT
category,
GROUP_CONCAT(name, ', ') AS product_list
FROM products
GROUP BY category;
-- Default separator is comma
SELECT GROUP_CONCAT(name) FROM products WHERE category = 'electronics';- GROUP_CONCAT is the only string aggregation function.
- Default separator is a comma.
- No ORDER BY clause inside the aggregate; use a subquery or CTE to pre-sort.
- No length limit enforced by SQLite itself (limited by available memory).
Docs: Aggregate Functions
Redshift
full
SELECT
category,
LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS product_list
FROM products
GROUP BY category
ORDER BY category;- LISTAGG with WITHIN GROUP (ORDER BY ...) is the string aggregation function.
- Result limited to varchar maximum size.
- WITHIN GROUP ORDER BY is supported for deterministic ordering.
Docs: LISTAGG
DB2
full
SELECT
category,
LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS product_list
FROM products
GROUP BY category
ORDER BY category;- LISTAGG added in DB2 9.7 Fix Pack 4 on LUW.
- WITHIN GROUP (ORDER BY ...) controls ordering.
- Older versions use XMLAGG-based workarounds.
- Result length is limited by the VARCHAR size limit.
Docs: LISTAGG
Snowflake
full
-- LISTAGG
SELECT
category,
LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS product_list
FROM products
GROUP BY category
ORDER BY category;
-- ARRAY_AGG for array output
SELECT category, ARRAY_AGG(name) FROM products GROUP BY category;- LISTAGG is the string aggregation function.
- WITHIN GROUP (ORDER BY ...) controls ordering.
- ARRAY_AGG is available for producing array output instead of a string.
- LISTAGG result size limited by Snowflake's VARCHAR max (16 MB).
Docs: LISTAGG
BigQuery
full
-- STRING_AGG with ORDER BY and LIMIT
SELECT
category,
STRING_AGG(name, ', ' ORDER BY name LIMIT 100) AS product_list
FROM products
GROUP BY category
ORDER BY category;
-- ARRAY_AGG for array output
SELECT category, ARRAY_AGG(name ORDER BY name) FROM products GROUP BY category;- STRING_AGG supports ORDER BY inside the aggregate for deterministic ordering.
- LIMIT clause inside STRING_AGG caps the number of items included.
- NULL values are ignored by default.
- ARRAY_AGG is also available for array output.
- STRING_AGG(DISTINCT ...) for deduplication.
Docs: STRING_AGG