Има много Бих направил различно и то с голям ефект.
Дефиниция на таблица
Започвайки с дефиницията на таблицата и конвенциите за именуване. Това са предимно само мнения:
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
);
Основни точки
-
Сигурни ли сте, че имате нужда от
bigserialза отдели? Едва ли има толкова много на тази планета. Обикновенserialби трябвало да е достатъчно. -
Почти никога не използвам
character varyingс ограничение на дължината. За разлика от някои други RDBMS, няма никакво увеличение на производителността чрез използване на ограничение. ДобаветеCHECKограничение, ако наистина трябва да наложите максимална дължина. Използвам самоtext, най-вече и си спестявам неприятностите. -
Предлагам конвенция за именуване, при която колоната с външен ключ споделя името с препратената колона, така че
master_idвместоmaster_fk, и т.н. Също така позволява да се използваUSINGв обединения. -
И аз рядко използвайте неописателното име на колона
id. Използване наdept_idвместо тук.
PL/pgSQL функция
Може да се опрости до голяма степен до:
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;
Обаждане:
SELECT f_retornar_plpgsql(2, 5);
Или:
SELECT f_retornar_plpgsql(VARIADIC '{2,5}');
-
ALIAS FOR $1е остарял синтаксис и не се препоръчва . Вместо това използвайте функционални параметри. -
VARIADICпараметърът го прави по-удобен за извикване. Свързани: -
Нямате нужда от
EXECUTEза заявки без динамични елементи. Тук няма какво да спечелите. -
Не се нуждаете от обработка на изключения, за да създадете таблица. Цитирайки ръководството тук :
-
Postgres 9.1 или по-нова версия има
CREATE TEMP TABLE IF NOT EXISTS. Използвам заобиколно решение за 9.0, за да създам условно временната таблица. -
Postgres 9.1 също предлага
FOREACHза преминаване през масиви .
Всичко казано дотук идва неприятното:повечето от това не ви трябва.
SQL функция с rCTE
Дори в Postgres 9.0 рекурсивен CTE прави това много по-лесно :
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;
Същото обаждане.
Тясно свързан отговор с обяснение:
SQL Fiddle демонстрира и двете.