PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

A função de loop não funciona como esperado


muito Eu faria diferente, e com grande efeito.

Definição da tabela


Começando com a definição de tabela e convenções de nomenclatura. Estas são principalmente apenas opiniões:
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
);

Pontos principais


  • Tem certeza de que precisa de um bigserial para departamentos? Quase não há tantos neste planeta. Um serial simples deve bastar.

  • Eu quase nunca uso character varying com restrição de comprimento. Ao contrário de alguns outros RDBMS, não há ganho de desempenho com o uso de uma restrição. Adicione um CHECK restrição se você realmente precisar impor um comprimento máximo. Eu apenas uso text , principalmente e me poupe do problema.

  • Eu sugiro uma convenção de nomenclatura onde a coluna de chave estrangeira compartilha o nome com a coluna referenciada, então master_id em vez de master_fk , etc. Também permite usar USING em junções.

  • E eu raramente use o nome de coluna não descritivo id . Usando dept_id em vez disso aqui.

função PL/pgSQL


Pode ser amplamente simplificado para:
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;

Ligar:
SELECT f_retornar_plpgsql(2, 5);

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

  • ALIAS FOR $1 é uma sintaxe desatualizada e desanimada . Em vez disso, use parâmetros de função.

  • A VARIADIC parâmetro torna mais conveniente para chamar. Relacionado:

  • Você não precisa de EXECUTE para consultas sem elementos dinâmicos. Nada a ganhar aqui.

  • Você não precisa de tratamento de exceção para criar uma tabela. Citando o manual aqui :

  • Postgres 9.1 ou posterior tem CREATE TEMP TABLE IF NOT EXISTS . Eu uso uma solução alternativa para 9.0 para criar condicionalmente a tabela temporária.

  • O Postgres 9.1 também oferece FOREACH para percorrer um array .

Dito tudo isso, aqui vem a chatice:você não precisa mais disso.

Função SQL com rCTE


Mesmo no Postgres 9.0, um CTE recursivo torna isso muito mais simples :
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;

Mesma chamada.

Resposta intimamente relacionada com explicação:

SQL Fiddle demonstrando ambos.