Gibt es in PostgreSQL eine typsichere first () - Aggregatfunktion?


21

Vollständige Frage neu schreiben

Ich suche nach einer First () - Aggregatfunktion.

Hier habe ich etwas gefunden, das fast funktioniert:

CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

Das Problem ist, dass eine varchar (n) -Spalte, die die first () -Funktion durchläuft, in eine einfache varchar (ohne Größe) konvertiert wird. Beim Versuch, die Abfrage in einer Funktion als RETURNS SETOF anyelement zurückzugeben, wird die folgende Fehlermeldung angezeigt:

FEHLER: Die Struktur der Abfrage stimmt nicht mit dem Ergebnistyp der Funktion überein. SQL-Status: 42804 Detalhe: Die zurückgegebene Zeichenvariation stimmt nicht mit der erwarteten Zeichenvariation (40) in Spalte 2 überein. Kontext: PL / pgSQL-Funktion vsr_table_at_time (beliebiges Element, Zeitstempel ohne Zeitzone ) Zeile 31 bei RETURN QUERY

Auf derselben Wiki-Seite befindet sich ein Link zu einer C-Version der Funktion , die die oben genannten ersetzen würde. Ich weiß nicht, wie ich es installieren soll, aber ich frage mich, ob diese Version mein Problem lösen könnte.

Gibt es eine Möglichkeit, die obige Funktion so zu ändern, dass genau derselbe Typ der Eingabespalte zurückgegeben wird?

Antworten:


17

DISTINCT ON()

Nur als Randnotiz, das ist genau das, was DISTINCT ON()(nicht zu verwechseln mit DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) behält nur die erste Zeile jeder Reihe von Zeilen bei, in denen die angegebenen Ausdrücke als gleich ausgewertet werden . Die DISTINCT ONAusdrücke werden nach den gleichen Regeln wie für ORDER BY(so) interpretiert . Beachten Sie, dass die "erste Zeile" jedes Satzes nicht vorhersehbar ist, es ORDER BYsei denn , Sie stellen sicher, dass die gewünschte Zeile zuerst angezeigt wird. Beispielsweise

Also, wenn Sie schreiben würden,

SELECT myFirstAgg(z)
FROM foo
GROUP BY x,y;

Es ist effektiv

SELECT DISTINCT ON(x,y) z
FROM foo;
-- ORDER BY z;

Darin braucht es den ersten z. Es gibt zwei wichtige Unterschiede:

  1. Sie können auch andere Spalten auswählen, ohne dass eine weitere Aggregation erforderlich ist.

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. Weil es keine GROUP BYgibt, können Sie keine (echten) Aggregate damit verwenden.

    CREATE TABLE foo AS
    SELECT * FROM ( VALUES
      (1,2,3),
      (1,2,4),
      (1,2,5)
    ) AS t(x,y,z);
    
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- fails, as you should expect.
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- would not otherwise fail.
    SELECT myFirstAgg(z), sum(z)
    FROM foo
    GROUP BY x,y;

Nicht vergessen ORDER BY

Auch wenn ich es damals nicht gewagt habe, werde ich es jetzt tun

Beachten Sie, dass die "erste Zeile" jedes Satzes nicht vorhersehbar ist, es sei denn, ORDER BY wird verwendet, um sicherzustellen, dass die gewünschte Zeile zuerst angezeigt wird. Beispielsweise

Verwenden Sie immer ein ORDER BYmitDISTINCT ON

Verwenden einer Bestellmengen-Aggregatfunktion

Ich stelle mir vor, dass viele Leute suchen first_value, Bestellte-Set Aggregatfunktionen . Ich wollte das nur rausschmeißen. Es würde so aussehen, wenn die Funktion existiert:

SELECT a, b, first_value() WITHIN GROUP (ORDER BY z)    
FROM foo
GROUP BY a,b;

Aber leider können Sie dies tun.

SELECT a, b, percentile_disc(0) WITHIN GROUP (ORDER BY z)   
FROM foo
GROUP BY a,b;

1
Das Problem mit dieser Antwort ist, dass es nur funktioniert, wenn Sie EIN Aggregat in Ihrer Auswahlliste haben möchten, was von der Frage nicht impliziert wird. Wenn Sie beispielsweise aus einer Tabelle auswählen und mehrere geordnete erste Werte finden möchten, DISTINCT ONfunktioniert dies in diesem Fall nicht. Es ist keine Aggregatfunktion, Sie filtern die Daten tatsächlich und können sie nur einmal ausführen.
DB140141

6

Ja, ich habe einen einfachen Weg mit Ihrem Fall gefunden, indem ich einige Funktionen in PostgreSQL 9.4+ verwendet habe

Sehen wir uns dieses Beispiel an:

select  (array_agg(val ORDER BY i))[1] as first_value_orderby_i,
    (array_agg(val ORDER BY i DESC))[1] as last_value_orderby_i,
    (array_agg(val))[1] as last_value_all,
    (array_agg(val))[array_length(array_agg(val),1)] as last_value_all
   FROM (
        SELECT i, random() as val
        FROM generate_series(1,100) s(i)
        ORDER BY random()
    ) tmp_tbl

Ich hoffe, es wird Ihnen in Ihrem Fall helfen.


Das Problem bei dieser Lösung ist, dass sie nicht mit DOMAINDatentypen oder anderen kleinen Ausnahmen funktioniert . Es ist auch viel komplexer und zeitaufwendiger, ein Array des gesamten Datensatzes aufzubauen. Die einfache Lösung wäre, ein benutzerdefiniertes Aggregat zu erstellen, aber bisher habe ich selbst damit nicht die ideale Lösung gefunden. Fensterfunktionen sind auch schlecht, da sie nicht so verwendet werden können, wie Sie Aggregate verwenden könnten (mit FILTER-Anweisungen oder in CROSS JOIN LATERAL)
AlexanderMP

5

Keine direkte Antwort auf Ihre Frage, aber Sie sollten die first_valueFensterfunktion ausprobieren . Das funktioniert so:

CREATE TABLE test (
    id SERIAL NOT NULL PRIMARY KEY,
    cat TEXT,
    value VARCHAR(2)
    date TIMESTAMP WITH TIME ZONE

);

Wenn Sie dann das erste Element in jeder cat(Kategorie) möchten, fragen Sie folgendermaßen ab:

SELECT
    cat,
    first_value(date) OVER (PARTITION BY cat ORDER BY date)
FROM
    test;

oder:

SELECT
    cat,
    first_value(date) OVER w
FROM
    test
WINDOW w AS (PARTITION BY cat ORDER BY date);

Entschuldigung, ich glaube nicht, dass dies auf meinen Anwendungsfall zutrifft. First_value ist keine Aggregationsfunktion, die alle Datensätze von mit einem bestimmten gemeinsamen Wert (Ihrer Beispielkatze) anzeigt, die nach einer bestimmten Reihenfolge (Ihrem Beispieldatum) als erste ausgewertet werden. Mein Bedürfnis ist anders. Ich muss in der gleichen Auswahl mehrere Spalten zusammenfassen, indem ich den ersten Wert ungleich Null wähle. Das heißt, es sollte für jede der Wertekombinationen in GROUP BY ein einzelner Datensatz ausgegeben werden.
Alexandre Neto

2
Das Obige kann durch das Werfen verschieden in den Mix Arbeit gemacht werden: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Wahrscheinlich ineffizient, aber genug, um mit dem Prototyping fortzufahren. Auf jeden Fall etwas zu überdenken!
Max Murphy
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.