Как вернуть результат SELECT внутри функции в PostgreSQL? - PullRequest
79 голосов
/ 30 октября 2011

У меня есть эта функция в PostgreSQL, но я не знаю, как вернуть результат запроса:

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;

Но я не знаю, как вернуть результат запроса внутриФункция PostgreSQL.

Я обнаружил, что тип возвращаемого значения должен быть SETOF RECORD, верно?Но команда возврата не верна.

Как правильно сделать это?

Ответы [ 2 ]

103 голосов
/ 30 октября 2011

Использование 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.Это может быть неоднозначным в других контекстах, однако.Существуют различные способы избежать путаницы:

    1. Используйте порядковый номер элемента в списке SELECT: ORDER BY 2 DESC.Пример:
    2. Повторить выражение ORDER BY count(*).
    3. (Неприменимо здесь.) Установите параметр конфигурации 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 числом, и все просто становится на свои места.

0 голосов
/ 26 июня 2019

Привет, пожалуйста, проверьте ссылку ниже

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