2011-12-21 8 views
28

Una query Pg restituisce una matrice. Vorrei recuperarlo con ogni elemento formattato a 3 posizioni decimali. Come posso applicare una funzione a ciascun elemento di un array? Qualcosa di simile a quanto segue (sbagliato, ovviamente) -Come applicare una funzione a ciascun elemento di una colonna di array in Postgres?

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

Credo che sto cercando qualcosa di simile funzione di Perl map.

+0

fantastici suggerimenti da parte di tutti. Penso che andrò con il proc memorizzato in quanto ho bisogno di applicare questo tipo di una funzione 'mappa' tutto il tempo. Sarebbe ancora più grande se potessi passare una funzione al processo memorizzato, rendendolo quindi un processo memorizzato in fabbrica che lo convertirà in una vera funzione 'map'. Ma questo funzionerà per ora. Grazie ancora, a tutti. – punkish

+0

Ri: passare in una funzione: potresti essere interessato a http://stackoverflow.com/questions/8346065/funzionamento-come-parametro-per-un'altra-funzione-in-postgres. (È lontano dall'ideale, ma potresti farne un uso.) – ruakh

risposta

8

Potrebbe essere necessario creare una funzione memorizzata. Ecco quello che fa quello che vi serve:

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; 

quindi chiamare qualcosa di simile:

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

È necessario attivare l'array in un set di fila. Ad esempio, utilizzando generate_series:

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

So che è piuttosto brutto. Ci dovrebbe essere una funzione di supporto per rendere più semplici tali mappature.

Forse qualcosa di simile (sì, è orribile, lento, e fragile codice dinamico):

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} 

Aggiornamento mi viene in mente che potremmo usare un unico prospetto dinamica per l'intero ciclo. Questo potrebbe mitigare alcuni dei problemi di prestazioni.

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

Dynamic SQL inside loop è un'idea davvero terribile. Non farlo in plpgsql. Questo è un linguaggio statico relativo e i pattern di perl, python o altri linguaggi di scripting non possono essere applicati qui. –

+1

Sì, è quello che dice la mia frase prima del codice. Ho fatto ricorso ad esso in questo esempio esplicitamente estroso perché voglio passare la funzione per essere chiamata in plpgsql. Questa è un'astrazione fondamentale nella programmazione e una cosa utile da poter fare in qualsiasi lingua. Ovviamente è un dolore che dobbiamo fare come una stringa e quindi costruire una nuova istruzione in modo dinamico, ma è qualcosa che deve essere preso in considerazione se si desidera scrivere una funzione di mappatura generale. L'alternativa è quella di dover scrivere 100 funzioni di mapping non generale. – Edmund

+0

se hai bisogno di questa forma di astrazione, quindi usa un proprio modulo C. PL/pgSQL non è un linguaggio per la codifica astratta.Qualsiasi codice non efficace sul lato del database può ridurre significativamente le prestazioni. map_with_args dovrebbe essere relativo semplicemente implementato in C –

68

Innanzitutto, ruotare la matrice in un apparecchio con unnest:

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

Quindi, applicare un'espressione alla colonna:

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

Infine, utilizzare array_agg girare la arretrato in un array:

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

E lo si può racchiudere in una funzione 'round (int [], int)' definita dall'utente. –

+5

Questa è la soluzione più concisa e mi piace il modo chiaro in cui la costruisci. – Edmund

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

Risposta migliore qui – sudo

+0

Ci sono alcune differenze di prestazioni tra 'array_agg()' (vedi la soluzione di Joey) e 'array()' (questa soluzione)? –

+1

@PeterKrauss Ho fatto benchmark ora, e la velocità è la stessa –

Problemi correlati