循环功能无法按预期工作

时间:2013-10-08 20:11:49

标签: sql postgresql loops plpgsql common-table-expression

使用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 []指的是临时表中记录的内容。

我的问题是该函数返回了我提供的相同列表。我做错了什么?

2 个答案:

答案 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
);

重点

  • 您确定需要部门bigserial吗?这个星球上几乎没有那么多人。普通serial就足够了。

  • 我几乎没有使用长度限制的character varying。与其他一些RDBMS不同,使用限制没有任何性能提升。如果确实需要强制执行最大长度,请添加CHECK约束。我只使用text,主要是and save myself the trouble.

  • 我建议使用命名约定,其中外键列与引用列共享名称,因此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语法已过时且discouraged。请改用函数参数。

  • VARIADIC 参数可让您更方便地致电。相关:

  • 对于没有动态元素的查询,您不需要EXECUTE。这里无济于事。

  • 您无需异常处理即可创建表格。引用手册here

      

    提示:包含EXCEPTION子句的块明显更多   进入和退出比没有一个的块贵。所以,不要   

  • Postgres 9.1或更高版本有CREATE TEMP TABLE IF NOT EXISTS。我使用9.0的解决方法来有条件地创建临时表。

  • Postgres 9.1还提供FOREACH to loop through an arrays

所有这一切都说明了这一点:“你不需要大部分内容。

使用rCTE

的SQL函数

即使在Postgres 9.0中,recursive CTE使更加简单

EXCEPTION

同一个电话。

与解释密切相关的答案:

SQL Fiddle demonstrating both.

答案 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
相关问题