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!
Category:Español