Como retornar o resultado de um SELECT dentro de uma função no PostgreSQL?

106

Tenho essa função no PostgreSQL, mas não sei como retornar o resultado da consulta:

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;

Mas não sei como retornar o resultado da consulta dentro da função PostgreSQL.

Achei que o tipo de retorno deveria ser SETOF RECORD, certo? Mas o comando de retorno não está certo.

Qual é a maneira certa de fazer isso?

Renato Dinhani
fonte
Por que você os conta; você tem tokens duplicados em sua TABELA de tokens? Além disso: adicione a definição da tabela à sua pergunta.
wildplasser
1
Esta é toda a sua função? Se você não tiver nenhuma outra instrução na função, você deve apenas fazê-la LANGUAGE SQL.
jpmc26

Respostas:

134

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

Ligar:

SELECT * FROM word_frequency(123);

Explicação:

  • É muito mais prático definir explicitamente o tipo de retorno do que simplesmente declará-lo como registro. Dessa forma, você não precisa fornecer uma lista de definição de coluna com cada chamada de função. RETURNS TABLEé uma maneira de fazer isso. Há outros. Os tipos de dados dos OUTparâmetros devem corresponder exatamente ao que é retornado pela consulta.

  • Escolha os nomes dos OUTparâmetros com cuidado. Eles são visíveis no corpo da função em quase qualquer lugar. Colunas de qualificação de tabela com o mesmo nome para evitar conflitos ou resultados inesperados. Fiz isso para todas as colunas do meu exemplo.

    Mas observe o possível conflito de nomenclatura entre o OUTparâmetro cnte o alias da coluna de mesmo nome. Neste caso particular ( RETURN QUERY SELECT ...) Postgres usa o alias da coluna sobre o OUTparâmetro de qualquer maneira. Isso pode ser ambíguo em outros contextos, no entanto. Existem várias maneiras de evitar qualquer confusão:

    1. Use a posição ordinal do item na lista SELECT: ORDER BY 2 DESC. Exemplo:
    2. Repita a expressão ORDER BY count(*).
    3. (Não aplicável aqui.) Defina o parâmetro de configuração plpgsql.variable_conflictou use o comando especial #variable_conflict error | use_variable | use_columnna função. Vejo:
  • Não use "texto" ou "contagem" como nomes de coluna. Ambos são permitidos no Postgres, mas "contagem" é uma palavra reservada no SQL padrão e um nome de função básica e "texto" é um tipo de dados básico. Pode levar a erros confusos. Eu uso txte cntem meus exemplos.

  • Foi adicionado um ;erro de sintaxe ausente e corrigido no cabeçalho. (_max_tokens int), não (int maxTokens)- digite após o nome .

  • Ao trabalhar com divisão inteira, é melhor multiplicar primeiro e dividir depois, para minimizar o erro de arredondamento. Ainda melhor: trabalhe com numeric(ou um tipo de ponto flutuante). Ver abaixo.

Alternativo

É assim que eu acho que sua consulta realmente deve ser (calculando um compartilhamento relativo por 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;

A expressão sum(t.cnt) OVER ()é uma função de janela . Você poderia usar um CTE em vez da subconsulta - bonito, mas uma subconsulta é normalmente mais barata em casos simples como este.

Uma declaração explícitaRETURN final não é necessária (mas permitida) ao trabalhar com OUTparâmetros ou RETURNS TABLE(o que faz uso implícito de OUTparâmetros).

round()com dois parâmetros só funciona para numerictipos. count()na subconsulta produz um bigintresultado e um sum()over this bigintproduz um numericresultado, portanto, lidamos com um numericnúmero automaticamente e tudo simplesmente se encaixa.

Erwin Brandstetter
fonte
Muito obrigado pela sua resposta e correções. Está funcionando bem agora (só mudei o tipo de proporção para numérico).
Renato Dinhani 30/10/2011
@ RenatoDinhaniConceição Cool! Eu adicionei uma versão que pode ou não responder a uma pergunta adicional que você realmente não fez. ;)
Erwin Brandstetter
Legal, a única coisa que acho que você precisa de um RETURN;antes disso END;, pelo menos eu precisava - mas estou fazendo um UNION, então não tenho certeza se isso o torna diferente.
yekta
@yekta: Eu adicionei algumas informações sobre a função de RETURN. Corrigido um erro não relacionado e adicionando algumas melhorias enquanto fazia isso.
Erwin Brandstetter
1
Qual é a maneira de fazer isso quando você não quer restringir o que está em Return TABLE (). IE RETURN TABLE (*)?
Nick,
1

Olá, por favor, verifique o link abaixo

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
fonte