Wie kann das Ergebnis eines SELECT innerhalb einer Funktion in PostgreSQL zurückgegeben werden?

106

Ich habe diese Funktion in PostgreSQL, aber ich weiß nicht, wie ich das Ergebnis der Abfrage zurückgeben soll:

CREATE OR REPLACE FUNCTION wordFrequency(maxTokens INTEGER)
  RETURNS SETOF RECORD AS
$$
BEGIN
    SELECT text, count(*), 100 / maxTokens * count(*)
    FROM (
        SELECT text
    FROM token
    WHERE chartype = 'ALPHABETIC'
    LIMIT maxTokens
    ) as tokens
    GROUP BY text
    ORDER BY count DESC
END
$$
LANGUAGE plpgsql;

Ich weiß jedoch nicht, wie ich das Ergebnis der Abfrage innerhalb der PostgreSQL-Funktion zurückgeben soll.

Ich fand, dass der Rückgabetyp sein sollte SETOF RECORD, richtig? Der Rückgabebefehl ist jedoch nicht richtig.

Was ist der richtige Weg, um dies zu tun?

Renato Dinhani
quelle
Warum zählst du sie? Haben Sie doppelte Token in Ihrer Token-Tabelle? Außerdem: Fügen Sie Ihrer Frage die Tabellendefinition hinzu.
Wildplasser
1
Ist das deine gesamte Funktion? Wenn Sie keine anderen Anweisungen in der Funktion haben, sollten Sie es einfach machen LANGUAGE SQL.
jpmc26

Antworten:

134

Verwendung RETURN QUERY:

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt   text   -- also visible as OUT parameter inside function
               , cnt   bigint
               , ratio bigint) AS
$func$
BEGIN
   RETURN QUERY
   SELECT t.txt
        , count(*) AS cnt                 -- column alias only visible inside
        , (count(*) * 100) / _max_tokens  -- I added brackets
   FROM  (
      SELECT t.txt
      FROM   token t
      WHERE  t.chartype = 'ALPHABETIC'
      LIMIT  _max_tokens
      ) t
   GROUP  BY t.txt
   ORDER  BY cnt DESC;                    -- potential ambiguity 
END
$func$  LANGUAGE plpgsql;

Anruf:

SELECT * FROM word_frequency(123);

Erläuterung:

  • Es ist viel praktischer, den Rückgabetyp explizit zu definieren, als ihn einfach als Datensatz zu deklarieren. Auf diese Weise müssen Sie nicht bei jedem Funktionsaufruf eine Spaltendefinitionsliste bereitstellen. RETURNS TABLEist eine Möglichkeit, das zu tun. Da sind andere. Datentypen von OUTParametern müssen genau mit dem übereinstimmen, was von der Abfrage zurückgegeben wird.

  • Wählen Sie die Namen der OUTParameter sorgfältig aus. Sie sind im Funktionskörper fast überall sichtbar. Gleichnamige Spalten mit Tabellenqualifizierung, um Konflikte oder unerwartete Ergebnisse zu vermeiden. Ich habe das für alle Spalten in meinem Beispiel gemacht.

    Beachten Sie jedoch den möglichen Namenskonflikt zwischen dem OUTParameter cntund dem gleichnamigen Spaltenalias. In diesem speziellen Fall ( RETURN QUERY SELECT ...) verwendet Postgres den Spaltenalias über den OUTParameter. Dies kann jedoch in anderen Kontexten nicht eindeutig sein. Es gibt verschiedene Möglichkeiten, um Verwirrung zu vermeiden:

    1. Verwenden Sie die Ordnungsposition des Elements in der SELECT-Liste : ORDER BY 2 DESC. Beispiel:
    2. Wiederholen Sie den Ausdruck ORDER BY count(*).
    3. (Gilt hier nicht.) Legen Sie den Konfigurationsparameter fest plpgsql.variable_conflictoder verwenden Sie den Sonderbefehl #variable_conflict error | use_variable | use_columnin der Funktion. Sehen:
  • Verwenden Sie nicht "Text" oder "Anzahl" als Spaltennamen. Beide sind in Postgres legal zu verwenden, aber "count" ist ein reserviertes Wort in Standard-SQL und ein grundlegender Funktionsname und "text" ist ein grundlegender Datentyp. Kann zu verwirrenden Fehlern führen. Ich benutze txtund cntin meinen Beispielen.

  • Ein fehlender ;und korrigierter Syntaxfehler im Header wurde hinzugefügt . (_max_tokens int), Nicht (int maxTokens)- geben Sie nach dem Namen .

  • Bei der Arbeit mit der Ganzzahldivision ist es besser, zuerst zu multiplizieren und später zu dividieren, um den Rundungsfehler zu minimieren. Noch besser: Arbeiten Sie mit numeric(oder einem Gleitkomma-Typ). Siehe unten.

Alternative

Ich denke, Ihre Abfrage sollte tatsächlich so aussehen (Berechnung eines relativen Anteils pro Token ):

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt            text
               , abs_cnt        bigint
               , relative_share numeric) AS
$func$
BEGIN
   RETURN QUERY
   SELECT t.txt, t.cnt
        , round((t.cnt * 100) / (sum(t.cnt) OVER ()), 2)  -- AS relative_share
   FROM  (
      SELECT t.txt, count(*) AS cnt
      FROM   token t
      WHERE  t.chartype = 'ALPHABETIC'
      GROUP  BY t.txt
      ORDER  BY cnt DESC
      LIMIT  _max_tokens
      ) t
   ORDER  BY t.cnt DESC;
END
$func$  LANGUAGE plpgsql;

Der Ausdruck sum(t.cnt) OVER ()ist eine Fensterfunktion . Sie könnten einen CTE anstelle der Unterabfrage verwenden - hübsch, aber eine Unterabfrage ist in einfachen Fällen wie diesem normalerweise billiger.

Eine abschließende explizite RETURNAnweisung ist nicht erforderlich (aber zulässig), wenn mit OUTParametern gearbeitet wird oder RETURNS TABLE( wobei Parameter implizit verwendet OUTwerden).

round()mit zwei Parametern funktioniert nur für numericTypen. count()in der Unterabfrage ergibt sich ein bigintErgebnis und ein sum()Over biginterzeugt ein numericErgebnis, daher behandeln wir numericautomatisch eine Zahl und alles passt einfach zusammen.

Erwin Brandstetter
quelle
Vielen Dank für Ihre Antwort und Korrekturen. Funktioniert jetzt einwandfrei (ich habe nur den Verhältnis-Typ in numerisch geändert).
Renato Dinhani
@ RenatoDinhaniConceição Cool! Ich habe eine Version hinzugefügt, die möglicherweise eine zusätzliche Frage beantwortet, die Sie nicht gestellt haben. ;)
Erwin Brandstetter
Schön, das einzige, was ich denke, ist, dass du RETURN;vorher eine brauchst END;, zumindest habe ich das getan - aber ich mache eine UNION, also bin ich mir nicht sicher, ob das den Unterschied macht.
Yekta
@yekta: Ich habe einige Informationen zur Rolle von hinzugefügt RETURN. Ein nicht verwandter Fehler wurde behoben und einige Verbesserungen hinzugefügt, während Sie dabei waren.
Erwin Brandstetter
1
Was ist der Weg, um dies zu tun, wenn Sie nicht einschränken möchten, was in Return TABLE () enthalten ist? IE RÜCKGABETABELLE (*)?
Nick
1

Hallo, bitte überprüfen Sie den folgenden Link

https://www.postgresql.org/docs/current/xfunc-sql.html

EX:

CREATE FUNCTION sum_n_product_with_tab (x int)
RETURNS TABLE(sum int, product int) AS $$
    SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;
Moumita Das
quelle