sql >> Base de Datos >  >> RDS >> PostgreSQL

La función de bucle no funciona como se esperaba

Hay mucho Yo lo haría diferente y con gran efecto.

Definición de tabla

Comenzando con la definición de la tabla y las convenciones de nomenclatura. Estas son en su mayoría solo opiniones:

CREATE TEMP TABLE conta (conta_id bigint primary key, ...);

CREATE TEMP TABLE departamento (
   dept_id   serial PRIMARY KEY
 , master_id int REFERENCES departamento (dept_id)
 , conta_id  bigint NOT NULL REFERENCES conta (conta_id)
 , nome      text NOT NULL
);

Puntos principales

  • ¿Está seguro de que necesita un bigserial para departamentos? Apenas hay tantos en este planeta. Un simple serial debería ser suficiente.

  • Casi nunca uso variación de caracteres con una restricción de longitud. A diferencia de otros RDBMS, no hay ganancia alguna en el rendimiento al usar una restricción. Agrega un CHEQUE restricción si realmente necesita imponer una longitud máxima. Solo uso text , principalmente y ahórrate el problema.

  • Sugiero una convención de nomenclatura en la que la columna de clave externa comparte el nombre con la columna a la que se hace referencia, por lo que master_id en lugar de master_fk , etc. También permite usar USING en uniones.

  • Y yo rara vez use el nombre de columna no descriptivo id . Usando dept_id en cambio aquí.

Función PL/pgSQL

Se puede simplificar en gran medida a:

CREATE OR REPLACE FUNCTION f_retornar_plpgsql(lista_ini_depts VARIADIC int[])
  RETURNS int[] AS
$func$
DECLARE
   _row departamento;                     -- %ROWTYPE is just noise
BEGIN

IF NOT EXISTS (                           -- simpler in 9.1+, see below
    SELECT FROM pg_catalog.pg_class
    WHERE  relnamespace = pg_my_temp_schema()
    AND    relname      = 'tbl_temp_dptos') THEN

   CREATE TEMP TABLE tbl_temp_dptos (dept_id bigint NOT NULL)
   ON COMMIT DELETE ROWS;
END IF;

FOR i IN array_lower(lista_ini_depts, 1)  -- simpler in 9.1+, see below
      .. array_upper(lista_ini_depts, 1) LOOP
   SELECT *  INTO _row                    -- since rowtype is defined, * is best
   FROM   departamento
   WHERE  dept_id = lista_ini_depts[i];

   CONTINUE WHEN NOT FOUND;

   INSERT INTO tbl_temp_dptos VALUES (_row.dept_id);

   LOOP
      SELECT *  INTO _row
      FROM   departamento
      WHERE  dept_id = _row.master_id;

      EXIT WHEN NOT FOUND;

      INSERT INTO tbl_temp_dptos
      SELECT _row.dept_id
      WHERE  NOT EXISTS (
         SELECT FROM tbl_temp_dptos
         WHERE dept_id =_row.dept_id);
   END LOOP;
END LOOP;

RETURN ARRAY(SELECT dept_id FROM tbl_temp_dptos);

END
$func$  LANGUAGE plpgsql;

Llamar:

SELECT f_retornar_plpgsql(2, 5);

O:

SELECT f_retornar_plpgsql(VARIADIC '{2,5}');

Dicho todo esto, aquí viene el fastidio:no necesitas la mayor parte de esto.

Función SQL con rCTE

Incluso en Postgres 9.0, un CTE recursivo hace esto mucho más simple :

CREATE OR REPLACE FUNCTION f_retornar_sql(lista_ini_depts VARIADIC int[])
  RETURNS int[] AS
$func$
WITH RECURSIVE cte AS (
   SELECT dept_id, master_id
   FROM   unnest($1) AS t(dept_id)
   JOIN   departamento USING (dept_id)

   UNION ALL
   SELECT d.dept_id, d.master_id
   FROM   cte
   JOIN   departamento d ON d.dept_id = cte.master_id
   )
SELECT ARRAY(SELECT DISTINCT dept_id FROM cte)    -- distinct values
$func$  LANGUAGE sql;

Misma llamada.

Respuesta estrechamente relacionada con explicación:

SQL Fiddle demostrando ambos.