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

标签 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;

最佳答案

回答主要问题

据我所知,您不能在没有"template"变量或参数的情况下声明多态类型的变量。

手册末尾有相关例子Declaring Function Parameters ,但未涵盖这个技巧:添加另一个ININOUTOUT数据类型为 ANYELEMENT 的参数添加到函数定义中。它自动解析为匹配的元素类型,并且可以直接(ab)用作函数体内的变量或用作更多变量的模板:

CREATE OR REPLACE FUNCTION uniq1(ary ANYARRAY, <b>v ANYELEMENT = NULL</b>)
  RETURNS anyarray AS
$func$
DECLARE
   ret      ary%TYPE := '{}';
   <b>some_var v%TYPE;  -- we could declare more variables now
                     -- but we don't need to</b>
BEGIN
   IF ary IS NULL THEN
      RETURN NULL;
   END IF;

   FOREACH <b>v</b> IN ARRAY ary LOOP  <b>-- instead, we can use v directly</b>
      IF NOT v = any(ret) THEN
         ret := array_append(ret, v);
      END IF;
   END LOOP;

   RETURN ret;
END
$func$  LANGUAGE plpgsql;

相关:

这样的复制类型只适用于 DECLARE 部分并且是不同的类型转换。 It is explained in the manual here.

分配默认值,因此函数调用中不必包含添加的参数:ANYELEMENT= NULL

调用(不变):

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

更好的功能

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

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 元素的情况。

适当的功能

同样适用于 NULL 元素:

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;

检查数组中的 NULL 有点麻烦:

所有这些功能只是概念验证。我会两者都不。相反:

使用纯 SQL 的高级解决方案

在 Postgres 9.4 中使用 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:

内置查询:

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;

这有一个小的极端情况:它返回一个空数组 NULL 数组。覆盖所有基地:

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;

或者:

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;

Postgres 9.3 或更早版本中,您可以用 generate_subscripts() 代替:

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;

我们在 sqlfiddle 中需要这个,它目前只支持 pg 9.3,所以 WITH ORDINALITY 不可用:

SQL Fiddle.

关于arrays - 如何获取数组元素的类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33929538/

相关文章:

c++ - 如何将字符串转换成char *数组

c++ - 将 char* 分配给字符串而不复制

sql - 如何在 SQL 中获取 "top X with the rest"?

Swift 字典键类型

java - 如何在 JMX 中表示多态性?

c - 如何在 C 中创建静态 volatile 结构数组?

c - 静态常量数组中的出站访问

java - 如何使用 SQL 进行递归调用?

Postgresql - 不正确的排序

java - 编译时多态性与运行时多态性(或方法签名)的行为