【问题标题】:How do I get the type of an array's elements?如何获取数组元素的类型?
【发布时间】:2015-11-26 02:12:37
【问题描述】:

我正在编写一个迭代数组的多态 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


    【解决方案1】:

    回答主要问题

    AFAIK,你不能声明一个多态类型的变量没有一个“模板”变量或参数。

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

    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;

    相关:

    这样的复制类型只适用于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.

    【讨论】:

    • 我喜欢 WITH ORDINALITY 部分。这正是我想要的。
    • 非常好的答案,我一直在寻找相同的答案,但在其他任何地方都找不到,如果您能提供从哪里获得此文件的链接,那就太好了---> variable%TYPE。它也只适用于变量的声明我尝试过同样的类型转换但它没有工作
    • @abhirathore2006:我为上面的%TYPE 添加了指向手册的链接。类型转换是完全不同的野兽。
    【解决方案2】:

    我不知道如何声明anyarray 参数的基类型变量(the documentation 没有提到这种可能性)。

    您可以将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 $$;
    

    【讨论】:

    • 我已经尝试了您的第二个解决方案。问题是我使用的数据必须保持顺序,但是您的第二个解决方案会打乱顺序。
    • 有趣的是,有人在 Postgres 9.4 版被冻结发布之前提交了一个补丁,该补丁实现了arr%elemtype
    • 我添加了不排序结果的版本。这种特殊类型的想法是有道理的。
    猜你喜欢
    • 1970-01-01
    • 2019-09-30
    • 1970-01-01
    • 2011-10-25
    • 2013-10-28
    • 2019-11-17
    • 2018-07-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多