come escludere valori nulli in array_agg come in string_agg usando postgres?


96

Se uso array_aggper raccogliere i nomi, ottengo i miei nomi separati da virgole, ma nel caso in cui ci sia un nullvalore, anche quel null viene preso come nome nell'aggregato. Per esempio :

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;

ritorna ,Larry,Philinvece di solo Larry,Phil(nel mio 9.1.2, mostra NULL,Larry,Phil). come in questo violino

Se invece lo uso string_agg(), mi mostra solo i nomi (senza virgole vuote o null) come qui

Il problema è che l'ho Postgres 8.4installato sul server e string_agg()non funziona lì. C'è un modo per far funzionare array_agg in modo simile a string_agg ()?


Vedi questo thread della mailing list PostgreSQL su questo argomento: postgresql.1045698.n5.nabble.com/…
Craig Ringer

Mi dispiace, non credo che ci sia una soluzione in quel thread ..
Daud

Ci sono due soluzioni in quel thread. Uno è creare una funzione e l'altro (appena suggerito non mostrato) è quello a cui ho risposto.
Clodoaldo Neto

@Clodoaldo - tutte le righe avranno canonical in ('y', 'n') ... quindi la clausola where sembra essere ridondante. Il problema è che all'interno di un raggruppamento, se il valore del campo canonico è "Y", e stiamo raccogliendo "N", anche un valore nullo deve essere raccolto ..
Daud

Ok. Ora ho capito. Controlla la risposta dell'aggiornamento.
Clodoaldo Neto

Risposte:


28

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

Oppure, più semplice e potrebbe essere più economico, utilizzando array_to_stringche elimina i valori nulli:

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


Grazie. Ma se le query principali restituiscono 1000 righe, le 2 sottoquery (utilizzando unest) verranno eseguite una volta per ogni riga. Sarà meglio tollerare i NULL piuttosto che eseguire 2000 query di selezione extra?
Daud

@Daud Nuova versione che potrebbe essere più economica. Prendi la spiegazione dell'output di entrambi per essere sicuro.
Clodoaldo Neto

3
@Clodoaldo Se stai usando array_to_string(array_agg(...))potresti anche usare string_agg.
Craig Ringer

1
@Craig Il problema in questione è 8.4
Clodoaldo Neto

@Clodoaldo Gah, vecchie versioni. Grazie.
Craig Ringer

247

Con postgresql-9.3 è possibile farlo;

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;

Aggiornamento : con 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;

5
Funziona ed è veloce ed elegante, mi ha risolto un problema simile agli OP. Un motivo per passare alla 9.3 per coloro che non l'hanno ancora fatto. +1
Pavel V.

12
Il 9.4 è ancora più elegante. Funziona a
meraviglia

2
La variante 9.4 è ancora migliore, perché quello che devo filtrare nel mio caso sono i null.
coladict

Ho usato prima la versione aggiornata, ma poi ho capito che dovevo rimuovere Null e duplicati, quindi sono tornato al primo suggerimento. È una query di grandi dimensioni, ma serve a creare una vista materializzata, quindi non è un grosso problema.
Relequestual

12

Nel risolvere la questione generale della rimozione di null dagli aggregati di array, ci sono due modi principali per affrontare il problema: eseguire array_agg (unnest (array_agg (x)) o creare un aggregato personalizzato.

Il primo è della forma mostrata sopra :

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

Il secondo:

/*
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 = '{}'
);

Chiamare il secondo è (naturalmente) un po 'più bello del primo:

seleziona array_agg_notnull (v) da x;


9

Sto aggiungendo questo anche se questo thread è piuttosto vecchio, ma mi sono imbattuto in questo trucco che funziona abbastanza bene su piccoli array. Funziona su Postgres 8.4+ senza librerie o funzioni aggiuntive.

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

Il array_to_string()metodo effettivamente elimina i valori nulli.


8

Se stai cercando una risposta moderna alla domanda generale su come rimuovere un NULL da un array , è:

array_remove(your_array, NULL)

Ero particolarmente curioso delle prestazioni e volevo confrontarlo con la migliore alternativa possibile:

CREATE OR REPLACE FUNCTION strip_nulls(
    IN array_in ANYARRAY
)
RETURNS anyarray AS
'
SELECT
    array_agg(a)
FROM unnest(array_in) a
WHERE
    a IS NOT NULL
;
'
LANGUAGE sql
;

Fare un test pgbench ha dimostrato (con grande sicurezza) che array_remove () è poco più del doppio più veloce . Ho fatto il mio test su numeri a doppia precisione con una varietà di dimensioni di array (10, 100 e 1000 elementi) e NULL casuali in mezzo.


@VivekSinha quale versione di postgres stai usando? Ho appena testato la tua query e per me è risultato "{1,2,3}". Sto usando 12.1.
Alexi Theodore

Ah, vedo @ alexi-theodore cosa sta succedendo alla mia fine. Stavo usando un driver postgres personalizzato + modificato. Quando eseguo una query direttamente nella console, posso vedere l'output giusto! Scusa per la confusione. Commento precedente eliminato e risposta votata positivamente!
Vivek Sinha

3

Come è stato suggerito nei commenti è possibile scrivere una funzione per sostituire i null in un array, tuttavia come sottolineato anche nel thread collegato nei commenti, questo tipo di vanifica l'efficienza della funzione aggregata se devi creare un aggregato , dividerlo e quindi aggregarlo di nuovo.

Penso che mantenere i valori nulli nell'array sia solo una caratteristica (forse indesiderata) di Array_Agg. Potresti usare sottoquery per evitare questo:

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


Grazie. Ma avevo bisogno di "case" per gestire le righe all'interno di un dato raggruppamento e le sottoquery sarebbero inefficienti lì
Daud

0

È molto semplice, prima di tutto crea un nuovo operatore - (meno) per il testo [] :

CREATE OR REPLACE FUNCTION diff_elements_text
    (
        text[], text[] 
    )
RETURNS text[] as 
$$
    SELECT array_agg(DISTINCT new_arr.elem)
    FROM
        unnest($1) as new_arr(elem)
        LEFT OUTER JOIN
        unnest($2) as old_arr(elem)
        ON new_arr.elem = old_arr.elem
    WHERE old_arr.elem IS NULL
$$ LANGUAGE SQL IMMUTABLE;

CREATE OPERATOR - (
    PROCEDURE = diff_elements_text,
    leftarg = text[],
    rightarg = text[]
);

E sottrai semplicemente l'array [null]:

select 
    array_agg(x)-array['']
from
    (   select 'Y' x union all
        select null union all
        select 'N' union all
        select '' 
    ) x;

È tutto:

{Y, N}


array_agg(x) FILTER (WHERE x is not null)sembra molto più semplice: dbfiddle.uk/… e non hai davvero bisogno della tua funzione, puoi semplicemente usare array_remove() dbfiddle.uk/…
a_horse_with_no_name

-6

Una domanda più grande è perché tirare tutte le combinazioni utente / gruppo contemporaneamente. Garantito che la tua interfaccia utente non possa gestire tutti quei dati. Anche l'aggiunta di paging a dati di grandi dimensioni è una cattiva idea. Chiedi ai tuoi utenti di filtrare il set prima di visualizzare i dati. Assicurati che il tuo set di opzioni JOIN sia nell'elenco in modo che possano filtrare in base alle prestazioni, se lo desiderano. A volte 2 query rendono gli utenti più felici se sono entrambe veloci.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.