使用数组中的参数值动态执行 PostgresSQL

Ric*_*ard 9 postgresql stored-procedures dynamic-sql parameter plpgsql

我想知道这在 Postgres 中是否可行:

最好用一个人为的例子来解释:

create or replace function test_function(filter_param1 varchar default null
                                       , filter_param2 varchar default null)
  returns integer as
$$ 
declare
  stmt text;
  args varchar[];
  wher varchar[];
  retid integer;
begin
  if filter_param1 is not null then 
    array_append(args, filter_param1);
    array_append(wher, 'parameter_name = $1');
  end if;
  if filter_param2 is not null then 
    array_append(args, filter_param2);
    array_append(wher, 'parameter_name = $2');
  end if;

  stmt := 'select id from mytable where ' || array_to_string(wher, ' or ');
  execute stmt into retid using args;

  return retid;
end;
$$ language plpgsql;
Run Code Online (Sandbox Code Playgroud)

在 Python 中有*args- 也许 PostgreSQL 有类似的机制?

编辑 Erwin Brandstetter 问题:

  • 所有filter参数都将应用于不同的列,但应该进行 AND 运算。
  • 返回setof在这里更有意义。
  • 所有参数都可以是相同的列类型(即。varchar)。

Erw*_*ter 8

无论哪种方式,这都是完全可能的,因为您的所有参数都具有相同的数据类型

EXECUTE ... USING愉快地接受一个数组,它被视为单个参数。使用数组下标访问元素。

create or replace function test_function(_filter1 text = null
                                       , _filter2 text = null
                                       , OUT retid int) as
$func$
declare
   _args text[] := ARRAY[_filter1, _filter2];
   _wher text[];
begin
   if _filter1 is not null then 
      _wher := _wher || 'parameter_name = $1[1]'; -- note array subscript
   end if;

   if _filter2 is not null then 
      _wher := _wher || 'parameter_name = $1[2]'; -- assign the result!
   end if;
  
   IF _args  IS NULL         -- check whether all params are NULL
      RAISE EXCEPTION 'At least one parameter required!';
   END IF;

   execute 'select id from mytable where ' -- cover case with all params NULL
         || array_to_string(_wher, ' or ')
         || ' ORDER BY id LIMIT 1';   -- For a single value (???)
   into  retid
   using _args;
end
$func$  language plpgsql;
Run Code Online (Sandbox Code Playgroud)

这只是一个概念证明,并且不必要地复杂化。对于实际的数组输入,这将是一个有趣的选项,例如使用 [ VARIADICfunction][2]。例子:

对于手头的情况,请改用:

CREATE OR REPLACE FUNCTION test_function(_filter1 text = null
                                       , _filter2 text = null)
  RETURNS SETOF int AS
$func$
DECLARE
   _wher text := concat_ws(' OR '
             , CASE WHEN _filter1 IS NOT NULL THEN 'parameter_name = $1' END
             , CASE WHEN _filter2 IS NOT NULL THEN 'parameter_name = $2' END);
BEGIN
   IF _wher = ''   -- check whether all params are NULL
      RAISE EXCEPTION 'At least one parameter required!';
   END IF;

   RETURN QUERY EXECUTE 'SELECT id FROM mytable WHERE ' || _wher
   USING  $1, $2;
   -- USING  _filter1 , filter2; -- alternatively use func param names
END
$func$  LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

解释

  • USING按出现顺序列出子句中动态查询中可能引用的所有值。如果不是所有这些都将在动态查询中被引用,那也没有什么坏处。但是我们需要保持有序位置不变。

  • 特别要注意的是,在动态查询内部,按序数引用子句的给定值,而子句中引用函数参数。相同的语法,不同的范围!为了简单起见, 在我的示例中引用。但是可以以任何方式对子句中的值重新排序,以便(例如)在动态查询中引用子句中的第二个位置,它引用了第一个函数参数。$n USING$n USING
    $2$2USING$2$1USING

  • 这允许具有任何(异构)数据类型的任意数量的参数。

  • 在这个例子中返回一组整数(RETURNS SETOF int),它更适合这个例子 - 相应地使用RETURN QUERY EXECUTE

  • concat_ws() 有条件地组合 OR 或 AND 谓词列表特别方便。