使用PostgreSQL 9.0.4
下面是我的表格非常相似的结构:
CREATE TABLE departamento
(
id bigserial NOT NULL,
master_fk bigint,
nome character varying(100) NOT NULL
CONSTRAINT departamento_pkey PRIMARY KEY (id),
CONSTRAINT departamento_master_fk_fkey FOREIGN KEY (master_fk)
REFERENCES departamento (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
我创建的功能:
CREATE OR REPLACE FUNCTION fn_retornar_dptos_ate_raiz(bigint[])
RETURNS bigint[] AS
$BODY$
DECLARE
lista_ini_dptos ALIAS FOR $1;
dp_row departamento%ROWTYPE;
dpto bigint;
retorno_dptos bigint[];
BEGIN
BEGIN
PERFORM id FROM tbl_temp_dptos;
EXCEPTION
WHEN undefined_table THEN
EXECUTE 'CREATE TEMPORARY TABLE tbl_temp_dptos (id bigint NOT NULL) ON COMMIT DELETE ROWS';
END;
FOR i IN array_lower(lista_ini_dptos, 1)..array_upper(lista_ini_dptos, 1) LOOP
SELECT id, master_fk INTO dp_row FROM departamento WHERE id=lista_ini_dptos[i];
IF dp_row.id IS NOT NULL THEN
EXECUTE 'INSERT INTO tbl_temp_dptos VALUES ($1)' USING dp_row.id;
WHILE dp_row.master_fk IS NOT NULL LOOP
dpto := dp_row.master_fk;
SELECT id, master_fk INTO dp_row FROM departamento WHERE id=lista_ini_dptos[i];
EXECUTE 'INSERT INTO tbl_temp_dptos VALUES ($1)' USING dp_row.id;
END LOOP;
END IF;
END LOOP;
RETURN ARRAY(SELECT id FROM tbl_temp_dptos);
END;
$BODY$
LANGUAGE plpgsql VOLATILE
关于我可以翻译的名字的任何问题..
这个功能有什么想法?我首先检查临时表是否已经存在(执行),并且当异常发生时,我创建一个临时表。
然后我获取数组中的每个元素并使用它来获取部门的id和master_fk。如果搜索成功(检查id是否为null,甚至是不必要的)我将id插入临时表并开始一个新的循环。
第二个循环旨在通过执行前面的步骤(即选择一个部门并将其插入临时表)来获取该部门的所有父母。(/ p>
在第二个循环结束时返回第一个循环。当这个结束时我返回bigint []指的是临时表中记录的内容。
我的问题是该函数返回了我提供的相同列表。我做错了什么?
答案 0 :(得分:2)
很多我会做不同的事情,效果很好。
从表定义和命名约定开始。这些大多只是意见:
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
);
我几乎没有使用长度限制的character varying
。与其他一些RDBMS不同,使用限制没有任何性能提升。如果确实需要强制执行最大长度,请添加CHECK
约束。我只使用text
,主要是and save myself the trouble.
我建议使用命名约定,其中外键列与引用列共享名称,因此master_id
代替master_fk
等。也允许使用USING
联接。
我很少使用非描述性列名id
。在此处使用dept_id
。
它可以在很大程度上简化为:
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
语法已过时且discouraged。请改用函数参数。
VARIADIC
参数可让您更方便地致电。相关:
对于没有动态元素的查询,您不需要EXECUTE
。这里无济于事。
您无需异常处理即可创建表格。引用手册here:
提示:包含
EXCEPTION
子句的块明显更多 进入和退出比没有一个的块贵。所以,不要 1}}无需使用。
Postgres 9.1或更高版本有CREATE TEMP TABLE IF NOT EXISTS
。我使用9.0的解决方法来有条件地创建临时表。
Postgres 9.1还提供FOREACH
to loop through an arrays。
所有这一切都说明了这一点:“你不需要大部分内容。
即使在Postgres 9.0中,recursive CTE使更加简单:
EXCEPTION
同一个电话。
与解释密切相关的答案:
答案 1 :(得分:0)
我设法修复了我的代码。在此回复的最后是最终形式,但如果您有任何改进建议,欢迎。以下是更改:
1 - 我提供了我桌子的基本结构,但实际上它要大得多。在master_fk字段之前,有一个名为account_fk的字段,由于变量部门dp_row%**ROWTYPE**
,我的表的整个结构被复制到变量,所以如果我只填充前两个字段,即id和account_fk,那么第三个字段的master_fk将为null。
2 - @Nicolas是对的,我最终使用变量dpto进行第二次循环。而且我忘了在循环中填充它。除了在循环内完成的搜索中使用它。
3 - 我添加了一个if语句,以确保临时表中没有重复项。
我桌子结构的更正:
CREATE TABLE departamento
(
id bigserial NOT NULL,
account_fk bigint NOT NULL,
master_fk bigint,
nome character varying(100) NOT NULL,
CONSTRAINT departamento_pkey PRIMARY KEY (id),
CONSTRAINT departamento_account_fk_fkey FOREIGN KEY (account_fk)
REFERENCES conta (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT departamento_master_fk_fkey FOREIGN KEY (master_fk)
REFERENCES departamento (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
我现在的功能是:
CREATE OR REPLACE FUNCTION fn_retornar_dptos_ate_raiz(bigint[]) RETURNS bigint[] AS
$BODY$
DECLARE
lista_ini_dptos ALIAS FOR $1;
dp_row departamento%ROWTYPE;
dpto bigint;
BEGIN
BEGIN
PERFORM id FROM tbl_temp_dptos;
EXCEPTION
WHEN undefined_table THEN
EXECUTE 'CREATE TEMPORARY TABLE tbl_temp_dptos (id bigint NOT NULL) ON COMMIT DELETE ROWS';
END;
FOR i IN array_lower(lista_ini_dptos, 1)..array_upper(lista_ini_dptos, 1) LOOP
SELECT id, conta_fk, master_fk INTO dp_row FROM departamento WHERE id=lista_ini_dptos[i];
EXECUTE 'INSERT INTO tbl_temp_dptos VALUES ($1)' USING dp_row.id;
dpto := dp_row.master_fk;
-- RAISE NOTICE 'dp_row: (%); ', dp_row.master_fk;
WHILE dpto IS NOT NULL LOOP
SELECT id, conta_fk, master_fk INTO dp_row FROM departamento WHERE id=dpto;
IF NOT(select exists(select 1 from tbl_temp_dptos where id=dp_row.id limit 1)) THEN
EXECUTE 'INSERT INTO tbl_temp_dptos VALUES ($1)' USING dp_row.id;
END IF;
dpto := dp_row.master_fk;
-- RAISE NOTICE 'dp_row: (%); ', dp_row.master_fk;
END LOOP;
END LOOP;
RETURN ARRAY(SELECT id FROM tbl_temp_dptos);
END;
$BODY$
LANGUAGE plpgsql VOLATILE