Matrices Multidimensionales con funciones
by Emanuel Calvo Franco, Alvaro Herrera, Carlos Bazán, Miguel Villa Gomez.
Este artículo fue escrito basándome en un thread de las listas de correo, http://archives.postgresql.org/message-id/86D81900A3164F978746F096076C5F53@pc13
Si bien el tema de matrices puede ser sencillo de entender, algunos trucos pueden tenerse en cuenta.
Este código puede resultarle al lector lógico y funcional,
sin embargo al ejecutar esta función se retorna el siguiente
error:
ERROR: invalid array subscripts CONTEXT: PL/pgSQL function "pepe" line 7 at assignment
sin embargo, la compilación no traerá aparejado errores.
CREATE OR REPLACE FUNCTION pepe(tamano integer) RETURNS integer
AS $_$
declare
matriz integer[][];
i integer;
BEGIN
for i in 1..tamano loop
matriz[i][1] = 1;
matriz[i][2] = 2;
end loop;
return 0;
END;
$_$ LANGUAGE plpgsql;
Desde otro lenguaje, como PL/Perl, es mucho más sencillo
y práctico:
CREATE OR REPLACE FUNCTION matriz(integer) returns integer language
'plperl' as $_$
$array = ();
my $numero = 0;
for ($numero;$numero < $_[0]; $numero++){
$array[$numero][1] = 1;
$array[$numero][2] = 2;
}
return 0;
$_$;
Sin embargo, por diversas cuestiones, puede que al lector no le sirva este lenguaje.
El siguiente código lo expuso Miguel Angel Villa Gomez, quién propone crear una función dinámica pasándole como parámetro las dimensiones del array:
CREATE OR REPLACE FUNCTION pepe_master (integer) RETURNS VOID AS $BODY$
DECLARE
nombre_funcion VARCHAR:= 'pepe_child';
notificacion VARCHAR:= $$RAISE NOTICE 'Elemento:%
(%)',elemento,array[elemento];$$;
funcion VARCHAR:=
'
CREATE OR REPLACE FUNCTION ' || nombre_funcion || ' ()
RETURNS VOID AS $$
DECLARE
array INTEGER[' || $1 || '];
elemento INTEGER;
BEGIN
FOR elemento IN 1..' || $1 || ' LOOP
array[elemento]:=elemento;
' || notificacion || '
END LOOP;
END;
$$ LANGUAGE PLPGSQL;
';
garbage VARCHAR;
BEGIN
EXECUTE funcion;
EXECUTE 'SELECT ' || nombre_funcion || '()';
END;
$BODY$ LANGUAGE PLPGSQL;
SELECT pepe_master(10);
Esta otra función fue propuesta como solución por Alvaro Herrera.
CREATE OR REPLACE FUNCTION pepe(tamano integer) RETURNS integer AS $_$
DECLARE
matriz integer[] = '{{1,2}}';
i integer;
BEGIN
for i in 1 .. tamano loop
matriz := matriz || '{1,2}'::integer[];
-- matriz[i] := 1;
RAISE NOTICE 'matriz: %', matriz[i];
end loop;
return matriz[1];
END;
$_$ LANGUAGE plpgsql;
Si la ejecutamos obtendremos el resultado:
ubuntu=# select pepe(8); NOTICE: matriz: NULL NOTICE: matriz: NULL NOTICE: matriz: NULL NOTICE: matriz: NULL NOTICE: matriz: NULL NOTICE: matriz: NULL NOTICE: matriz: NULL NOTICE: matriz: NULL pepe -- (1 row)
¡Tiene sentido! En ningún momento se le asigno valor alguno a ninguna posición del arreglo...
Carlos Bazán propuso:
DECLARE
mi_arreglo TEXT[] := '{}';
BEGIN
...
...
-- y lo alimento con
mi_arreglo := mi_arreglo || ARRAY[[rec.dato1::text, rec.dato2::text]];
...
Obviamente hay que preocuparse de los tipos de datos (entero, texto etc.) dependiendo del tipo de arreglo.
Alvaro, propuso mediante plpgsql la siguiente solución:
CREATE OR REPLACE FUNCTION pepe(tamano integer) RETURNS integer AS $_$
declare
matriz integer[] = '{{1,2}}';
i integer;
begin
for i in 1 .. tamano loop
matriz := matriz || '{1,2}'::integer[];
-- matriz[i] := 1;
RAISE NOTICE 'matriz: % %', matriz[i][1], matriz[i][2];
end loop;
return matriz[1];
end;
$_$ LANGUAGE plpgsql;
O si quieres ver el elemento i-ésimo completo,
RAISE NOTICE 'matriz: %', matriz[i:i][1:2];
(esto se llama un "slice" del array).
Si no sabes el tamaño de la segunda dimensión, puedes usar array_upper() para pedirlo:
RAISE NOTICE 'matriz: %', matriz[i:i][array_upper(matriz, 2)];
Observa que esto es equivalente a:
RAISE NOTICE 'matriz: %', matriz[i:i][1:array_upper(matriz, 2)];
Referenciando al Manual:
si cada dimensión está escrita en partes (slice), por ejemplo: que contenga un :, entonces todas las dimensiones deben ser tratadas igual. Si alguna dimensión tiene un solo número (no :) será tratado como 1 hasta el número especificado.
Normalmente los elementos empiezan de 1, pero puedes tener arreglos raros que empiezan en otra subíndice, en cuyo caso lo que necesitas es array_lower():
RAISE NOTICE 'matriz: %', matriz[i:i][array_lower(matriz, 2):array_upper(matriz, 2)];
Lo cual se puede probar si modificas el literal con que inicializas el array:
CREATE OR REPLACE FUNCTION pepe(tamano integer) RETURNS integer AS $_$
declare
matriz integer[] = '[1:1][-1:0]={{1,2}}';
i integer;
begin
for i in 1 .. tamano loop
matriz := matriz || '[-1:0]={1,2}'::integer[];
-- matriz[i] := 1;
RAISE NOTICE 'matriz: %', matriz[i:i][array_lower(matriz, 2):array_upper(matriz, 2)];
end loop;
raise notice 'matriz: %', matriz;
return matriz[1];
end;
$_$ LANGUAGE plpgsql ;
alvherre=# select pepe(10); NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: {{1,2}} NOTICE: matriz: [1:11][-1:0]={{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2}} pepe -- (1 fila)
Gracias a Todos los que participaron de este thread. ¡Espero que les haya servido!