关于postgresql:是否可以进行递归SQL查询?

关于postgresql:是否可以进行递归SQL查询?

Is it possible to make a recursive SQL query?

我有一张与此类似的表:

1
2
3
4
5
CREATE TABLE example (
  id INTEGER PRIMARY KEY,
  name CHAR(200),
  parentid INTEGER,
  VALUE INTEGER);

我可以使用parentid字段将数据排列成树状结构。

现在这是我无法解决的问题。给定一个parentid,是否有可能编写一条SQL语句来添加该parentid下的所有值字段并向下递归该树的分支?

更新:我正在使用posgreSQL,因此无法使用精美的MS-SQL功能。无论如何,我希望将其视为通用SQL问题。

顺便说一句,我很高兴在提出问题的15分钟内能得到6个答案!去堆栈溢出!


以下是使用公用表表达式的示例脚本:

1
2
3
4
5
6
7
8
9
10
WITH recursive sumthis(id, val) AS (
    SELECT id, VALUE
    FROM example
    WHERE id = :selectedid
    UNION ALL
    SELECT C.id, C.value
    FROM sumthis P
    INNER JOIN example C ON P.id = C.parentid
)
SELECT SUM(val) FROM sumthis

上面的脚本创建一个名为sumthis的"虚拟"表,该表具有列idval。它定义为与union all合并的两个选择的结果。

第一个select获得根(where id = :selectedid)。

第二个select迭代地跟踪先前结果的子级,直到没有返回值为止。

然后可以像正常表一样处理最终结果。在这种情况下,将对val列求和。


从8.4版开始,PostgreSQL使用SQL标准WITH语法对通用表表达式提供了递归查询支持。


如果您想使用可在任何ANSI SQL-92 RDBMS上使用的便携式解决方案,则需要在表中添加一个新列。

Joe Celko是嵌套集方法在SQL中存储层次结构的原始作者。您可以使用Google"嵌套集"层次结构来了解有关背景的更多信息。

或者您可以将parentid重命名为leftid并添加一个rightid。

这是我尝试对嵌套集进行总结,因为我不是Joe Celko,嵌套集将严重不足:SQL是基于集合的语言,而邻接模型(存储父ID)不是基于集合的表示形式层次结构。因此,没有用于查询邻接模式的纯基于集合的方法。

但是,近年来,大多数主要平台都引入了扩展,以解决这一精确问题。因此,如果有人回复了Postgres特定的解决方案,请务必使用它。


在PostgreSQL中有几种方法可以满足您的需求。

  • 如果可以安装模块,请查看tablefunc组件。它具有一个处理遍历树的connectby()函数。 http://www.postgresql.org/docs/8.3/interactive/tablefunc.html

  • 还要检查ltree贡献,您可以修改表以使用:http://www.postgresql.org/docs/8.3/interactive/ltree.html

  • 或者您可以使用PL / PGSQL函数自己遍历树。

类似这样的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE OR REPLACE FUNCTION example_subtree (INTEGER)
RETURNS setof example AS
'declare results record;
         child record;
 begin
  select into results * from example where parent_id = $1;
  if found then
    return next results;
    for child in select id from example
                  where parent_id = $1
      loop
        for temp in select * from example_subtree(child.id)
        loop
          return next temp;
        end loop;
      end loop;
  end if;
  return null;
end;'
LANGUAGE 'plpgsql';

SELECT SUM(VALUE) AS value_sum
  FROM example_subtree(1234);

SQL中进行递归查询的标准方法是递归CTE。从8.4开始,PostgreSQL支持它们。

在早期版本中,您可以编写递归集返回函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE FUNCTION fn_hierarchy (parent INT)
RETURNS SETOF example
AS
$$
        SELECT  example
        FROM    example
        WHERE   id = $1
        UNION ALL
        SELECT  fn_hierarchy(id)
        FROM    example
        WHERE   parentid = $1
$$
LANGUAGE 'sql';

SELECT  *
FROM    fn_hierarchy(1)

查看本文:

  • PostgreSQL中的分层查询

如果您使用的是SQL Server 2005,则可以使用"公用表表达式"来实现此目的。

从创建临时表中消除了所有繁琐的工作,并且基本上允许您仅使用WITH和UNION来完成所有工作。

这是一个很好的教程:

http://searchwindevelopment.techtarget.com/tip/0,289483,sid8_gci1278207,00.html


使用公用表表达式。

May want to indicate this is SQL Server 2005 or above only. Dale Ragan

这是有关SqlTeam没有通用表表达式的递归的文章。


下面的代码可以编译,并且已经过测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE OR REPLACE FUNCTION subtree (BIGINT)
RETURNS setof example AS $$
DECLARE
    results record;
    entry   record;
    recs    record;
BEGIN
    SELECT INTO results * FROM example WHERE parent = $1;
    IF found THEN
        FOR entry IN SELECT child FROM example WHERE parent = $1 AND child  parent loop
            FOR recs IN SELECT * FROM subtree(entry.child) loop
                RETURN NEXT recs;
            END loop;
        END loop;
    END IF;
    RETURN NEXT results;
END;
$$ LANGUAGE 'plpgsql';

在我的情况下,条件"子代<>父代"是必需的,因为节点指向自己。

玩得开心:)


这些示例都无法为我工作,因此我已将其修复为:

1
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE
    results record;
    entry   record;
    recs    record;
BEGIN
    FOR results IN SELECT * FROM project WHERE pid = $1 loop
        RETURN NEXT results;
        FOR recs IN SELECT * FROM project_subtree(results.id) loop
            RETURN NEXT recs;
        END loop;
    END loop;
    RETURN;
END;

尽管问题已得到很好的回答,但作为简短说明,应该注意的是,如果我们将其视为:

generic SQL question

然后,SQL实现相当简单,因为SQL'99允许通过WITH RECURSIVE语句在规范中进行线性递归(尽管我相信没有RDBMS会完全实现该标准)。因此,从理论上讲,我们现在可以执行此操作。


Oracle具有" START WITH"和" CONNECT BY"

1
2
3
4
5
6
7
8
SELECT
    lpad(' ',2*(level-1)) || to_char(child) s

FROM
    test_connect_by

START WITH parent IS NULL
CONNECT BY prior child = parent;

http://www.adp-gmbh.ch/ora/sql/connect_by.html


我认为使用HierarchyID

在SQL 2008中更容易


这是SQL Server吗?您是否可以编写一个TSQL存储过程来循环并将结果合并在一起?

我也很感兴趣是否有一种仅使用SQL的方式来执行此操作。从我记得的地理数据库类的资料来看,应该有。


如果您需要存储任意图形,而不仅仅是层次结构,则可以将Postgres推到一边,然后尝试使用诸如AllegroGraph:

的图形数据库

图形数据库中的所有内容都存储为三元组(源节点,边,目标节点),它为您提供了一流的支持,可操纵图形结构并使用类似SQL的语言对其进行查询。

它不能与Hibernate或Django ORM之类的东西很好地集成在一起,但是如果您对图结构很认真(不仅像Nested Set模型这样的层次结构会给您),请检查一下它。

我也相信Oracle终于在其最新产品中增加了对真实图的支持,但是我很惊讶它花了这么长时间,该模型可能会带来很多问题。


推荐阅读