Как вернуть результат SELECT внутри функции в PostgreSQL?


106

У меня есть эта функция в 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, верно? Но команда возврата неверна.

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


Почему вы их считаете? у вас есть дубликаты токенов в ТАБЛИЦЕ токенов? Также: добавьте определение таблицы к вашему вопросу.
wildplasser

1
Это вся ваша функция? Если у вас нет других операторов в функции, просто сделайте это LANGUAGE SQL.
jpmc26 04

Ответы:


136

Использование 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. Используйте порядковый номер элемента в списке выбора: 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автоматически обрабатываем число, и все просто становится на свои места.


Большое спасибо за ваш ответ и исправления. Теперь работает нормально (я только изменил тип отношения на числовой).
Ренато Динхани

@ RenatoDinhaniConceição Круто! Я добавил версию, которая может отвечать или не отвечать на дополнительный вопрос, который вы на самом деле не задавали. ;)
Эрвин Брандштеттер

Отлично, единственное, что я думаю, вам нужна RETURN;до этого END;, по крайней мере, я сделал - но я делаю UNION, поэтому я не уверен, что это отличает его.
yekta 04

@yekta: Я добавил информацию о роли RETURN. Исправлена ​​не связанная с этим ошибка и добавлены некоторые улучшения, пока она была на ней.
Эрвин Брандштеттер,

1
Как это сделать, если вы не хотите ограничивать то, что находится в Return TABLE (). ТАБЛИЦА ВОЗВРАТА IE (*)?
Ник

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.