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
- 廉价的祖先、后代、等级
- 廉价的插入、删除、移动叶子
- 昂贵的内部节点插入、删除和移动
- 硬限制层次结构的深度
- 用于遍历邻接列表
- 物化路径
- 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;