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 OUT
parâmetros devem corresponder exatamente ao que é retornado pela consulta.
Escolha os nomes dos OUT
parâ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 OUT
parâmetro cnt
e o alias da coluna de mesmo nome. Neste caso particular ( RETURN QUERY SELECT ...
) Postgres usa o alias da coluna sobre o OUT
parâmetro de qualquer maneira. Isso pode ser ambíguo em outros contextos, no entanto. Existem várias maneiras de evitar qualquer confusão:
- Use a posição ordinal do item na lista SELECT:
ORDER BY 2 DESC
. Exemplo:
- Repita a expressão
ORDER BY count(*)
.
- (Não aplicável aqui.) Defina o parâmetro de configuração
plpgsql.variable_conflict
ou use o comando especial #variable_conflict error | use_variable | use_column
na 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 txt
e cnt
em 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 OUT
parâmetros ou RETURNS TABLE
(o que faz uso implícito de OUT
parâmetros).
round()
com dois parâmetros só funciona para numeric
tipos. count()
na subconsulta produz um bigint
resultado e um sum()
over this bigint
produz um numeric
resultado, portanto, lidamos com um numeric
número automaticamente e tudo simplesmente se encaixa.