2011-12-21 17 views
28

Una consulta Pg devuelve una matriz. Me gustaría recuperar eso con cada elemento formateado con 3 decimales. ¿Cómo puedo aplicar una función a cada elemento de una matriz? Algo como lo siguiente (mal, obviamente) -¿Cómo aplicar una función a cada elemento de una columna de matriz en Postgres?

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

supongo que estoy buscando algo como función de Perl map.

+0

sugerencias increíbles de todos. Creo que iré con el proceso almacenado ya que necesito aplicar este tipo de función de 'mapa' todo el tiempo. Sería aún mayor si pudiera pasar una función al proceso almacenado, convirtiéndolo así en un proceso almacenado de fábrica que lo convertiría en una verdadera función de "mapa". Pero, esto funcionará por ahora. Gracias de nuevo, a todos. – punkish

+0

Re: paso en una función: puede estar interesado en http://stackoverflow.com/questions/8346065/function-as-parameter-to-another-function-in-postgres. (Está lejos de ser ideal, pero puede obtener algún uso de él.) – ruakh

Respuesta

8

Es posible que necesite crear una función almacenada. Aquí es el que hace lo que usted necesita:

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; 

luego llamar a algo como esto:

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

Es necesario convertir la matriz en una fila. Por ejemplo, el uso de generate_series:

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

Sé que es bastante feo. Debería haber una función de ayuda para facilitar tales asignaciones.

Tal vez algo así como (sí que es horrible, lento y frágil código dinámico):

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} 

actualización Se me ocurre que podríamos utilizar un único estado dinámico para todo el bucle. Esto podría mitigar algunas de las preocupaciones de rendimiento.

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

El bucle interno de SQL dinámico es realmente una idea terrible. No lo hagas en plpgsql. Este es un lenguaje estático relativo y los patrones de Perl, Python u otros lenguajes de scripting no se pueden aplicar aquí. –

+1

Sí, eso es lo que dice mi oración antes del código. Recurrí a él en este ejemplo ciertamente caprichoso porque quiero pasar la función para llamar a plpgsql. Esa es una abstracción fundamental en la programación y una cosa útil para poder hacer en cualquier idioma. Por supuesto, es una pena que tengamos que hacerlo como una cadena y luego construir una nueva declaración de forma dinámica, pero eso es algo que debe tenerse en cuenta si alguna vez desea escribir una función de mapeo general. La alternativa es tener que escribir 100 funciones de mapeo no generales. – Edmund

+0

si necesita esta forma de abstracción, luego use un módulo C propio. PL/pgSQL no es el lenguaje para la codificación abstracta.Cualquier código no efectivo en el lado de la base de datos puede disminuir significativamente el rendimiento. map_with_args debe ser relativamente simple y se implementa en C –

68

En primer lugar, convertir la matriz en un conjunto utilizando unnest:

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

A continuación, aplicar una expresión a la columna:

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

Por último, utilizar array_agg para convertir el conjunto posterior en una matriz:

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

Y también podría envolverlo en una función 'round (int [], int)' definida por el usuario. –

+5

Esta es la solución más concisa, y me gusta la forma clara en que la construyes. – Edmund

20
postgres=# select array(select round(unnest(array[1.2,2.4,3,4]))); 
    array 
----------- 
{1,2,3,4} 
(1 row) 
+0

La mejor respuesta aquí – sudo

+0

Existen algunas diferencias de rendimiento entre 'array_agg()' (vea la solución de Joey) y 'array()' (esta solución)? –

+1

@PeterKrauss Hice benchmark ahora, y la velocidad es la misma –

Cuestiones relacionadas