xmlagg
PG 8.3+→ xmlAggregates XML values into a concatenated XML sequence. Equivalent to string_agg for XML.
Signature
xmlagg ( xml [ORDER BY sort_expression] ) → xmlParameters
| Parameter | Type | Description |
|---|---|---|
| xml_expression | xml | XML value to aggregate |
Examples
SELECT xmlagg(xmlelement(NAME item, name) ORDER BY name) FROM products;<item>Apple</item><item>Banana</item>...SELECT xmlelement(NAME catalog, xmlagg(xmlelement(NAME product, XMLATTRIBUTES(id), name) ORDER BY id)) FROM products;Full product catalog XMLSELECT dept_id, xmlagg(xmlelement(NAME employee, XMLATTRIBUTES(id), name) ORDER BY name) AS staff FROM employees GROUP BY dept_id;Per-department employee XML listsSELECT xmlelement(NAME feed, XMLATTRIBUTES('2.0' AS version), xmlagg(xmlelement(NAME entry, xmlforest(title, published_at AS pubDate, body AS content)) ORDER BY published_at DESC)) FROM posts;RSS-style XML feed ordered by publication dateFetching all XML rows into application code and concatenating them in a loop wastes network bandwidth and forces the application to handle XML serialization. `xmlagg` performs the aggregation entirely in the database.
✓ Instead: Use `xmlagg` with an `ORDER BY` clause inside the aggregate to produce a deterministically ordered XML sequence without a round-trip to the application.
Combine `xmlagg` with `GROUP BY` to build XML with nested collections: outer SELECT wraps in a root element, inner `xmlagg` collects child elements per group.
SELECT xmlelement(NAME category, XMLATTRIBUTES(c.name), xmlagg(xmlelement(NAME product, p.name))) FROM categories c JOIN products p ON p.cat_id = c.id GROUP BY c.id, c.name;Products grouped under category XML elementsCommon Gotchas
string_agg without ORDER BY produces non-deterministic results
string_agg(col, ',') returns results in arbitrary order unless you specify ORDER BY inside the aggregate.
COUNT(col) skips NULLs but COUNT(*) does not
COUNT(column_name) counts non-NULL values only. COUNT(*) counts all rows. These produce different results when the column has NULLs.