Ответ 1
Используйте 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;
Вызов:
SELECT * FROM word_frequency(123);
Объяснение:
-
Гораздо практичнее явно определить тип возвращаемого значения, чем просто объявить его как запись. Таким образом, вам не нужно предоставлять список определений столбцов при каждом вызове функции.
RETURNS TABLE
является одним из способов сделать это. Есть и другие. Типы данных параметровOUT
должны точно соответствовать тому, что возвращается запросом. -
Тщательно выбирайте имена для параметров
OUT
. Они видны в теле функции практически везде. Уточняйте столбцы с одинаковыми именами, чтобы избежать конфликтов или неожиданных результатов. Я сделал это для всех столбцов в моем примере.Но обратите внимание на потенциальный конфликт имен между параметром
OUT
cnt
и псевдонимом столбца с тем же именем. В этом конкретном случае (RETURN QUERY SELECT...
) Postgres в любом случае использует псевдоним столбца над параметромOUT
. Это может быть неоднозначным в других контекстах, однако. Существуют различные способы избежать путаницы:- Используйте порядковый номер элемента в списке SELECT:
ORDER BY 2 DESC
. Пример: - Повторите выражение
ORDER BY count(*)
. - (Не применимо здесь.) Установите параметр конфигурации
plpgsql.variable_conflict
или используйте специальную команду#variable_conflict error | use_variable | use_column
#variable_conflict error | use_variable | use_column
#variable_conflict error | use_variable | use_column
в функции. Увидеть:
- Используйте порядковый номер элемента в списке SELECT:
-
Не используйте "текст" или "считать" в качестве имен столбцов. И то, и другое разрешено использовать в Postgres, но "count" - зарезервированное слово в стандартном SQL, а базовое имя функции и "text" - базовый тип данных. Может привести к запутанным ошибкам. Я использую
txt
иcnt
в моих примерах. -
Добавлен недостающий
;
и исправил синтаксическую ошибку в заголовке.(_max_tokens int)
, not(int maxTokens)
- введите после имени. -
При работе с целочисленным делением лучше сначала умножить, а потом разбить, чтобы минимизировать ошибку округления. Еще лучше: работа с
numeric
(или с плавающей точкой). Увидеть ниже.
альтернатива
Вот как я думаю, ваш запрос должен выглядеть так (вычисляя относительную долю на каждый токен):
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;
Выражение sum(t.cnt) OVER()
является оконной функцией. Вы можете использовать CTE вместо подзапроса - довольно, но подзапрос, как правило, дешевле в таких простых случаях, как этот.
Последний явный оператор RETURN
не требуется (но допускается) при работе с параметрами OUT
или RETURNS TABLE
(что подразумевает использование параметров OUT
неявно).
round()
с двумя параметрами работает только для numeric
типов. count()
в подзапросе дает результат bigint
а sum()
этого bigint
- numeric
результат, поэтому мы автоматически работаем с numeric
числом, и все становится на свои места.