Der er meget Jeg ville gøre anderledes, og med stor effekt.
Tabeldefinition
Startende med tabeldefinitionen og navngivningskonventioner. Disse er for det meste kun meninger:
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
);
Vigtige punkter
-
Er du sikker på, at du har brug for en
bigserial
for afdelinger? Der er knap så mange på denne planet. En almindeligserial
burde være tilstrækkeligt. -
Jeg bruger næsten aldrig
character varying
med en længdebegrænsning. I modsætning til nogle andre RDBMS er der ingen præstationsgevinst overhovedet ved at bruge en begrænsning. Tilføj enCHECK
begrænsning, hvis du virkelig har brug for at håndhæve en maksimal længde. Jeg bruger baretext
, for det meste og spar mig selv for besværet. -
Jeg foreslår en navnekonvention, hvor fremmednøglekolonnen deler navnet med den refererede kolonne, så
master_id
i stedet formaster_fk
, osv. Tillader også at brugeUSING
i slutter sig. -
Og jeg sjældent brug det ikke-beskrivende kolonnenavn
id
. Brugerdept_id
i stedet her.
PL/pgSQL-funktion
Det kan stort set forenkles til:
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;
Ring til:
SELECT f_retornar_plpgsql(2, 5);
Eller:
SELECT f_retornar_plpgsql(VARIADIC '{2,5}');
-
ALIAS FOR $1
er forældet syntaks og frarådes . Brug funktionsparametre i stedet. -
VARIADIC
parameter gør det mere bekvemt at ringe. Relateret: -
Du behøver ikke
EXECUTE
for forespørgsler uden dynamiske elementer. Intet at vinde her. -
Du behøver ikke håndtering af undtagelser for at oprette en tabel. Citerer manualen her :
-
Postgres 9.1 eller nyere har
CREATE TEMP TABLE IF NOT EXISTS
. Jeg bruger en løsning til 9.0 til betinget at oprette temp-tabellen. -
Postgres 9.1 tilbyder også
FOREACH
at sløjfe gennem et array .
Når det er sagt, her kommer nederdelen:du har ikke brug for det meste af dette.
SQL-funktion med rCTE
Selv i Postgres 9.0, en rekursiv CTE gør dette en hel del enklere :
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;
Samme opkald.
Nært beslægtet svar med forklaring:
SQL Fiddle demonstrerer begge dele.