PostgreSQL plpgsql variadic argments , parameters - 可变参数个数

3 minute read

背景

PostgreSQL 函数支持可变个数的参数,有很多聚合函数就是可变参数的,可变参数在一些场景非常有效,能解决一些特殊的问题,帮助简化代码.

正文

但是使用可变参数, 有一定的要求. 首先可变参数必须放在函数参数列表的末尾部位, 另外就是可变参数使用时定义为数组,所以参数类型必须一致.

例如 :

digoal=> CREATE FUNCTION mleast(VARIADIC arr numeric[]) RETURNS numeric AS $$  
digoal$>     SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i);  
digoal$> $$ LANGUAGE SQL;  
CREATE FUNCTION  

变长参数函数的调用方法如下 :

digoal=> select mleast(1,2,3);  
 mleast   
--------  
      1  
(1 row)  
digoal=> SELECT mleast(VARIADIC ARRAY[1,2,3]);  
 mleast   
--------  
      1  
(1 row)  
digoal=> SELECT mleast(VARIADIC arr := ARRAY[1,2,3]);  
 mleast   
--------  
      1  
(1 row)  

前面提到VARIADIC参数必须放在所有参数的末尾, 如果不是末尾将报错

digoal=> CREATE FUNCTION mleast(VARIADIC arr numeric[], info text) RETURNS numeric AS $$  
    SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i);  
$$ LANGUAGE SQL;  
ERROR:  VARIADIC parameter must be the last input parameter  

放到末尾正常 :

digoal=> create or replace function mleast(info text, VARIADIC arr numeric[]) RETURNS numeric AS $$  
digoal$> declare   
digoal$>   result numeric := null;  
digoal$> begin  
digoal$>   raise notice '%', info;  
digoal$>   SELECT min(arr[i]) into result FROM generate_subscripts(arr, 1) g(i);   
digoal$>   return result;  
digoal$> end;  
digoal$> $$ language plpgsql strict;  
CREATE FUNCTION  
digoal=> select * from mleast('hello', 1.1, 3.0, -1.1, 9.9);  
NOTICE:  hello  
 mleast   
--------  
   -1.1  
(1 row)  

同时variadic参数至少输入1个参数, 否则函数是不匹配的 :

digoal=> select * from mleast('hello');  
ERROR:  function mleast(unknown) does not exist  
LINE 1: select * from mleast('hello');  
                      ^  
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.  
  
digoal=> select * from mleast('hello',null);  
NOTICE:  hello  
 mleast   
--------  
         
(1 row)  

又如 :

postgres=# create or replace function avg(VARIADIC arr numeric[]) RETURNS numeric AS $$  
declare   
  result numeric := null;  
begin  
  SELECT avg(arr[i]) into result FROM generate_subscripts(arr, 1) g(i);   
  return result;                                                          
end;              
$$ language plpgsql strict;  
CREATE FUNCTION  
  
postgres=# select * from avg(1,2,3,4,5,6,7,8,9);  
        avg           
--------------------  
 5.0000000000000000  
(1 row)  

不支持多个不定长数组.

postgres=# create or replace function avg(VARIADIC arr1 numeric[], VARIADIC arr2 text[]) RETURNS numeric AS $$  
declare   
  result numeric := null;  
begin  
  SELECT avg(arr[i]) into result FROM generate_subscripts(arr, 1) g(i);   
  return result;  
end;  
$$ language plpgsql strict;  
ERROR:  VARIADIC parameter must be the last input parameter  

如果需要用到varidic的refcursor, 需要使用一些手段,不要直接使用open arr[slice] 来打开游标

postgres=# CREATE FUNCTION myfunc(variadic refcursor[]) RETURNS SETOF refcursor AS $$  
begin  
 open $1[1] for select * from pg_class;  
 return next $1[1];  
 open $1[2] for select * from pg_class;  
 return next $1[2];  
end;  
$$ lANGUAGE plpgsql;  
ERROR:  42601: syntax error, expected "FOR" at or near "["  
LINE 3:  open $1[1] for select * from pg_class;  
                ^  
LOCATION:  plpgsql_yyerror, pl_scanner.c:639  
Time: 0.734 ms  

可以使用变量消除这个语法错误

postgres=# CREATE FUNCTION myfunc(variadic refcursor[]) RETURNS SETOF refcursor AS $$  
declare  
  res refcursor;  
begin  
 for x in 1..array_length($1,1) loop  
   res := $1[x];  
   open res for select * from pg_class;  
   return next res;  
 end loop;  
end;  
$$ lANGUAGE plpgsql;  
  
postgres=# select * from myfunc('a','b');  
 myfunc   
--------  
 a  
 b  
(2 rows)  
  
Time: 0.930 ms  
postgres=# fetch 1 in a;  
     relname     | relnamespace | reltype | reloftype | relowner | relam | relfilenode | reltablespace | relpages | reltuples | relallvisible | reltoastrelid | relhasindex | relisshared | relpersistence | relkind | relnatts | relchecks |  
 relhasoids | relhaspkey | relhasrules | relhastriggers | relhassubclass | relrowsecurity | relforcerowsecurity | relispopulated | relreplident | relfrozenxid | relminmxid | relacl | reloptions   
-----------------+--------------+---------+-----------+----------+-------+-------------+---------------+----------+-----------+---------------+---------------+-------------+-------------+----------------+---------+----------+-----------+  
------------+------------+-------------+----------------+----------------+----------------+---------------------+----------------+--------------+--------------+------------+--------+------------  
 pg_toast_252744 |           99 |  252748 |         0 |       10 |     0 |      252747 |             0 |        0 |         0 |             0 |             0 | t           | f           | p              | t       |        3 |         0 |  
 f          | t          | f           | f              | f              | f              | f                   | t              | n            |    386118340 |          1 |        |   
(1 row)  
  
Time: 0.687 ms  
postgres=# fetch 1 in b;  
     relname     | relnamespace | reltype | reloftype | relowner | relam | relfilenode | reltablespace | relpages | reltuples | relallvisible | reltoastrelid | relhasindex | relisshared | relpersistence | relkind | relnatts | relchecks |  
 relhasoids | relhaspkey | relhasrules | relhastriggers | relhassubclass | relrowsecurity | relforcerowsecurity | relispopulated | relreplident | relfrozenxid | relminmxid | relacl | reloptions   
-----------------+--------------+---------+-----------+----------+-------+-------------+---------------+----------+-----------+---------------+---------------+-------------+-------------+----------------+---------+----------+-----------+  
------------+------------+-------------+----------------+----------------+----------------+---------------------+----------------+--------------+--------------+------------+--------+------------  
 pg_toast_252744 |           99 |  252748 |         0 |       10 |     0 |      252747 |             0 |        0 |         0 |             0 |             0 | t           | f           | p              | t       |        3 |         0 |  
 f          | t          | f           | f              | f              | f              | f                   | t              | n            |    386118340 |          1 |        |   
(1 row)  
  
Time: 0.669 ms  

如果你不想传参,也可以在函数内直接生成游标数组,然后使用变量消除open array[slice]的语法问题。

CREATE FUNCTION myfunc(int) RETURNS SETOF refcursor AS $$  
declare  
  ref refcursor[];  
  res refcursor;  
begin  
 for x in 1..$1 loop  
   res := $1[x];  
   open res for select * from pg_class;  
   return next res;  
 end loop;  
end;  
$$ lANGUAGE plpgsql;  

参考

1. http://www.postgresql.org/docs/devel/static/xfunc-sql.html#XFUNC-SQL-VARIADIC-FUNCTIONS

2. http://www.postgresql.org/docs/devel/static/plpgsql-overview.html

3. http://blog.163.com/digoal@126/blog/static/1638770402012714102818672/

Flag Counter

digoal’s 大量PostgreSQL文章入口