How to apply a function to each element of an array column in Postgres?

后端 未结 4 696
遇见更好的自我
遇见更好的自我 2020-12-07 20:32

A Pg query returns an array. I would like to retrieve that with each element formatted to 3 decimal places. How can I apply a function to each element of an array? Something

相关标签:
4条回答
  • 2020-12-07 20:50

    You may need to create a stored function. Here is the one that does what you need:

    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;
    

    Then call something like this:

    # SELECT array_round(ARRAY[1.53224,0.23411234], 2);
     array_round 
    -------------
     {1.53,0.23}
    (1 row)
    
    0 讨论(0)
  • 2020-12-07 20:55

    You need to turn the array into a row set. For example, using generate_series:

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

    I know that's pretty ugly. There should be a helper function to make such mappings easier.

    Perhaps something like (yes it's horrible, slow, and brittle dynamic code):

    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}
    

    Update It occurs to me that we could use a single dynamic statement for the whole loop. This could mitigate some of the performance concerns.

    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;
    $$;
    
    0 讨论(0)
  • 2020-12-07 21:00

    First, turn the array into a set using unnest:

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

    Then, apply an expression to the column:

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

    Finally, use array_agg to turn the set back into an array:

    > SELECT array_agg(ROUND(n, 2)) FROM unnest(ARRAY[1.53224,0.23411234]) AS n;
      array_agg  
    -------------
     {1.53,0.23}
    (1 row)
    
    0 讨论(0)
  • 2020-12-07 21:06
    postgres=# select array(select round(unnest(array[1.2,2.4,3,4])));
       array   
    -----------
    {1,2,3,4}
    (1 row)
    
    0 讨论(0)
提交回复
热议问题