Як повернути результат 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, правда? Але команда return не вірна.

Який правильний спосіб це зробити?


Чому ти їх рахуєш; у вас є повторювані маркери в токені TABLE? Також: будь-ласка, додайте до свого запитання визначення таблиці.
wildplasser

1
Це ваша ціла функція? Якщо у вас немає інших тверджень у функції, вам слід просто зробити це LANGUAGE SQL.
jpmc26

Відповіді:


134

Використання 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

@yekta: Я додав деяку інформацію, що стосується ролі RETURN. Виправлено непов’язану помилку та додав деякі вдосконалення під час її роботи.
Ервін Брандстеттер

1
Який спосіб це зробити, коли ви не хочете обмежувати те, що знаходиться у Return TABLE (). IE ПОВЕРНУТЬСЯ ТАБЛИЦЯ (*)?
Нік

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.