PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

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

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)
  LANGUAGE plpgsql 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$;

Chiama:

SELECT * FROM word_frequency(123);

La definizione esplicita del tipo restituito è molto più pratico della restituzione di un record generico . In questo modo non è necessario fornire un elenco di definizioni di colonna con ogni chiamata di funzione. RETURNS TABLE è un modo per farlo. Ce ne sono altri. Tipi di dati di OUT i parametri devono corrispondere esattamente a ciò che viene restituito dalla query.

Scegli i nomi per OUT parametri con attenzione. 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.

Ma nota il potenziale conflitto di denominazione tra il OUT parametro cnt e l'alias di colonna con lo stesso nome. In questo caso particolare (RETURN QUERY SELECT ... ) Postgres usa l'alias di colonna sopra OUT parametro in entrambi i casi. Questo può essere ambiguo in altri contesti, però. Esistono vari modi per evitare qualsiasi confusione:

  1. Utilizzare la posizione ordinale dell'elemento nell'elenco SELECT:ORDER BY 2 DESC . Esempio:
    • Seleziona la prima riga in ogni gruppo GROUP BY?
  2. Ripetere l'espressione ORDER BY count(*) .
  3. (Non applicabile qui.) Imposta il parametro di configurazione plpgsql.variable_conflict oppure usa il comando speciale #variable_conflict error | use_variable | use_column nella funzione. Vedi:
    • Conflitto di denominazione tra parametro di funzione e risultato di JOIN con clausola USING

Non utilizzare "testo" o "conta" come nomi di colonna. Entrambi sono legali da usare in Postgres, ma "count" è una parola riservata in SQL standard e un nome di funzione di base e "testo" è un tipo di dati di base. Può portare a errori di confusione. Uso txt e cnt nei miei esempi, potresti volere nomi più espliciti.

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

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

Alternativa

Questo è ciò che penso la tua query dovrebbe effettivamente assomigliare a (calcolo di una quota relativa per token ):

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt            text
               , abs_cnt        bigint
               , relative_share numeric)
  LANGUAGE plpgsql 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$;

L'espressione sum(t.cnt) OVER () è una funzione della finestra. Potresti utilizzare un CTE invece della sottoquery. Carina, ma una sottoquery è in genere più economica in casi semplici come questo (principalmente prima di Postgres 12).

Un esplicito RETURN finale dichiarazione è non richiesto (ma consentito) quando si lavora con OUT parametri o RETURNS TABLE (che fa uso implicito di OUT parametri).

round() con due parametri funziona solo per numeric tipi. count() nella sottoquery produce un bigint risultato e un sum() su questo bigint produce un numeric risultato, quindi abbiamo a che fare con un numeric numero automaticamente e tutto va a posto.