几种数据库中保存树的常见存储结构

在数据库中存储树时,常见的存储结构有以下几种:

常见存储结构

邻接列表

每个节点都有一个指向其父节点(pid)的引用。这种方法简单直观,也是最容易理解和常用的,但在获取整棵树或子树时可能需要多次查询。

存储结构

一般表结构如下:

CREATE TABLE tree (
  id INT PRIMARY KEY,
  pid INT,
  name VARCHAR(100),
  FOREIGN KEY (pid) REFERENCES nodes(id)
);

示例数据如下:

idpidname
1NULLroot
21A
31B
41C
52A-1
62A-2
72A-3

表示的树如下:

  • root
    • A
      • A-1
      • A-2
      • A-3
    • B
    • C

需要注意
pid树的查询和删除操作涉及到的递归操作,无法通过优化SQL解决,必须通过程序递归实现,这也意味着,在复杂树查询和操作时,需要在客户端与数据库之间频繁传输数据,效率较低。

优缺点

邻接列表模型,有明也简称为pid树,是最简单和常见的树结构存储模型。

  • 查询效率低

当需要以下查询时,一般需要进行递归查询,效率较低:

- 查询整棵树或子树
- 查询某个节点的所有后代
- 查询某个节点的祖先节点
  • 删除节点效率低

同样的,当我们要删除一个节点时,也需要递归删除其所有子节点,效率较低。

比如我们要删除节点A(id=100),只执行DELETE tree WHERE id=100 AND pid=100是不够的,因为A可能还存在多级后代节点,因此还需要递归删除其所有子节点,效率比较低下。

  • 无序树

树节点的之间的无序的,为此我们需要额外的字段来维护节点之间的顺序。
因此,在实际使用需要为表结构添加一个order字段,用来代表同一层级内节点的顺序,从而使得树变成有序树

idpidordername
1NULL1root
211A
312B
413C
521A-1
622A-2
723A-3

然后,我们通过select * from tree where pid=1 order by order来查询A的所有有序子节点集。

问题是,如果整棵树是有序的呢?

如果整棵树是有序的,则order字段必须是扩展到全树,如下:

idpidordername
1NULL1root
212A
317B
418C
524A-1
625A-2
726A-3

这样,我们就可以通过select * from tree order by order来查询整棵树的有序节点集。

但是问题来了,如果我们要调整树结构,比如将节点A移动到节点B下,那么我们就需要调整order字段,这样会导致大量的数据更新,效率较低。同时也无法避免order字段的唯一性问题,因此order字段的值必须是唯一的,这也增加了调整树结构的难度。

更糟糕的是, 始终无法避免递归调用更新的问题。

所以pid树的要实现完全的有序树是比较麻烦的。

  • 更新树结构

pid树需要调整树结构时效率是比较高的。

比如将节点A移动到节点B下 ,只需要将节点Apid修改为Bid即可。

但是由于其是无序树,所以如果要将节点A移动为节点B的下一个兄弟节点,则比较麻烦,需要额外的处理。

路径枚举

每个节点都保存了从根节点到自己的路径。这种方法可以方便地查询子树,但在插入或删除节点时需要更新大量数据。

存储结构
CREATE TABLE nodes (
  id INT PRIMARY KEY,
  path VARCHAR(255),
  name VARCHAR(100)
);

示例数据如下:

idpathname
1/rootroot
2/root/AA
3/root/BB
4/root/CC
5/root/A/A-1A-1
6/root/A/A-2A-2
7/root/A/A-3A-3
优缺点
  • 子树查询效率高

由于其在每个节点都保存了从根节点到自己的路径,因此查询子树非常方便,只需要一次查询即可。
比如查询节点A的所有子节点,只需要执行如下SQL即可:

select * from nodes where path like '/A/B%'
  • 路径组成

首要问题是我们该使用哪个字段来表示和组合路径,一般来说,我们可以使用id或者name来组合路径。

  • 如果使用id字段,一般也就表的主键,这样可以保证路径的唯一性。但是id不能使用UUID之类的字段,否则 path字段会变得非常长。也不能使用自增字段字段,因此其id值不可预测。所以只能使用人工分配的数字值,这就比较麻烦。

  • 如果使用name字段,一般来说name字段是唯一的,但是name字段可能会变化,这样会导致path字段的变化,因此需要额外的处理。

  • 同级路径名称重名

路径组成字段在同级中不允许存在重名。如果存在重名,那么就会导致path字段的唯一性问题,因此path字段的值必须是唯一。

  • 路径长度限制

path字段的长度可能有限制的,因此我们需要限制树的深度。

  • 无序树

同样地,树节点的之间的无序的,与pid树一样,要维护一棵有序树也是比较麻烦的。

  • 更新树结构

路径枚举模型下,如果需要调整树结构,比如将节点/root/A移动到节点/root/B下。

  • 首先需要将节点/root/A的所有子节点的path字段修改为/root/B/A/开头的路径。对应的SQL语句如下:
update nodes set path = replace(path,'/root/A/','/root/B/A/') where path like '/root/A/%'
  • 然后将节点/root/Apath字段修改为/root/B/A。对应的SQL语句如下:
update nodes set path = '/root/B/A' where path = '/root/A'

左右值

存储结构

每个节点都有两个数字,代表了一个范围,这个范围内的所有节点都是它的子节点。这种方法可以方便地查询子树和父节点,但在插入或删除节点时需要更新大量数据。

CREATE TABLE nodes (
  id INT PRIMARY KEY,
  leftValue INT,
  rightValue INT,
  name VARCHAR(100)
);

示例数据如下:

idlftrgtname
1114root
229A
31011B
41213C
534A-1
656A-2
778A-3

表示的树如下:

  • root
    • A
      • A-1
      • A-2
      • A-3
    • B
    • C
优缺点

左右值结构是一种非常高效的树存储结构,它可以方便地查询子树和父节点。

基本原理是给每个节点分配两个数字,代表了一个范围,这个范围内的所有节点都是它的子节点。

在这里插入图片描述

  • 高效查询

左右值结构可以方便地查询子树和父节点,比如查询节点A的所有子节点,只需要执行如下SQL即可:

select * from nodes where lft > 2 and rgt < 9

左右值结构除了可以方便查询子树外,还可以方便查询:

  • 查询某个节点的所有后代
  • 查询某个节点的祖先节点
  • 查询整棵树
  • 查询子树节点数量
  • 查询兄弟节点

最大的特点就是可以避免pid树面临的的递归查询,效率非常高。

  • 有序树

左右值结构表示的树天然就是有序树,因此不需要额外的字段来维护节点之间的顺序。

  • 更新树结构

由于树是通过左右值来表示的,节点的移动、更新、删除等操作均会造成相关节点的左右值的变化,因此调整树结构时,需要更新大量数据。
每个更新操作均可能涉到多条SQL的执行,因此效率较低。

比如添加子节点:

LOCK TABLE Tree WRITE;
SELECT @parent_id := node_id, @myLeft := lft FROM Tree WHERE name = "Food";
UPDATE Tree SET rgt = rgt + 2 WHERE rgt > @myLeft;
UPDATE Tree SET lft = lft + 2 WHERE lft > @myLeft;
INSERT INTO Tree( name, lft, rgt) VALUES( "Fruit", @myLeft + 1, @myLeft + 2);
UNLOCK TABLES;

更多可以查看这里

闭包表

存储结构

使用一个单独的表来存储每个节点与其所有祖先节点之间的关系。这种方法可以方便地查询子树和父节点,但需要额外的存储空间。

CREATE TABLE nodes (
  id INT PRIMARY KEY,
  name VARCHAR(100)
);

CREATE TABLE closure (
  ancestor INT,
  descendant INT,
  depth INT,
  PRIMARY KEY (ancestor, descendant),
  FOREIGN KEY (ancestor) REFERENCES nodes(id),
  FOREIGN KEY (descendant) REFERENCES nodes(id)
);

示例数据如下:

nodes 表:

idname
1root
2A
3B
4C
5A-1
6A-2
7A-3

closure 表:

ancestordescendantdepth
110
121
131
141
152
162
172
220
251
261
271
330
440
550
660
770
优缺点
  • 高效查询

通过Join操作也可以比较方便进行查询操作,比如查询节点A的所有子节点,只需要执行如下SQL即可:

select * from nodes n join closure c on n.id=c.descendant where c.ancestor=2
  • 空间换时间

相对于前几种存储结构,ClosureTable结构是一种空间换时间的算法,这也意味着

  • 需要额外的存储空间。
  • 需要额外的Join操作。

总结

  • 邻接列表模型最简单直观,但是无法避免的递归查询让其只能用在一些简单的场合。
  • 路径枚举模型查询效率高,但路径字段选择比较重要,当路径字段更新或重名时,面临很大的问题,所以并不推荐。
  • 左右值模型查询效率高,但是严格的左右值的完整性,使得调整树结构时,需要更新大量数据,效率较低,所以树的结构完整性比较脆弱,任意更新操作均需要进行锁表操作,否则任何错误的左右值更新均可能导致树结构损坏。
  • 闭包表模型查询效率高,但需要额外的存储空间。

总体而言,在以查询为主的场景中,本人更推荐使用左右值的存储结构,因为查询的确是比较方便,并且天然是有序的。

最后给大家推荐一个基于Nodejs下使用基于左右值算法的树存储管理库FlexTree

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 对于保存多个指标的区间数据,可以考虑以下几种设计方案: 1. 每个指标单独保存一个区间表:这种设计方案可以将每个指标的区间数据单独保存在一个表,每个表包含指标名称、区间起始值、区间结束值、区间数据等字段。这种方案的优点是数据结构简单,易于维护和查询,缺点是需要创建多个表,不太灵活。 2. 每个指标保存在同一个区间表:这种设计方案可以将所有指标的区间数据保存在同一个表,表包含指标名称、区间类型、区间起始值、区间结束值、区间数据等字段。这种方案的优点是数据结构简单,易于维护和查询,缺点是不太灵活,难以区分每个指标的数据。 3. 将多个指标保存在同一个区间表,每个指标占据一列:这种设计方案可以将所有指标的区间数据保存在同一个表,每个指标占据一列,表包含区间类型、区间起始值、区间结束值、指标1、指标2、指标3等字段。这种方案的优点是灵活,易于添加或删除指标,缺点是数据结构较为复杂,查询时需要使用动态 SQL。 在选择设计方案时,需要考虑业务需求和数据量等因素,选择最为合适的方案。 ### 回答2: 要设计一个合理的数据库保存多个指标的区间数据,可以采用以下方法: 1. 创建一个主表来存储每个指标的基本信息,包括指标名称、单位、说明等,每个指标对应主表的一行。 2. 创建一个区间表用于存储指标的区间数据,该表与主表建立关联。区间表的字段可以包括指标ID(与主表关联),下限值、上限值、区间说明等。 3. 为了提高查询效率,可以为主表和区间表的关联字段建立索引。 4. 如果区间数据需要频繁地被修改,可以考虑使用分区表。根据指标的特点,将区间数据按照一定的规则进行分区,例如按照时间或者数值范围进行分区,可以加快数据的插入和查询速度。 5. 如果需要进行复杂的查询和分析操作,可以考虑使用数据仓库技术。将数据库的数据抽取、转换和加载到数据仓库,通过数据仓库的多维数据模型和聚集操作,可以快速地进行多维度的查询和分析。 综上所述,设计一个合理的数据库保存多个指标的区间数据,需要考虑表的结构、索引的建立、分区表的使用,以及数据仓库的结构。这样可以提高数据库的查询和分析效率,同时满足对多个指标的区间数据的保存和管理需求。 ### 回答3: 在数据库保存多个指标的区间数据,可以采用以下设计方法来比较合理: 1. 表结构设计:创建一个指标数据表,包含字段如下:指标名称、起始值、结束值。这样可以将每个指标的区间数据保存在同一个表。 2. 数据类型选择:对于起始值和结束值,可以选择合适的数据类型来保存。例如,可以使用整数类型、浮点数类型或者字符类型来保存区间数据。 3. 数据组织方式:可以将不同的指标数据分别存储在不同的行,或者使用一行来保存一个指标的整个区间数据。这取决于实际需求和数据复杂性。 4. 索引设计:针对常见的查询需求,可以根据具体情况来设计索引,以提高查询效率。例如,可以根据指标名称来设计索引,或者根据起始值和结束值来设计范围索引。 5. 数据完整性约束:为了确保数据的完整性,可以在数据库添加适当的约束条件。例如,可以添加唯一约束来保证每个指标的区间数据不重复,或者添加范围约束来限制起始值和结束值的取值范围。 6. 数据查询和统计:根据实际需求,可以使用SQL查询语句来获取指定指标的区间数据。也可以使用聚合函数来对区间数据进行统计分析,例如计算平均值、最大值、最小值等。 综上所述,数据库保存多个指标的区间数据可以通过合理的表结构设计、数据类型选择、数据组织方式、索引设计、数据完整性约束以及数据查询和统计等方法来实现。这样能够提高查询效率、保证数据完整性,便于数据分析和统计。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值