如何获取数组元素的类型?

时间:2015-11-26 02:12:38

标签: arrays postgresql polymorphism plpgsql postgresql-9.4

我正在编写一个迭代数组的多态 PL / pgSQL函数。我对使用FOREACH感兴趣,但我无法弄清楚如何使用正确的类型声明临时变量。

我的功能如下,有关详细信息,请参阅第4行的评论。

CREATE OR REPLACE FUNCTION uniq(ary anyarray) RETURNS anyarray AS $$
DECLARE
  ret ary%TYPE := '{}';
  v ???; -- how do I get the element type of @ary@?
BEGIN
  IF ary IS NULL THEN
    return NULL;
  END IF;

  FOREACH v IN ARRAY ary LOOP
    IF NOT v = any(ret) THEN
      ret = array_append(ret, v);
    END IF;
  END LOOP;

  RETURN ret;
END;
$$ LANGUAGE plpgsql;

2 个答案:

答案 0 :(得分:3)

回答主要问题

AFAIK,你不能声明多态类型的变量而不用 a"模板"变量或参数。

本章末Declaring Function Parameters的手册中有相关示例,但 技巧未涵盖:添加另一个IN函数定义的数据类型为INOUT的{​​{1}}或OUT参数。它自动解析为匹配元素类型,可以(ab)直接在函数体内用作变量,也可以用作更多变量的模板:

ANYELEMENT

相关:

复制类似的类型仅适用于CREATE OR REPLACE FUNCTION uniq1(ary ANYARRAY, v ANYELEMENT = NULL) RETURNS anyarray AS $func$ DECLARE ret ary%TYPE := '{}'; some_var v%TYPE; -- we could declare more variables now -- but we don't need to BEGIN IF ary IS NULL THEN RETURN NULL; END IF; FOREACH v IN ARRAY ary LOOP -- instead, we can use v directly IF NOT v = any(ret) THEN ret := array_append(ret, v); END IF; END LOOP; RETURN ret; END $func$ LANGUAGE plpgsql;部分,并且是不同的类型转换。 It is explained in the manual here.

指定默认值,因此添加的参数不必包含在函数调用中:DECLARE ANYELEMENT

致电(未更改):

= NULL

更好的功能

为了方便,我实际上会使用OUT参数并反转测试逻辑:

SELECT uniq1('{1,2,1}'::int[]);
SELECT uniq1('{foo,bar,bar}'::text[]);

但是这仍然不包括所有包含NULL元素的情况。

正常功能

也适用于NULL元素:

CREATE OR REPLACE FUNCTION uniq2(ary ANYARRAY, elem ANYELEMENT = NULL
                               , OUT ret ANYARRAY)
  RETURNS anyarray AS
$func$
BEGIN
   IF ary IS NULL
      THEN RETURN;
      ELSE ret := '{}';  -- init
   END IF;

   FOREACH elem IN ARRAY ary LOOP
      IF elem = ANY(ret) THEN  -- do nothing
      ELSE
         ret := array_append(ret, elem);
      END IF;
   END LOOP;
END
$func$  LANGUAGE plpgsql;

检查数组中的NULL有点痛苦:

所有这些功能都是只是概念验证。我会使用 。代替:

使用纯SQL的高级解决方案

在Postgres 9.4中使用CREATE OR REPLACE FUNCTION uniq3(ary ANYARRAY, elem ANYELEMENT = NULL , OUT ret ANYARRAY) RETURNS anyarray AS $func$ BEGIN IF ary IS NULL THEN RETURN; ELSE ret := '{}'; -- init END IF; FOREACH elem IN ARRAY ary LOOP IF elem IS NULL THEN -- special test for NULL IF array_length(array_remove(ret, NULL), 1) = array_length(ret, 1) THEN ret := array_append(ret, NULL); END IF; ELSIF elem = ANY(ret) THEN -- do nothing ELSE ret := array_append(ret, elem); END IF; END LOOP; END $func$ LANGUAGE plpgsql; 来保留元素的原始顺序。 详细解释:

单值基本代码:

WITH ORDINALITY

返回:

SELECT ARRAY (
   SELECT elem
   FROM  (
      SELECT DISTINCT ON (elem) elem, i
      FROM   unnest('{1,2,1,NULL,4,NULL}'::int[]) WITH ORDINALITY u(elem, i)
      ORDER  BY elem, i
      ) sub
   ORDER  BY i) AS uniq;

关于uniq ------------ {1,2,NULL,4}

内置于查询中:

DISTINCT ON

这有一个小角落的情况:它返回一个空数组一个NULL数组。涵盖所有基础:

SELECT *
FROM   test t
     , LATERAL (
   SELECT ARRAY (
      SELECT elem
      FROM  (
         SELECT DISTINCT ON (elem) elem, i
         FROM   unnest(t.arr) WITH ORDINALITY u(elem, i)
         ORDER  BY elem, i
         ) sub
      ORDER BY i) AS arr
   ) a;

或者:

SELECT t.*, CASE WHEN t.arr IS NULL THEN NULL ELSE a.arr END AS arr
FROM   test t
     , LATERAL (
   SELECT ARRAY (
      SELECT elem
      FROM  (
         SELECT DISTINCT ON (elem) elem, ord
         FROM   unnest(t.arr) WITH ORDINALITY u(elem, ord)
         ORDER  BY elem, ord
         ) sub
      ORDER BY ord) AS arr
   ) a;

Postgres 9.3 或更早版本中,您可以使用SELECT * FROM test t LEFT JOIN LATERAL ( SELECT ARRAY ( SELECT elem FROM ( SELECT DISTINCT ON (elem) elem, i FROM unnest(t.arr) WITH ORDINALITY u(elem, i) ORDER BY elem, i ) sub ORDER BY i) AS arr ) a ON t.arr IS NOT NULL; 代替:

generate_subscripts()

我们在sqlfiddle中需要这个,目前只支持第9.3页,所以SELECT * FROM test t , LATERAL ( SELECT ARRAY ( SELECT elem FROM ( SELECT DISTINCT ON (t.arr[i]) t.arr[i] AS elem, i FROM generate_subscripts(t.arr, 1) i ORDER BY t.arr[i], i ) sub ORDER BY i ) AS arr ) a; 不可用:

SQL Fiddle.

答案 1 :(得分:2)

我不知道如何声明`@section('scripts')` <script> $(function() { $("#posts-table").DataTable({ order: [[0, "desc"]] }); }); </script> `@stop`参数的基类型变量(the documentation没有提到这种可能性。)

您可以使用anyarray代替整数变量:

FOR LOOP

但是,循环和变量可能没有必要:

CREATE OR REPLACE FUNCTION uniq(ary anyarray) RETURNS anyarray AS $$
DECLARE
  ret ary%TYPE := '{}';
  i int;
BEGIN
  IF ary IS NULL THEN
    return NULL;
  END IF;

  FOR i IN array_lower(ary, 1) .. array_upper(ary, 1) LOOP
    IF NOT ary[i] = any(ret) THEN
      ret = array_append(ret, ary[i]);
    END IF;
  END LOOP;

  RETURN ret;
END;
$$ LANGUAGE plpgsql;

使数组顺序保持不变的上述函数的版本:

create or replace function uniq_without_loop(arr anyarray)
returns anyarray language plpgsql as $$
begin
    return (
        select array_agg(distinct elem)
        from unnest(arr) elem);
end $$;