【问题标题】:iterative depth of tree树的迭代深度
【发布时间】:2012-11-23 15:46:53
【问题描述】:

有问题的代码:

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;

我需要找到 id 为 searchid 的元素的树深度,我编写了一个与上面略有不同的函数,它使用 mid NOTNULL 作为 while 循环的条件,它可以工作。

但是,当我尝试在 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 上运行。

【问题讨论】:

  • 表达式是mid NOTNULL 还是mid NOT NULL?空间很重要。
  • @GordonLinoff 没有空格。
  • 。 .如果您添加空格,我认为这可能会解决您的问题。
  • @GordonLinoff 在哪里添加空间?带有EXISTS 的代码不包含NOTNULL
  • 您忘记提及您的 PostgreSQL 版本。对于程序员来说,这是不言而喻的。最佳解决方案取决于您的版本。

标签: sql postgresql plpgsql


【解决方案1】:

关键错误是您不能在 EXISTS 构造内分配带有 SELECT INTO 的变量。 SELECT 结构中的 EXISTS 项目将被忽略。

我重写了函数以简化并使其更安全:

CREATE OR REPLACE FUNCTION foo(_searchid int, OUT _level int)
  RETURNS int
  LANGUAGE plpgsql STABLE 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$;

呼叫:

SELECT foo(1);

要点

  • 防止循环无限是你的责任。

  • 参数的_前缀是为了避免与使用表的潜在列发生命名冲突。

  • 我使用特殊变量FOUND,它是在某些 SQL 语句(如SELECT INTO)找到一行之后(且仅在之后)设置的。

  • 找不到行时使用EXIT 命令退出循环。

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

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

  • 该函数不应声明为IMMUTABLE,因为它访问的是一个表。我改为STABLE。您可以将函数IMMUTABLE 设置为“作弊”并能够在索引创建中使用它(例如) - 但如果在基础表发生更改后此类索引中断,则由您自己决定。

递归 CTE

使用现代 PostgreSQL,您还可以使用 recursive CTE 来完成这项工作。这就是@a_horse 在his comment 中暗示的内容——哦,他现在发布为answer
另一个例子(SO 上的许多例子)here

【讨论】:

  • 也许SELECT _level + 1, t.id INTO _level, _searchid 更好?您当前的查询确实令人困惑。即使对我来说。
  • @IgorRomanchenko:嗯,这真的是一个品味问题。您可以将INTO 部分放在命令中的任何位置。我总是把它放在第一位,因为这最不容易混淆恕我直言。这样您就可以立即看到它是 SELECT INTO 而不仅仅是 SELECT - 这恰好是 OP 一开始就错过的。
【解决方案2】:

也许您不想编写自己的函数,而是使用带有函数的 postgres 扩展,该函数是为了呈现存储在表中的分层数据而创建的? 它被称为 connectby,它是 tablefunc 扩展的一部分。功能使用方法可以找here.

安装扩展:

 CREATE EXTENSION tablefunc;

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

【讨论】:

    【解决方案3】:

    仅作记录:

    如果您使用的是最新版本的 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-21
      • 2013-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多