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

问题描述 投票:0回答:2

我正在编写一个迭代数组的多态 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;
arrays postgresql polymorphism plpgsql postgresql-9.4
2个回答
4
投票

回答主要问题

如果没有“模板”变量或参数,则无法声明多态类型的变量。

手册中章节

声明函数参数有相关示例,但是这个技巧没有涉及到:

将数据类型为

IN

 的另一个 
INOUT
OUT
ANYELEMENT
 参数添加到函数定义中。它自动解析为匹配的元素类型,并且可以直接(ab)用作函数体内的变量或用作更多变量的模板:

CREATE OR REPLACE FUNCTION uniq1(ary ANYARRAY, v ANYELEMENT = NULL) RETURNS anyarray LANGUAGE plpgsql 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$;
相关:

  • 我可以让 plpgsql 函数在不使用变量的情况下返回整数吗?
这样的复制类型仅适用于

DECLARE

 部分,并且是不同的类型转换。 
这里的手册中有说明。

分配一个默认值,因此添加的参数不必包含在函数调用中:

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 LANGUAGE plpgsql 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$;
但这仍然没有涵盖所有包含空元素的情况。

功能正常

也适用于空元素:

CREATE OR REPLACE FUNCTION uniq3(ary ANYARRAY, elem ANYELEMENT = NULL , OUT ret ANYARRAY) RETURNS anyarray LANGUAGE plpgsql 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$;
检查数组中的 NULL 有点痛苦:

  • Postgres中如何判断数组中是否包含NULL?
所有这些功能都

只是概念证明。我不会使用都不会。相反:

使用简单 SQL 的卓越解决方案

在 Postgres 9.4 中使用

WITH ORDINALITY

 来保留元素的原始顺序。
详细解释:

  • PostgreSQL unnest() 与元素编号
单值基本代码:

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

  • 选择每个 GROUP BY 组中的第一行?
内置查询:

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 小提琴。


2
投票
我不知道如何声明

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 $$;

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

create or replace function unsorted_uniq_without_loop(arr anyarray) returns anyarray language plpgsql as $$ begin return ( select array_agg(elem) from ( select elem from ( select distinct on(elem) elem, row_number() over () from unnest(array[arr]) elem ) sub order by row_number ) sub); end $$;
    
© www.soinside.com 2019 - 2024. All rights reserved.