Returning empty string when string_agg has no records
Use COALESCE
to catch and replace NULL
values:
SELECT f.name AS foo
, 'Bazzes: ' || COALESCE(string_agg(b.baz, ', '), '') AS bazzes
FROM foo f
LEFT JOIN bar b ON b.fooid = f.id
GROUP BY 1;
concat()
is another convenient option as you found yourself, in particular to concatenate multiple values. I suggest the variant concat_ws()
("with separator"), though, to avoid the trailing space.
concat_ws(' ', 'Bazzes:', string_agg(b.baz, ', ')) AS bazzes
Why NULL
?
Almost all aggregate function return NULL
if all source fields are NULL
(no non-null values, to be precise) - count()
being the exception for practical reasons. The manual:
It should be noted that except for
count
, these functions return a null value when no rows are selected. In particular,sum
of no rows returns null, not zero as one might expect, andarray_agg
returns null rather than an empty array when there are no input rows. Thecoalesce
function can be used to substitute zero or an empty array for null when necessary.
Related:
- How to concatenate columns in a Postgres SELECT?
- Combine two columns and add into one new column
The simplest method I have found to accomplish this is to swap out the string concatenation operator for the string concatenation function concat()
. For some reason the former apparently coerces the entire result to NULL
if one operand is null, as opposed to the latter which effectively casts any NULL
arguments to ''
.
So this query performs as desired:
SELECT
foo.name,
concat('Bazzes: ', string_agg(bar.baz, ', ')) AS bazzes
FROM
foo LEFT JOIN bar ON bar.fooid = foo.id
GROUP BY
foo.name
It returns the desired result set:
+--------+------------------------------------+
| foo | bazzes |
+--------+------------------------------------+
| FooOne | Bazzes: FooOneBazOne, FooOneBazTwo |
| FooTwo | Bazzes: |
+--------+------------------------------------+
Another option
A second approach is slightly more involved, but I found it first and it may be relevant in other situations, so I'll document it here as well. A CASE
statement can be used to count the number of elements before passing them to the aggregation function, and return an empty string instead of NULL
when the answer is zero.
In the original query, replace
string_agg(bar.baz, ', ')
with
CASE count(bar.baz) WHEN 0 THEN '' ELSE string_agg(bar.baz, ', ') END
and the string_agg
function will only be called when there is at least one string to aggregate. The empty string will be returned and combined correctly with the rest of the text otherwise, instead of coercing the whole result to NULL
.