Wie kann man Nullwerte in array_agg wie in string_agg mit postgres ausschließen?

95

Wenn ich array_aggNamen sammle, werden meine Namen durch Kommas getrennt. Wenn jedoch ein nullWert vorhanden ist , wird diese Null auch als Name im Aggregat verwendet. Zum Beispiel :

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;

es kehrt ,Larry,Philstatt nur zurück Larry,Phil(in meinem 9.1.2 zeigt es NULL,Larry,Phil). wie in dieser Geige

Wenn ich stattdessen verwende string_agg(), werden mir nur die Namen (ohne leere Kommas oder Nullen) wie hier angezeigt

Das Problem ist, dass ich Postgres 8.4auf dem Server installiert habe und string_agg()dort nicht funktioniert. Gibt es eine Möglichkeit, array_agg ähnlich wie string_agg () arbeiten zu lassen?

Daud
quelle
Siehe diesen PostgreSQL-Mailinglisten-Thread zu vielen Themen: postgresql.1045698.n5.nabble.com/…
Craig Ringer
Es tut mir leid, ich glaube nicht, dass es eine Lösung in diesem Thread gibt ..
Daud
In diesem Thread gibt es zwei Lösungen. Eine ist das Erstellen einer Funktion und die andere (nur vorgeschlagen, nicht gezeigt) ist die, die ich beantwortet habe.
Clodoaldo Neto
@Clodoaldo - alle Zeilen haben kanonisch in ('y', 'n') ... also scheint die where-Klausel redundant zu sein. Das Problem ist, dass innerhalb einer Gruppierung, wenn der Wert des kanonischen Feldes 'Y' ist und wir 'N' sammeln, auch eine Null gesammelt wird.
Daud
OK. Jetzt hab ich es verstanden. Überprüfen Sie die Update-Antwort.
Clodoaldo Neto

Antworten:

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

Oder einfacher und möglicherweise billiger, array_to_stringwodurch Nullen beseitigt werden:

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

Clodoaldo Neto
quelle
Vielen Dank. Wenn die Hauptabfrage (n) 1000 Zeilen zurückgeben, werden die 2 Unterabfragen (mit unnest) einmal für jede Zeile ausgeführt. Ist es besser, NULL-Werte zu tolerieren, als 2000 zusätzliche Auswahlabfragen auszuführen?
Daud
@Daud Neue Version, die billiger sein könnte. Nehmen Sie die EXPLAIN-Ausgabe von beiden, um sicherzugehen.
Clodoaldo Neto
3
@Clodoaldo Wenn Sie verwenden, können array_to_string(array_agg(...))Sie auch verwenden string_agg.
Craig Ringer
1
@Craig Das Problem in der Frage ist 8.4
Clodoaldo Neto
@ Clodoaldo Gah, alte Versionen. Vielen Dank.
Craig Ringer
244

Mit postgresql-9.3 kann man das machen;

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;

Update : mit 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;
Dale O'Brien
quelle
5
Dies funktioniert und ist schnell und elegant, es hat mich ein Problem gelöst, das den OPs ähnelt. Ein Grund für ein Upgrade auf 9.3 für diejenigen, die es noch nicht getan haben. +1
Pavel V.
12
Der 9.4 ist noch eleganter. Funktioniert wie ein Zauber
jmgarnier
2
Die 9.4-Variante ist sogar noch besser, weil ich in meinem Fall die Nullen herausfiltern muss.
Coladict
Ich habe zuerst die aktualisierte Version verwendet, dann aber festgestellt, dass ich Nullen und Duplikate entfernen muss, und bin zum ersten Vorschlag zurückgekehrt. Es ist eine große Abfrage, aber es geht darum, eine materialisierte Ansicht zu erstellen, also kein großes Problem.
Relequestual
12

Bei der Lösung der allgemeinen Frage des Entfernens von Nullen aus Array-Aggregaten gibt es zwei Hauptmethoden, um das Problem anzugreifen: entweder array_agg (unnest (array_agg (x)) oder das Erstellen eines benutzerdefinierten Aggregats.

Die erste hat die oben gezeigte Form :

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

Der Zweite:

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

Das zweite anzurufen ist (natürlich) etwas schöner als das erste:

wähle array_agg_notnull (v) aus x;

rorycl
quelle
9

Ich füge dies hinzu, obwohl dieser Thread ziemlich alt ist, aber ich bin auf diesen netten Trick gestoßen, der auf kleinen Arrays ziemlich gut funktioniert. Es läuft auf Postgres 8.4+ ohne zusätzliche Bibliotheken oder Funktionen.

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

Die array_to_string()Methode entfernt tatsächlich die Nullen.

ced-b
quelle
8

Wenn Sie nach einer modernen Antwort auf die allgemeine Frage suchen, wie ein NULL aus einem Array entfernt werden kann , lautet dies:

array_remove(your_array, NULL)

Ich war besonders neugierig auf Leistung und wollte dies mit der bestmöglichen Alternative vergleichen:

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
;

Ein pgbench-Test hat (mit hoher Sicherheit) bewiesen, dass array_remove () etwas mehr als doppelt so schnell ist . Ich habe meinen Test mit Zahlen mit doppelter Genauigkeit mit einer Vielzahl von Arraygrößen (10, 100 und 1000 Elemente) und zufälligen NULL-Werten dazwischen durchgeführt.

Alexi Theodore
quelle
@VivekSinha Welche Version von Postgres verwenden Sie? Ich habe gerade Ihre Anfrage getestet und sie ergab für mich "{1,2,3}". Ich benutze 12.1.
Alexi Theodore
Ah, ich sehe @ alexi-theodore, was an meinem Ende passiert. Ich habe einen benutzerdefinierten + modifizierten Postgres-Treiber verwendet. Wenn ich direkt in der Konsole abfrage, sehe ich die richtige Ausgabe! Entschuldigung für die Verwirrung. Vorheriger Kommentar und optimierte Antwort gelöscht!
Vivek Sinha
3

Wie in den Kommentaren vorgeschlagen, können Sie eine Funktion schreiben, um Nullen in einem Array zu ersetzen. Wie jedoch auch in dem in den Kommentaren verknüpften Thread ausgeführt, beeinträchtigt diese Art die Effizienz der Aggregatfunktion, wenn Sie ein Aggregat erstellen müssen , teilen Sie es und aggregieren Sie es erneut.

Ich denke, Nullen im Array zu behalten ist nur eine (möglicherweise unerwünschte) Funktion von Array_Agg. Sie können Unterabfragen verwenden, um dies zu vermeiden:

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

GarethD
quelle
Vielen Dank. Aber ich brauchte 'case', um Zeilen innerhalb einer bestimmten Gruppierung zu behandeln, und Unterabfragen wären dort ineffizient
Daud
0

Es ist sehr einfach, erstellen Sie zunächst einen neuen - (Minus-) Operator für Text [] :

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[]
);

Und subtrahieren Sie einfach das Array [null]:

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

Das ist alles:

{J, N}

Miklos
quelle
array_agg(x) FILTER (WHERE x is not null)scheint viel einfacher zu sein: dbfiddle.uk/… und Sie brauchen nicht wirklich Ihre eigene Funktion, Sie können einfach array_remove() dbfiddle.uk/… verwenden
a_horse_with_no_name
-6

Eine größere Frage ist, warum alle Benutzer- / Gruppenkombinationen gleichzeitig gezogen werden. Garantiert kann Ihre Benutzeroberfläche nicht alle diese Daten verarbeiten. Das Hinzufügen von Paging zu übergroßen Daten ist ebenfalls eine schlechte Idee. Lassen Sie Ihre Benutzer den Satz filtern, bevor sie Daten sehen. Stellen Sie sicher, dass Ihr JOIN-Optionssatz in der Liste enthalten ist, damit sie nach Leistung filtern können, wenn sie möchten. Manchmal machen 2 Abfragen Benutzer glücklicher, wenn beide schnell sind.

Michael
quelle