2

使用 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插入临时表并开始一个新的循环。

第二个循环旨在获取先前通过执行先前步骤找到的该部门的所有父级(即,选择一个部门并将其插入临时表中)。

在第二个循环结束时返回第一个。当这个结束时,我返回 bigint[] 指的是临时表中记录的内容。

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

4

2 回答 2

3

我会做很多不同的事情,而且效果很好。

表定义

从表定义和命名约定开始。这些大多只是意见:

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没有动态元素的查询。在这里没有任何收获。

  • 您不需要异常处理来创建表。在这里引用手册:

    提示:包含EXCEPTION子句的块的进入和退出成本明显高于没有子句的块。因此,不要在EXCEPTION没有必要的情况下使用。

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

  • Postgres 9.1 还提供FOREACH循环遍历数组

话虽如此,但令人遗憾的是:你不需要大部分。

带有 rCTE 的 SQL 函数

即使在 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 演示了两者。

于 2013-10-11T02:48:52.197 回答
0

我设法修复了我的代码。此回复的最后是其最终形式,但如果您有任何改进建议,欢迎您提出。以下是更改:

1 - 我已经提供了我的桌子的基本结构,但实际上它要大得多。在master_fk字段之前,有一个字段叫account_fk,由于变量department dp_row%**ROWTYPE**,我的表的整个结构都被复制到变量中,所以如果我只填写前两个字段,即id和account_fk,那么master_fk就是第三个字段将为空。

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
于 2013-10-09T12:03:43.460 回答