Há 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 umbigserial
para departamentos? Quase não há tantos neste planeta. Umserial
simples deve bastar.
-
Eu quase nunca usocharacter 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 umCHECK
restrição se você realmente precisar impor um comprimento máximo. Eu apenas usotext
, 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ãomaster_id
em vez demaster_fk
, etc. Também permite usarUSING
em junções.
-
E eu raramente use o nome de coluna não descritivoid
. Usandodept_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.
-
AVARIADIC
parâmetro torna mais conveniente para chamar. Relacionado:
-
Você não precisa deEXECUTE
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 temCREATE 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 ofereceFOREACH
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.