Необязательный аргумент в функции PL/pgSQL

Я пытаюсь написать функцию PL/pgSQL с необязательными аргументами. Он выполняет запрос на основе отфильтрованного набора записей (если указан), в противном случае выполняет запрос по всему набору данных в таблице.

Например (PSEUDO CODE):

CREATE OR REPLACE FUNCTION foofunc(param1 integer, param2 date, param2 date, optional_list_of_ids=[]) RETURNS SETOF RECORD AS $$
    IF len(optional_list_of_ids) > 0 THEN
        RETURN QUERY (SELECT * from foobar where f1=param1 AND f2=param2 AND id in optional_list_of_ids);
    ELSE
        RETURN QUERY (SELECT * from foobar where f1=param1 AND f2=param2);
    ENDIF
$$ LANGUAGE SQL;

Каким будет правильный способ реализации этой функции?

В стороне, я хотел бы знать, как я мог бы назвать такую функцию в другой внешней функции. Вот как я это сделаю - это правильно, или есть лучший способ?

CREATE FUNCTION foofuncwrapper(param1 integer, param2 date, param2 date) RETURNS SETOF RECORD AS $$
BEGIN
   CREATE TABLE ids AS SELECT id from foobar where id < 100;
   RETURN QUERY (SELECT * FROM foofunc(param1, param2, ids));
END
$$ LANGUAGE SQL

Ответ 1

Поскольку PostgreSQL 8.4 (который вы, похоже, выполняете), есть значения по умолчанию для параметров функции. Если вы поместите свой параметр последним и укажите значение по умолчанию, вы можете просто опустить его из вызова:

CREATE OR REPLACE FUNCTION foofunc(_param1 integer
                                 , _param2 date
                                 , _ids    int[] DEFAULT '{}')
  RETURNS SETOF foobar AS -- declare return type!
$func$
BEGIN  -- required for plpgsql
   IF _ids <> '{}'::int[] THEN -- exclude empty array and NULL
      RETURN QUERY
      SELECT *
      FROM   foobar
      WHERE  f1 = _param1
      AND    f2 = _param2
      AND    id = ANY(_ids); -- "IN" is not proper syntax for arrays
   ELSE
      RETURN QUERY
      SELECT *
      FROM   foobar
      WHERE  f1 = _param1
      AND    f2 = _param2;
   END IF;
END  -- required for plpgsql
$func$  LANGUAGE plpgsql;

Основные моменты:

  • Ключевое слово DEFAULT используется для объявления параметров по умолчанию. Короткая альтернатива: =.

  • Я удалил избыточный param1 из грязного примера.

  • Поскольку вы возвращаете SELECT * FROM foobar, объявите возвращаемый тип как RETURNS SETOF foobar вместо RETURNS SETOF record. Последняя форма с анонимными записями очень громоздка, вам нужно будет предоставить список определения столбцов при каждом вызове.

  • Я использую массив integer (int[]) как параметр функции. Адаптировало выражение IF и WHERE соответственно.

  • Операторы IF не доступны в простом SQL. Для этого должен быть LANGUAGE plpgsql.

Вызов с или без _ids:

SELECT * FROM foofunc(1, '2012-1-1'::date);

Эффективно то же самое:

SELECT * FROM foofunc(1, '2012-1-1'::date, '{}'::int[]);

Вы должны убедиться, что вызов однозначен. Если у вас есть другая функция с тем же именем и двумя параметрами, Postgres может не знать, что выбрать. Явное кастинг (как я демонстрирую) сужает его. Иные, нетипизированные строковые литералы тоже работают, но явное никогда не болит.

Вызов из другой функции:

CREATE FUNCTION foofuncwrapper(_param1 integer, _param2 date)
  RETURNS SETOF foobar AS
$func$
DECLARE
   _ids int[] := '{1,2,3}';
BEGIN
   -- irrelevant stuff

   RETURN QUERY
   SELECT * FROM foofunc(_param1, _param2, _ids);
END
$func$  LANGUAGE plgpsql;

Ответ 2

Разрабатывая на Фрэнка ответ на эту тему:

VARIADIC агит не должен быть единственным аргументом, только последним.

Вы можете использовать VARIADIC для функций, которые могут принимать нулевые переменные аргументы, но это немного немного, потому что для нулевого args требуется другой стиль вызова. Вы можете предоставить функцию обертки, чтобы скрыть уродство. Учитывая начальное определение функции:

CREATE OR REPLACE FUNCTION foofunc(param1 integer, param2 date, param2 date, optional_list_of_ids VARIADIC integer[]) RETURNS SETOF RECORD AS $$
....
$$ language sql;

Для нулевых аргументов используйте обертку, например:

CREATE OR REPLACE FUNCTION foofunc(integer, date, date) RETURNS SETOF RECORD AS $body$
SELECT foofunc($1,$2,$3,VARIADIC ARRAY[]::integer[]);
$body$ LANGUAGE 'sql';

или просто вызовите главное func с пустым массивом, например VARIADIC '{}'::integer[]. Обертка уродлива, но в ней содержится уродство, поэтому я рекомендую использовать обертку.

Прямые вызовы могут быть сделаны в вариационной форме:

SELECT foofunc(1,'2011-01-01','2011-01-01', 1, 2, 3, 4);

... или форму вызова массива с массивом ctor:

SELECT foofunc(1,'2011-01-01','2011-01-01', VARIADIC ARRAY[1,2,3,4]);

... или текстовая форма массива:

SELECT foofunc(1,'2011-01-01','2011-01-01', VARIADIC '{1,2,3,4}'::int[]);

Последние две формы работают с пустыми массивами.