Sql 在关系数据库中存储分层数据的选项有哪些?

Sql 在关系数据库中存储分层数据的选项有哪些?,sql,database,tree,relational-database,hierarchical-data,Sql,Database,Tree,Relational Database,Hierarchical Data,好的概述 一般来说,您要在快速读取时间(例如,嵌套集)或快速写入时间(邻接列表)之间做出决定。通常,您最终会得到以下最适合您需要的选项组合。以下内容提供了一些深入阅读: :我找到的邻接列表、物化路径、嵌套集和嵌套间隔的最佳比较 :幻灯片,很好地解释了权衡和示例用法 :特别是嵌套集的概述非常好 :我见过的最全面、最有条理的链接集,但在解释方面没有太多 选项 我知道的和一般特征: : 列:ID,ParentID 易于实现 廉价节点移动、插入和删除 查找级别、祖先和后代、路径非常昂贵 避免在支持

好的概述

一般来说,您要在快速读取时间(例如,嵌套集)或快速写入时间(邻接列表)之间做出决定。通常,您最终会得到以下最适合您需要的选项组合。以下内容提供了一些深入阅读:

  • :我找到的邻接列表、物化路径、嵌套集和嵌套间隔的最佳比较
  • :幻灯片,很好地解释了权衡和示例用法
  • :特别是嵌套集的概述非常好
  • :我见过的最全面、最有条理的链接集,但在解释方面没有太多
选项

我知道的和一般特征:

  • :
    • 列:ID,ParentID
    • 易于实现
    • 廉价节点移动、插入和删除
    • 查找级别、祖先和后代、路径非常昂贵
    • 避免在支持N+1的数据库中通过
  • (又名)
    • 列:左、右
    • 廉价的祖先、后代
    • 非常昂贵的
      O(n/2)
      由于易失性编码而移动、插入和删除
  • (又名)
    • 使用单独的联接表:祖先、后代、深度(可选)
    • 廉价的祖先和后代
    • 写入插入、更新和删除的成本
      O(日志n)
      (子树大小)
    • 规范化编码:适用于联接中的RDBMS统计和查询计划器
    • 每个节点需要多行
  • (又称路径枚举)
    • 列:血统(例如/父/子/孙/等)
    • 通过前缀查询的廉价后代(例如
      左(沿袭,#)='/enumerated/path'
    • 写入插入、更新和删除的成本
      O(日志n)
      (子树大小)
    • 非关系:依赖于数组数据类型或序列化字符串格式
    • 与嵌套集类似,但使用实数/浮点数/十进制数,因此编码不会易变(便宜的移动/插入/删除)
    • 存在实/浮/十进制表示/精度问题
    • 为“free”添加祖先编码(物化路径),但增加了线性代数的技巧
    • 一种修改的邻接列表,在每条记录中添加一个级别和等级(如排序)列
    • 重复/分页的成本很低
    • 昂贵的移动和删除
    • 好用:线程化讨论-论坛/博客评论
    • 列:每个沿袭级别对应一列,表示到根的所有父级,从项目级别向下的级别设置为NULL
    • 廉价的祖先、后代、等级
    • 廉价的插入、删除、移动叶子
    • 昂贵的内部节点插入、删除和移动
    • 硬限制层次结构的深度
    特定于数据库的注释

    MySQL

    神谕

    • 用于遍历邻接列表
    PostgreSQL

    • 物化路径
    SQL Server

    • 2008提供的数据类型似乎有助于采用沿袭列方法并扩展可表示的深度

      • 这是对你问题的一个非常片面的回答,但我希望仍然有用

        Microsoft SQL Server 2008实现了两项功能,这两项功能对于管理分层数据非常有用:

        • 数据类型
        • 常用表表达式,使用关键字

        先看一下Kent Tegels在MSDN上的介绍。另请参见我自己的问题:

        我最喜欢的答案是本文第一句的建议。使用邻接列表维护层次结构,并使用嵌套集查询层次结构

        到目前为止的问题是,从邻接列表到嵌套集的转换方法非常慢,因为大多数人使用称为“推堆栈”的极端RBAR方法进行转换,一直被认为是一种昂贵的方法,可以通过邻接列表和嵌套集的出色性能实现维护的简单性。因此,大多数人最终不得不满足于其中一个,尤其是当节点数超过(比如)糟糕的100000个左右时。使用推栈方法可以花一整天的时间来完成MLME将被认为是百万字节层次结构的转换。 我想我会给Celko一点竞争的机会,想出一种方法,以似乎不可能的速度将邻接列表转换成嵌套集。下面是我的i5笔记本电脑上推堆栈方法的性能

        Duration for     1,000 Nodes = 00:00:00:870 
        Duration for    10,000 Nodes = 00:01:01:783 (70 times slower instead of just 10)
        Duration for   100,000 Nodes = 00:49:59:730 (3,446 times slower instead of just 100) 
        Duration for 1,000,000 Nodes = 'Didn't even try this'
        
        这是新方法的持续时间(括号中有push-stack方法)

        是的,没错。在不到一分钟的时间内转换了100万个节点,在不到4秒内转换了100000个节点

        您可以阅读有关新方法的信息,并从以下URL获取代码副本。

        我还使用类似的方法开发了一个“预聚合”层次结构。传销商和制作物料清单的人对本文特别感兴趣。


        如果您确实要来看看这两篇文章,请跳转到“加入讨论”链接,让我知道您的想法。

        如果您的数据库支持数组,您还可以将沿袭列或物化路径实现为父ID数组

        特别是对于Postgres,您可以使用set操作符查询层次结构,并使用GIN索引获得优异的性能。这使得在单个查询中查找父项、子项和深度变得非常简单。更新也很容易管理


        如果你好奇的话,我有一篇关于使用的完整文章。

        这个设计还没有提到:

        虽然它有局限性,但如果你能承受的话,它是非常简单和高效的。特点:

        • 栏目:
          Duration for     1,000 Nodes = 00:00:00:053 (compared to 00:00:00:870)
          Duration for    10,000 Nodes = 00:00:00:323 (compared to 00:01:01:783)
          Duration for   100,000 Nodes = 00:00:03:867 (compared to 00:49:59:730)
          Duration for 1,000,000 Nodes = 00:00:54:283 (compared to something like 2 days!!!)
          
          CREATE TABLE `taxons` (
            `TaxonId` smallint(6) NOT NULL default '0',
            `ClassId` smallint(6) default NULL,
            `OrderId` smallint(6) default NULL,
            `FamilyId` smallint(6) default NULL,
            `GenusId` smallint(6) default NULL,
            `Name` varchar(150) NOT NULL default ''
          );
          
          +---------+---------+---------+----------+---------+-------------------------------+
          | TaxonId | ClassId | OrderId | FamilyId | GenusId | Name                          |
          +---------+---------+---------+----------+---------+-------------------------------+
          |     254 |       0 |       0 |        0 |       0 | Aves                          |
          |     255 |     254 |       0 |        0 |       0 | Gaviiformes                   |
          |     256 |     254 |     255 |        0 |       0 | Gaviidae                      |
          |     257 |     254 |     255 |      256 |       0 | Gavia                         |
          |     258 |     254 |     255 |      256 |     257 | Gavia stellata                |
          |     259 |     254 |     255 |      256 |     257 | Gavia arctica                 |
          |     260 |     254 |     255 |      256 |     257 | Gavia immer                   |
          |     261 |     254 |     255 |      256 |     257 | Gavia adamsii                 |
          |     262 |     254 |       0 |        0 |       0 | Podicipediformes              |
          |     263 |     254 |     262 |        0 |       0 | Podicipedidae                 |
          |     264 |     254 |     262 |      263 |       0 | Tachybaptus                   |
          
          class Component extends Vertex {
              long assetId;
              long partNumber;
              long material;
              long amount;
          };
          
          class PartOf extends Edge {
          };
          
          class AdjacentTo extends Edge {
          };
          
          +-------------+----------------------+--------+-----+-----+
          | category_id | name                 | parent | lft | rgt |
          +-------------+----------------------+--------+-----+-----+
          |           1 | ELECTRONICS          |   NULL |   1 |  20 |
          |           2 | TELEVISIONS          |      1 |   2 |   9 |
          |           3 | TUBE                 |      2 |   3 |   4 |
          |           4 | LCD                  |      2 |   5 |   6 |
          |           5 | PLASMA               |      2 |   7 |   8 |
          |           6 | PORTABLE ELECTRONICS |      1 |  10 |  19 |
          |           7 | MP3 PLAYERS          |      6 |  11 |  14 |
          |           8 | FLASH                |      7 |  12 |  13 |
          |           9 | CD PLAYERS           |      6 |  15 |  16 |
          |          10 | 2 WAY RADIOS         |      6 |  17 |  18 |
          +-------------+----------------------+--------+-----+-----+
          
          CREATE FUNCTION nomen_tree() RETURNS trigger
              LANGUAGE plpgsql
              AS $_$
          DECLARE
            old_parent INTEGER;
            new_parent INTEGER;
            id_nom INTEGER;
            txt_name TEXT;
          BEGIN
          -- TG_ARGV[0] = name of table with entities with PARENT-CHILD relationships (TBL_ORIG)
          -- TG_ARGV[1] = name of helper table with ANCESTOR, CHILD, DEPTH information (TBL_TREE)
          -- TG_ARGV[2] = name of the field in TBL_ORIG which is used for the PARENT-CHILD relationship (FLD_PARENT)
              IF TG_OP = 'INSERT' THEN
              EXECUTE 'INSERT INTO ' || TG_ARGV[1] || ' (child_id,ancestor_id,depth) 
                  SELECT $1.id,$1.id,0 UNION ALL
                SELECT $1.id,ancestor_id,depth+1 FROM ' || TG_ARGV[1] || ' WHERE child_id=$1.' || TG_ARGV[2] USING NEW;
              ELSE                                                           
              -- EXECUTE does not support conditional statements inside
              EXECUTE 'SELECT $1.' || TG_ARGV[2] || ',$2.' || TG_ARGV[2] INTO old_parent,new_parent USING OLD,NEW;
              IF COALESCE(old_parent,0) <> COALESCE(new_parent,0) THEN
                EXECUTE '
                -- prevent cycles in the tree
                UPDATE ' || TG_ARGV[0] || ' SET ' || TG_ARGV[2] || ' = $1.' || TG_ARGV[2]
                  || ' WHERE id=$2.' || TG_ARGV[2] || ' AND EXISTS(SELECT 1 FROM '
                  || TG_ARGV[1] || ' WHERE child_id=$2.' || TG_ARGV[2] || ' AND ancestor_id=$2.id);
                -- first remove edges between all old parents of node and its descendants
                DELETE FROM ' || TG_ARGV[1] || ' WHERE child_id IN
                  (SELECT child_id FROM ' || TG_ARGV[1] || ' WHERE ancestor_id = $1.id)
                  AND ancestor_id IN
                  (SELECT ancestor_id FROM ' || TG_ARGV[1] || ' WHERE child_id = $1.id AND ancestor_id <> $1.id);
                -- then add edges for all new parents ...
                INSERT INTO ' || TG_ARGV[1] || ' (child_id,ancestor_id,depth) 
                  SELECT child_id,ancestor_id,d_c+d_a FROM
                  (SELECT child_id,depth AS d_c FROM ' || TG_ARGV[1] || ' WHERE ancestor_id=$2.id) AS child
                  CROSS JOIN
                  (SELECT ancestor_id,depth+1 AS d_a FROM ' || TG_ARGV[1] || ' WHERE child_id=$2.' 
                  || TG_ARGV[2] || ') AS parent;' USING OLD, NEW;
              END IF;
            END IF;
            RETURN NULL;
          END;
          $_$;
          
          CREATE TRIGGER nomenclature_tree_tr AFTER INSERT OR UPDATE ON nomenclature FOR EACH ROW EXECUTE PROCEDURE nomen_tree('my_db.nomenclature', 'my_db.nom_helper', 'parent_id');
          
          CREATE FUNCTION rebuild_tree(tbl_base text, tbl_closure text, fld_parent text) RETURNS void
              LANGUAGE plpgsql
              AS $$
          BEGIN
              EXECUTE 'TRUNCATE ' || tbl_closure || ';
              INSERT INTO ' || tbl_closure || ' (child_id,ancestor_id,depth) 
                  WITH RECURSIVE tree AS
                (
                  SELECT id AS child_id,id AS ancestor_id,0 AS depth FROM ' || tbl_base || '
                  UNION ALL 
                  SELECT t.id,ancestor_id,depth+1 FROM ' || tbl_base || ' AS t
                  JOIN tree ON child_id = ' || fld_parent || '
                )
                SELECT * FROM tree;';
          END;
          $$;
          
          -- get all descendants
          SELECT tbl_orig.*,depth FROM tbl_closure LEFT JOIN tbl_orig ON descendant_id = tbl_orig.id WHERE ancestor_id = XXX AND depth <> 0;
          -- get only direct descendants
          SELECT tbl_orig.* FROM tbl_closure LEFT JOIN tbl_orig ON descendant_id = tbl_orig.id WHERE ancestor_id = XXX AND depth = 1;
          -- get all ancestors
          SELECT tbl_orig.* FROM tbl_closure LEFT JOIN tbl_orig ON ancestor_id = tbl_orig.id WHERE descendant_id = XXX AND depth <> 0;
          -- find the deepest level of children
          SELECT MAX(depth) FROM tbl_closure WHERE ancestor_id = XXX;