web-dev-qa-db-fra.com

comment exclure les valeurs nulles dans array_agg comme dans string_agg en utilisant postgres?

Si j'utilise array_agg Pour collecter les noms, je reçois mes noms séparés par des virgules, mais dans le cas où il existe une valeur null, ce null est également pris comme nom dans l'agrégat. Par exemple :

SELECT g.id,
       array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END) canonical_users,
       array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END) non_canonical_users
FROM groups g
GROUP BY g.id;

il renvoie ,Larry,Phil au lieu de simplement Larry,Phil (dans mon 9.1.2, il affiche NULL,Larry,Phil). comme dans this violon

Au lieu de cela, si j'utilise string_agg(), il ne me montre que les noms (sans virgules vides ni null) comme ici

Le problème est que j'ai Postgres 8.4 Installé sur le serveur et que string_agg() ne fonctionne pas là-bas. Existe-t-il un moyen de faire fonctionner array_agg similaire à string_agg ()?

70
Daud

SQL Fiddle

select
    id,
    (select array_agg(a) from unnest(canonical_users) a where a is not null) canonical_users,
    (select array_agg(a) from unnest(non_canonical_users) a where a is not null) non_canonical_users
from (
    SELECT g.id,
           array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END) canonical_users,
           array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END) non_canonical_users
    FROM groups g
    GROUP BY g.id
) s

Ou, plus simple et peut être moins cher, en utilisant array_to_string qui élimine les null:

SELECT
    g.id,
    array_to_string(
        array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END)
        , ','
    ) canonical_users,
    array_to_string(
        array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END)
        , ','
    ) non_canonical_users
FROM groups g
GROUP BY g.id

SQL Fiddle

21
Clodoaldo Neto

Avec postgresql-9.3, on peut le faire;

SELECT g.id,
   array_remove(array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END), NULL) canonical_users,
   array_remove(array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END), NULL) non_canonical_users
FROM groups g 
GROUP BY g.id;

Mise à jour: avec postgresql-9.4;

SELECT g.id,
   array_agg(g.users) FILTER (WHERE g.canonical = 'Y') canonical_users,
   array_agg(g.users) FILTER (WHERE g.canonical = 'N') non_canonical_users
FROM groups g 
GROUP BY g.id;
194
Dale O'Brien

Pour résoudre la question générale de la suppression des valeurs NULL des agrégats de tableaux, il existe deux façons principales d'attaquer le problème: soit en faisant array_agg (unnest (array_agg (x)), soit en créant un agrégat personnalisé.

Le premier est de la forme indiquée ci-dessus :

SELECT 
    array_agg(u) 
FROM (
    SELECT 
        unnest(
            array_agg(v)
        ) as u 
    FROM 
        x
    ) un
WHERE 
    u IS NOT NULL;

La deuxième:

/*
With reference to
http://ejrh.wordpress.com/2011/09/27/denormalisation-aggregate-function-for-postgresql/
*/
CREATE OR REPLACE FUNCTION fn_array_agg_notnull (
    a anyarray
    , b anyelement
) RETURNS ANYARRAY
AS $$
BEGIN

    IF b IS NOT NULL THEN
        a := array_append(a, b);
    END IF;

    RETURN a;

END;
$$ IMMUTABLE LANGUAGE 'plpgsql';

CREATE AGGREGATE array_agg_notnull(ANYELEMENT) (
    SFUNC = fn_array_agg_notnull,
    STYPE = ANYARRAY,
    INITCOND = '{}'
);

Appeler le second est (naturellement) un peu plus joli que le premier:

sélectionnez array_agg_notnull (v) parmi x;

11
rorycl

J'ajoute cela même si ce fil est assez ancien, mais j'ai rencontré cette astuce intéressante qui fonctionne assez bien sur les petits tableaux. Il fonctionne sur Postgres 8.4+ sans bibliothèques ni fonctions supplémentaires.

string_to_array(array_to_string(array_agg(my_column)))::int[]

La méthode array_to_string() supprime en fait les valeurs nulles.

7
ced-b

Comme cela a été suggéré dans les commentaires, vous pouvez écrire une fonction pour remplacer les valeurs nulles dans un tableau, mais comme cela a également été souligné dans le thread lié à dans les commentaires, ce type de résultat nuit à l'efficacité de la fonction d'agrégation si vous devez créer un agrégat , le diviser puis l'agréger à nouveau.

Je pense que garder les valeurs nulles dans le tableau est juste une caractéristique (peut-être indésirable) d'Array_Agg. Vous pouvez utiliser des sous-requêtes pour éviter cela:

SELECT  COALESCE(y.ID, n.ID) ID,
        y.Users,
        n.Users
FROM    (   SELECT  g.ID, ARRAY_AGG(g.Users) AS Users
            FROM    Groups g
            WHERE   g.Canonical = 'Y'
            GROUP BY g.ID
        ) y
        FULL JOIN 
        (   SELECT  g.ID, ARRAY_AGG(g.Users) AS Users
            FROM    Groups g
            WHERE   g.Canonical = 'N'
            GROUP BY g.ID
        ) n
            ON n.ID = y.ID

SQL FIDDLE

3
GarethD