Matrices Multidimensionales con funciones

From PostgreSQL wiki
Jump to navigationJump to search

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!