树的迭代深度

时间:2012-12-05 18:09:23

标签: sql postgresql plpgsql

有问题的代码:

CREATE OR REPLACE FUNCTION foo(searchid INTEGER)
RETURNS INTEGER AS
$$
DECLARE
    level INTEGER := 0;
    mid INTEGER := searchid;
BEGIN
    WHILE EXISTS(SELECT id INTO mid FROM tbl1 WHERE parent_id=mid) LOOP
        level := level + 1;
    END LOOP;
    RETURN level;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE;

我需要找到标识为searchid的元素的树深度,我写了一个与上面稍微不同的函数,它使用mid NOTNULL作为while循环的条件并且它可以工作。 / p>

但是,当我尝试在WHILE条件中直接使用EXISTS时,如上面发布的代码,postgresql说:

SQL error:

ERROR:  syntax error at or near "$1"
LINE 1: SELECT  EXISTS(SELECT id INTO  $1  FROM tbl1 WHERE ...

因此它对我的代码进行了一些奇怪的转换,使其语法错误。

如何解决?

它在postgresql 8.3.17上运行。

3 个答案:

答案 0 :(得分:2)

仅供记录:

如果您使用的是Postgres的最新版本,则可以使用单个语句更有效地执行此操作:

with recursive tree as (
   select id, parent, 1 as level
   from tbl1
   where id = 1
   union all
   select c.id, c.parent, p.level + 1
   from tbl1 c
     join tree p on c.parent = p.id
)
select max(level)
from tree

答案 1 :(得分:1)

关键错误是您无法在SELECT INTO构造中分配带EXISTS的变量。 <{1}}构造中的SELECT项将被忽略。

我重写了这个函数,通常简化并使其更安全:

EXISTS

呼叫:

CREATE OR REPLACE FUNCTION foo(_searchid int, OUT _level int)
  RETURNS int AS
$func$
BEGIN
   _level := 0;
   LOOP
      SELECT INTO _level, _searchid
                  _level + 1, t.id FROM tbl1 t WHERE t.parent_id = _searchid;
      EXIT WHEN NOT FOUND;
   END LOOP;
END
$func$
LANGUAGE plpgsql STABLE;

重点

  • 你有责任防止循环无限。

  • 参数的SELECT foo(1); 前缀是为了避免与使用过的表的潜在列发生命名冲突。

  • 我在某些SQL语句(如_)找到一行之后(仅在此之后)使用设置为TRUE的特殊变量FOUND

    < / LI>
  • 使用EXIT命令在没有找到行时退出循环。

  • SELECT INTO内增加_level。 (或者在循环体中,它只是一个很小的简化。)

  • 由于PostgreSQL 9.1 ,您可以分配SELECT参数,因此I(ab)使用IN并且不需要DECLARE任何其他变量。如果您稍后需要函数中的原始参数值,请不要这样做。

  • 该函数不应声明_searchid,因为它访问表。我改为IMMUTABLE。你可以使函数STABLE“欺骗”并能够在索引创建中使用它(例如) - 但如果在底层更改后这样的索引中断,那么它就在你身边表

递归CTE

使用现代PostgreSQL,您还可以使用recursive CTE来完成工作。这可能是@a_horse在his comment中暗示的 - 哦,以及他现在发布的answer
一个例子(很多关于SO)here

答案 2 :(得分:0)

也许你不想编写自己的函数,而是想使用postgres扩展来创建用于呈现存储在表中的分层数据的函数? 它叫做connectby,它是tablefunc扩展的一部分。如何使用您可以找到的功能here.

安装扩展程序:

 CREATE EXTENSION tablefunc;

您可以选择许多可能性:要开始的行的键值,要下降的最大深度,无限深度的零值,或者用分支输出分隔键的字符串。