Come restituire il risultato di una SELECT all'interno di una funzione in PostgreSQL?


106

Ho questa funzione in PostgreSQL, ma non so come restituire il risultato della query:

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;

Ma non so come restituire il risultato della query all'interno della funzione PostgreSQL.

Ho scoperto che il tipo di restituzione dovrebbe essere SETOF RECORD, giusto? Ma il comando di ritorno non è corretto.

Qual è il modo giusto per farlo?


Perché li conti; hai gettoni duplicati nella tua TABELLA dei gettoni? Inoltre: aggiungi la definizione della tabella alla tua domanda.
wildplasser

1
È questa la tua intera funzione? Se non hai altre istruzioni nella funzione, dovresti semplicemente farlo LANGUAGE SQL.
jpmc26

Risposte:


135

Usa 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;

Chiamata:

SELECT * FROM word_frequency(123);

Spiegazione:

  • È molto più pratico definire in modo esplicito il tipo restituito piuttosto che dichiararlo semplicemente come record. In questo modo non è necessario fornire un elenco di definizioni di colonne con ogni chiamata di funzione. RETURNS TABLEè un modo per farlo. Ce ne sono altri. I tipi di dati dei OUTparametri devono corrispondere esattamente a ciò che viene restituito dalla query.

  • Scegli OUTattentamente i nomi per i parametri. Sono visibili nel corpo della funzione quasi ovunque. Qualifica le colonne con lo stesso nome per evitare conflitti o risultati imprevisti. L'ho fatto per tutte le colonne nel mio esempio.

    Si noti tuttavia il potenziale conflitto di denominazione tra il OUTparametro cnte l'alias di colonna con lo stesso nome. In questo caso particolare ( RETURN QUERY SELECT ...) Postgres utilizza l'alias di colonna sul OUTparametro in entrambi i casi. Tuttavia, questo può essere ambiguo in altri contesti. Esistono vari modi per evitare qualsiasi confusione:

    1. Utilizzare la posizione ordinale del elemento nell'elenco SELECT: ORDER BY 2 DESC. Esempio:
    2. Ripeti l'espressione ORDER BY count(*).
    3. (Non applicabile qui.) Impostare il parametro di configurazione plpgsql.variable_conflicto utilizzare il comando speciale #variable_conflict error | use_variable | use_columnnella funzione. Vedere:
  • Non utilizzare "testo" o "conteggio" come nomi di colonna. Entrambi sono legali da usare in Postgres, ma "count" è una parola riservata nello standard SQL e un nome di funzione di base e "text" è un tipo di dati di base. Può portare a errori confusi. Uso txte cntnei miei esempi.

  • Aggiunto un ;errore di sintassi mancante e corretto nell'intestazione. (_max_tokens int), non (int maxTokens)- digita dopo il nome .

  • Mentre si lavora con la divisione intera, è meglio moltiplicare prima e dividere in seguito, per ridurre al minimo l'errore di arrotondamento. Ancora meglio: lavora con numeric(o un tipo a virgola mobile). Vedi sotto.

Alternativa

Questo è come penso che la tua query dovrebbe effettivamente apparire (calcolando una quota relativa per 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;

L'espressione sum(t.cnt) OVER ()è una funzione finestra . Si potrebbe utilizzare un CTE al posto del subquery - bella, ma una subquery è in genere più conveniente nei casi più semplici come questo.

Una dichiarazione finale esplicita nonRETURN è richiesta (ma consentita) quando si lavora con i OUTparametri o RETURNS TABLE(il che fa un uso implicito dei OUTparametri).

round()con due parametri funziona solo per i numerictipi. count()nella sottoquery produce un bigintrisultato e un sum()over questo bigintproduce un numericrisultato, quindi trattiamo numericautomaticamente un numero e tutto va a posto.


Grazie mille per la tua risposta e correzioni. Ora funziona bene (ho cambiato solo il tipo di rapporto in numerico).
Renato Dinhani

@ RenatoDinhaniConceição Cool! Ho aggiunto una versione che potrebbe o meno rispondere a un'ulteriore domanda che non hai effettivamente posto. ;)
Erwin Brandstetter

Bello, l'unica cosa è che penso che tu abbia bisogno di un RETURN;prima END;, almeno io l'ho fatto - ma sto facendo UNION quindi non sono sicuro se questo lo renda diverso.
yekta

@yekta: ho aggiunto alcune informazioni riguardanti il ​​ruolo di RETURN. Risolto un errore non correlato e aggiunto alcuni miglioramenti mentre ci si trovava.
Erwin Brandstetter

1
Qual è il modo per farlo quando non vuoi vincolare ciò che è in Return TABLE (). IE TABELLA DEI RESI (*)?
Nick

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.