Il y a beaucoup Je ferais différemment, et avec grand effet.
Définition du tableau
En commençant par la définition de la table et les conventions de dénomination. Ce ne sont pour la plupart que des opinions :
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
);
Points majeurs
-
Êtes-vous sûr d'avoir besoin d'un
bigserial
pour les départements ? Il n'y en a presque pas beaucoup sur cette planète. Un simpleserial
devrait suffire. -
Je n'utilise presque jamais
character varying
avec une restriction de longueur. Contrairement à certains autres SGBDR, il n'y a aucun gain de performances en utilisant une restriction. Ajouter unCHECK
contrainte si vous avez vraiment besoin d'imposer une longueur maximale. J'utilise simplementtext
, principalement et m'épargner la peine. -
Je suggère une convention de dénomination où la colonne de clé étrangère partage le nom avec la colonne référencée, donc
master_id
au lieu demaster_fk
, etc. Permet également d'utiliserUSING
dans les jointures. -
Et je rarement utilisez le nom de colonne non descriptif
id
. Utilisation dedept_id
à la place ici.
Fonction PL/pgSQL
Il peut être largement simplifié en :
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;
Appel :
SELECT f_retornar_plpgsql(2, 5);
Ou :
SELECT f_retornar_plpgsql(VARIADIC '{2,5}');
-
ALIAS FOR $1
est une syntaxe obsolète et déconseillé . Utilisez plutôt les paramètres de fonction. -
Le
VARIADIC
Le paramètre rend l'appel plus pratique. Connexe : -
Vous n'avez pas besoin de
EXECUTE
pour les requêtes sans éléments dynamiques. Rien à gagner ici. -
Vous n'avez pas besoin de gérer les exceptions pour créer une table. Citant le manuel ici :
-
Postgres 9.1 ou version ultérieure a
CREATE TEMP TABLE IF NOT EXISTS
. J'utilise une solution de contournement pour 9.0 pour créer conditionnellement la table temporaire. -
Postgres 9.1 propose également
FOREACH
parcourir un tableau .
Cela dit, voici la déception :vous n'avez pas besoin de tout cela.
Fonction SQL avec rCTE
Même dans Postgres 9.0, un CTE récursif rend cela beaucoup plus 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;
Même appel.
Réponse étroitement liée avec explication :
SQL Fiddle démontrant les deux.