Использование 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
любом случае использует псевдоним столбца над параметром. Однако в других контекстах это может быть неоднозначным. Есть несколько способов избежать путаницы:
- Используйте порядковый номер элемента в списке выбора:
ORDER BY 2 DESC
. Пример:
- Повторите выражение
ORDER BY count(*)
.
- (Здесь не применимо.) Задайте параметр конфигурации
plpgsql.variable_conflict
или используйте специальную команду #variable_conflict error | use_variable | use_column
в функции. Видеть:
Не используйте «текст» или «счет» в качестве имен столбцов. Оба варианта разрешены для использования в Postgres, но «count» - зарезервированное слово в стандартном SQL, а имя базовой функции и «text» - это базовый тип данных. Может привести к сбивающим с толку ошибкам. Использую txt
и cnt
в своих примерах.
Добавил отсутствующий ;
и исправил синтаксическую ошибку в шапке. (_max_tokens int)
, а не (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
автоматически обрабатываем число, и все просто становится на свои места.