【问题标题】:PostgreSQL - tree organizationPostgreSQL - 树组织
【发布时间】:2009-02-25 10:24:10
【问题描述】:

我正在做一个项目,该项目需要一个类别树,按 id、父级、标题表组织。在 Postgres 中检索类别及其子类别(以及完整的树,如果根类别的 parent=0)的最佳方法是什么?我正在寻找一个纯粹的数据库解决方案,但如果有 Ruby 和 PHP 的方法 - 它也会很棒。

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

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

【问题讨论】:

    标签: php ruby postgresql search tree


    【解决方案1】:

    检索类别及其子类别

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

    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 在“父 ID 外键”类型架构上执行此操作。

    一些 DBMS 提供了非标准的分层工具,允许以各种方式进行此类操作,但如果您想坚持使用跨 DBMS 兼容的代码,则需要将架构重新调整为更好的表示层次结构的模型之一.两个流行的是:

    • Nested Set。将表示树的深度优先搜索的线性排序存储在目标表的两列中(如果您的目标具有显式排序,您将已经拥有其中一列)。

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

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

    【讨论】:

    • 可以在“parent-id-foreign-key”类型架构上使用标准 SQL 为任意深度的层次结构执行此操作:您可以使用递归公用表表达式.不可否认,PostgreSQL 仅从 8.4 开始支持这一点,该版本在该线程数月后发布,但我认为这可能是一个有用的附录。 FWIW、Firebird、MS SQL Server 和 DB2 也支持递归 CTE,尽管 MSSQL 的版本有限。 Oracle 有自己奇怪的语法。
    【解决方案2】:

    看看"ltree" contrib 模块。

    【讨论】:

      【解决方案3】:

      我一直在玩 ltree,这是一个 PostgreSQL contrib 模块,看看它是否适合线程化 cmets。您在表中创建一个列来存储路径并在其上创建一个 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;
      

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

      【讨论】:

        【解决方案4】:

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

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

        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 子句还可以为您提供一些关于您的树的快速统计信息。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-02-06
              • 2018-07-02
              • 1970-01-01
              相关资源
              最近更新 更多