Има много Бих направил различно и то с голям ефект.
Дефиниция на таблица
Започвайки с дефиницията на таблицата и конвенциите за именуване. Това са предимно само мнения:
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 демонстрира и двете.