Je connaissais la technique, grâce entre autre aux explications de Stéphane Bortzmeyer, mais je ne savais pas comment passer le séparateur. J'ai donc posé la question sur le canal IRC dédié à PostgreSQL ( irc.freenode.net#postgresql ), et en quelques minutes, j'ai eu l'explication de la part d'Andrew « RhodiumToad » Gierth.

Il s'agissait d'utiliser une syntaxe que je connaissais pas, qui consiste à passer les types de données de l'agrégat avant sa définition :

CREATE OR REPLACE FUNCTION string_concat(text, text, text)
RETURNS text 
 LANGUAGE SQL
AS $$
    SELECT CASE WHEN $1 IS NULL OR $1 = '' THEN $2
                WHEN $2 IS NULL OR $2 = '' THEN $1
                ELSE $1 || coalesce($3, '' ) || $2
                END; 
 $$ ;

CREATE AGGREGATE string_agg( text, text )  (
    SFUNC = string_concat,
    STYPE = text
);

L'utilisation est ensuite la même que pour l'agrégat de la version 9.0 :

 > select string_agg(relname,', ') from ( select relname from pg_class where relkind='r' order by relpages desc limit 5 ) x  ; 
string_agg     
----------
 pg_proc, pg_depend, pg_attribute, pg_description, pg_operator
(1 row)

> select string_agg(relname,' / ') from ( select relname from pg_class where relkind='r' order by relpages desc limit 5 ) x  ; 
string_agg                             
----------
 pg_proc / pg_depend / pg_attribute / pg_description / pg_operator
(1 row)