C'è molto Farei diversamente, e con grande efficacia.
Definizione tabella
A partire dalla definizione della tabella e dalle convenzioni di denominazione. Queste sono per lo più solo opinioni:
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
);
Punti principali
-
Sei sicuro di aver bisogno di un
bigserial
per i reparti? Non ce ne sono così tanti su questo pianeta. Un sempliceserial
dovrebbe essere sufficiente. -
Non uso quasi mai
character varying
con un limite di lunghezza. A differenza di altri RDBMS, l'utilizzo di una restrizione non comporta alcun guadagno in termini di prestazioni. Aggiungi unCHECK
vincolo se è davvero necessario imporre una lunghezza massima. Uso solotext
, principalmente e risparmiami la fatica. -
Suggerisco una convenzione di denominazione in cui la colonna della chiave esterna condivide il nome con la colonna di riferimento, quindi
master_id
invece dimaster_fk
, ecc. Permette anche di usareUSING
nei join. -
E io raramente usa il nome di colonna non descrittivo
id
. Usandodept_id
invece qui.
Funzione PL/pgSQL
Può essere ampiamente semplificato in:
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;
Chiama:
SELECT f_retornar_plpgsql(2, 5);
Oppure:
SELECT f_retornar_plpgsql(VARIADIC '{2,5}');
-
ALIAS FOR $1
è una sintassi obsoleta e scoraggiato . Utilizzare invece i parametri della funzione. -
Il
VARIADIC
parametro rende più conveniente chiamare. Correlati: -
Non hai bisogno di
EXECUTE
per query senza elementi dinamici. Niente da guadagnare qui. -
Non è necessaria la gestione delle eccezioni per creare una tabella. Citando il manuale qui :
-
Postgres 9.1 o versioni successive ha
CREATE TEMP TABLE IF NOT EXISTS
. Uso una soluzione alternativa per 9.0 per creare condizionalmente la tabella temporanea. -
Postgres 9.1 offre anche
FOREACH
per scorrere un array .
Detto questo, ecco la pecca:non ti serve la maggior parte di questo.
Funzione SQL con rCTE
Anche in Postgres 9.0, un CTE ricorsivo rende tutto questo molto più semplice :
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;
Stessa chiamata.
Risposta strettamente correlata con spiegazione:
SQL Fiddle che mostra entrambi.