【问题标题】:How to apply a function to each element of an array column in Postgres?如何将函数应用于 Postgres 中数组列的每个元素?
【发布时间】:2012-01-24 22:16:55
【问题描述】:

Pg 查询返回一个数组。我想用格式化为 3 位小数的每个元素来检索它。如何将函数应用于数组的每个元素?类似于以下内容(显然是错误的)--

SELECT Round(ARRAY[1.53224,0.23411234], 2);
{1.532, 0.234}

我想我正在寻找类似 Perl 的 map 函数的东西。

【问题讨论】:

  • 大家的好建议。我想我会使用存储过程,因为我需要一直应用这种map 函数。如果我可以将一个函数传递给存储过程,那就更好了,从而将其变成工厂存储过程,并将其转换为真正的map 函数。但是,这将暂时有效。再次感谢大家。
  • Re: 传入函数:你可能对stackoverflow.com/questions/8346065/…感兴趣。 (它远非理想,但你可能会从中得到一些用处。)

标签: arrays postgresql


【解决方案1】:

首先,使用unnest将数组变成一个集合:

> SELECT n FROM unnest(ARRAY[1.53224,0.23411234]) AS n;
     n      
------------
    1.53224
 0.23411234
(2 rows)

然后,将表达式应用于列:

> SELECT ROUND(n, 2) FROM unnest(ARRAY[1.53224,0.23411234]) AS n;
 round 
-------
  1.53
  0.23
(2 rows)

最后,使用array_agg 将集合转回数组:

> SELECT array_agg(ROUND(n, 2)) FROM unnest(ARRAY[1.53224,0.23411234]) AS n;
  array_agg  
-------------
 {1.53,0.23}
(1 row)

【讨论】:

  • 您也可以将其包装到用户定义的round(int[], int) 函数中。
  • 这是最简洁的解决方案,我喜欢你构建它的清晰方式。
  • 请注意,对于空输入数组,arrag_agg() 返回 NULL,即不是数组。有关始终返回(可能为空)数组的解决方案,请参阅@Pavel Stehule 的答案:stackoverflow.com/a/8586492/19163
  • 如果您关心数组排序,这是不安全的。虽然 PostgreSQL 目前不会重新排序此结果,但根据 SQL 规范,将来可以免费这样做。您必须使用unnest ... with ordinality (9.4+)
【解决方案2】:
postgres=# select array(select round(unnest(array[1.2,2.4,3,4])));
   array   
-----------
{1,2,3,4}
(1 row)

【讨论】:

  • array_agg()(参见 Joey 的解决方案)和 array()(此解决方案)之间存在一些性能差异?
  • @PeterKrauss 我现在做了基准测试,速度是一样的
  • 请注意,对于一个空输入数组,arrag_agg() 返回 NULL,而 array() 返回一个空数组。
【解决方案3】:

您可能需要创建一个存储函数。这是满足您需要的那个:

CREATE OR REPLACE FUNCTION array_round(float[], int)
RETURNS float[]
AS
$$
DECLARE
   arrFloats ALIAS FOR $1;
   roundParam ALIAS FOR $2;
   retVal float[];
BEGIN
   FOR I IN array_lower(arrFloats, 1)..array_upper(arrFloats, 1) LOOP
    retVal[I] := round(CAST(arrFloats[I] as numeric), roundParam);
   END LOOP;
RETURN retVal;
END;
$$
LANGUAGE plpgsql 
   STABLE 
RETURNS NULL ON NULL INPUT;

然后这样调用:

# SELECT array_round(ARRAY[1.53224,0.23411234], 2);
 array_round 
-------------
 {1.53,0.23}
(1 row)

【讨论】:

    【解决方案4】:

    您需要将数组转换为行集。例如,使用generate_series:

    SELECT ARRAY(SELECT ROUND(ARRAY[1.53224,0.23411234])[i], 2) FROM generate_series(1,2) AS s(i));    
    

    我知道这很丑。应该有一个辅助函数来使这种映射更容易。

    可能类似于(是的,它是可怕、缓慢且脆弱的动态代码):

    CREATE OR REPLACE FUNCTION map_with_arg(TEXT, ANYARRAY, TEXT)
    RETURNS ANYARRAY
    IMMUTABLE STRICT
    LANGUAGE 'plpgsql' AS
    $$
    DECLARE
        i INTEGER;
        t TEXT;
        cmd TEXT;
    BEGIN
        FOR i IN array_lower($2, 1) .. array_upper($2, 1) LOOP
            cmd := 'SELECT ('||quote_ident($1)||'('||quote_nullable($2[i])||', '||quote_nullable($3)||'))::TEXT';
            EXECUTE cmd INTO t;
            $2[i] := t;
        END LOOP;
        RETURN $2;
    END;
    $$;
    
    select map_with_arg('repeat', array['can','to']::TEXT[], '2');
     map_with_arg
    ---------------
     {cancan,toto}
    

    更新 在我看来,我们可以对整个循环使用单个动态语句。这可以缓解一些性能问题。

    CREATE OR REPLACE FUNCTION map_with_arg(TEXT, ANYARRAY, TEXT)
    RETURNS ANYARRAY
    IMMUTABLE STRICT
    LANGUAGE 'plpgsql' AS
    $$
    DECLARE
        cmd TEXT;
        rv TEXT;
    BEGIN
        cmd := 'SELECT ARRAY(SELECT (' || quote_ident($1)||'($1[i], '||quote_nullable($3)||'))::TEXT FROM generate_subscripts($1, 1) AS gs(i))';
        EXECUTE cmd USING $2 INTO rv;
        RETURN rv;
    END;
    $$;
    

    【讨论】:

    • 循环内的动态 SQL 真是个糟糕的主意。不要在 plpgsql 中这样做。这是 perl、python 或其他脚本语言的相对静态语言和模式,不能在这里应用。
    • 是的,这就是我在代码前的那句话。我在这个公认的异想天开的例子中使用了它,因为我想将要调用的函数传递给 plpgsql。这是编程中的基本抽象,并且可以用任何语言完成。当然,我们需要将其作为字符串执行,然后动态地构造一个新语句,这很痛苦,但是如果您想编写通用映射函数,则需要考虑这一点。另一种方法是必须编写 100 个非通用映射函数。
    • 如果你需要这种形式的抽象,那么使用自己的C模块。 PL/pgSQL 不是抽象编码的语言。数据库端的任何无效代码都会显着降低性能。 map_with_args 应该在 C 中相对简单地实现
    • 带有一些通用地图功能的内置或 C 模块会很有帮助。但我恭敬地不同意应该总是为这样的事情编写一个 C 模块。在每种情况下,性能并不是最重要的问题。对于经验丰富的 PostgreSQL 黑客而言,编写 C 模块与使用内置语言的经济性与我们其他人不同。 ;-)
    • 对于小型项目,您可以拥有真正的。但不适用于比小的更大的东西。数据库的速度通常很重要 - 使用快速数据库,您可以使用简单而小型的环境。当你的数据库很慢时,提高应用程序的速度是非常困难的,而且非常昂贵。对于 C 模块来说,还有什么比简单抽象的工具更好的任务。 C 编码器的价格并不是全部 - 如果那里的资源少,那么其他地方的资源就会更高 - 管理时间、调整时间以及较慢的应用程序带来的更少的业务量。
    猜你喜欢
    • 1970-01-01
    • 2021-05-25
    • 1970-01-01
    • 2014-09-24
    • 2021-12-27
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    相关资源
    最近更新 更多