PostgreSQL - 树组织

时间:2009-02-25 10:24:10

标签: php ruby postgresql search tree

我正在开发一个项目,需要一个类别树,组织为id,parent,title table。在Postgres中哪些是检索类别及其子类别(以及完整树,如果根类别具有parent = 0)的最佳方法?我正在寻找一个纯数据库解决方案,但如果有一种方法可以用于Ruby和PHP - 它也会很棒。

主要目标是选择子句的速度,因为此表中的数据对于更新/插入/删除速度并不重要。

UPD:还会有路径搜索,我的意思是从当前顶点(类别)到根类别的路径。

6 个答案:

答案 0 :(得分:3)

看看"ltree" contrib模块。

答案 1 :(得分:2)

  

检索类别及其子类别

如果您只有有限的子项目深度,您可以使用自联接来完成此操作,例如。两层深:

SELECT *
FROM categories AS child
LEFT JOIN categories AS parent ON parent.id=child.parent
LEFT JOIN categories AS grandparent ON grandparent.id=parent.parent
WHERE child.id=(id) OR parent.id=(id) OR grandparent.id=(id);

对于使用标准SQL而不是'parent-id-foreign-key'类型架构的任意深度层次结构,您不能这样做。

有些DBMS提供了非标准的分层工具,可以通过各种方式允许这样的东西,但是如果你想坚持使用跨DBMS兼容的代码,你需要将你的模式重新设置为表示层次结构的更好模型之一。两个受欢迎的是:

  • Nested Set。存储一个线性排序,表示目标表的两列中树的深度优先搜索(如果目标有明确排序,则其中一个已经存在)。

  • Adjacency Relation。将每个祖先/后代对存储在单独的连接表中。

每种方法都有优点和缺点,并且有许多变体(例如,稀疏嵌套集编号,AR中的“距离”),这可能会影响各种类型的添加/删除/移动位置操作的成本。就个人而言,我倾向于倾向于简化嵌套集模型,因为它包含的冗余少于AR。

答案 2 :(得分:2)

我一直在玩 ltree ,这是一个PostgreSQL contrib模块,看它是否适合线程评论。您在表中创建一个存储路径并在其上创建ltree索引的列。然后您可以执行以下查询:

 ltreetest=# select path from test where path ~ '*.Astronomy.*';
                     path                      
-----------------------------------------------
 Top.Science.Astronomy
 Top.Science.Astronomy.Astrophysics
 Top.Science.Astronomy.Cosmology
 Top.Collections.Pictures.Astronomy
 Top.Collections.Pictures.Astronomy.Stars
 Top.Collections.Pictures.Astronomy.Galaxies
 Top.Collections.Pictures.Astronomy.Astronauts

我还没有充分利用它来确定它在插入,更新或删除等方面的表现。我假设删除看起来像:

DELETE FROM test WHERE path ~ '*.Astronomy.*';

我在想,一个线程评论表可能看起来像:

CREATE SEQUENCE comment_id_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 78616
  CACHE 1;

CREATE TABLE comments (
comment_id int PRIMARY KEY,
path ltree,
comment text
);

CREATE INDEX comments_path_idx ON comments USING gist (path);

插入粗略(并且未经测试)看起来像:

CREATE FUNCTION busted_add_comment(text the_comment, int parent_comment_id) RETURNS void AS
$BODY$
DECLARE
    INT _new_comment_id; -- our new comment_id
    TEXT _parent_path;   -- the parent path
BEGIN
    _new_comment_id := nextval('comment_id_seq'::regclass);
    SELECT path INTO _parent_path FROM comments WHERE comment_id = parent_comment_id;

    -- this is probably busted SQL, but you get the idea... this comment's path looks like
    --   the.parent.path.US
    --
    -- eg (if parent_comment_id was 5 and our new comment_id is 43):
    --  3.5.43
    INSERT INTO comments (comment_id, comment, path) VALUES (_new_comment_id, the_comment, CONCAT(_parent_path, '.', _new_comment_id));

END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;

或者其他什么。基本上,路径只是由所有主键组成的层次结构。

答案 3 :(得分:1)

我喜欢这种情况的嵌套集模型。更新和插入可能有点棘手,但选择通常非常简洁和快速。如果向节点的父节点添加实际引用,性能可能会更好(在某些情况下它会消除连接。它还包括对子节点的自然排序。

当前节点和所有子节点的典型查询如下所示:

select name
from nestedSet c inner join nestedSet p ON c.lft BETWEEN p.lft AND p.rgt
where p.id = 1
order by lft

一些好的group by条款也会为你提供一些关于你的树的快速统计数据。

答案 4 :(得分:0)

Rails有一个acts_as_tree插件,过去对我来说效果很好。不过,我有一棵相当小的树 - 大约15,000个节点。

答案 5 :(得分:0)

只需补充一点,文章Managing Hierarchical Data in MySQL对邻接列表模型和嵌套集模型有很好的解释,包括用于树操作的示例SQL等。

RDBMS中的层次结构是一个难题。我的愿望清单上有Joe Celko’s Trees and Hierarchies in SQL for Smarties可以在某一天购买和阅读。