数据库表设计-邻接表、路径枚举、嵌套集、闭包表
技术百科
巴扎黑
发布时间:2017-06-23
浏览: 次 CREATE TABLE Area ([id] [int] NOT NULL,[name] [nvarchar] (50) NULL,[parent_id] [int] NULL,[type] [int] NULL );
name:地域的名称, parent_id 是父ID,省的父ID是国,市的父ID 为省,以此类推。 type 是区域的阶级: 1:国,2:省,3:市,4:区 在层级比较确定的情况下,这么设计表格没有什么问题,调用起来也很方便。 但是使用这种邻接表设计方式,并不能满足所有的需求,当我们不确定层级的情况下,假设我有下面一个评论结构: 用邻接表记录这个评论的数据(comments 表):
| comment_id | parent_id | author | comment |
| 1 | 0 | 小明 | 我不大认同这个观点 |
| 2 | 1 | 小张 | 我也不认同 |
| 3 | 2 | 小红 | 我同意楼上 |
| 4 | 1 | 小全 | 你为什么不认同呢 |
| 5 | 4 | 小明 | 我以前遇到过这情况 |
| 6 | 5 | 小张 | 那也不代表你所说是对的 |
| 7 | 5 | 小新 | 这个视情况而定吧 |
SELECT c1.*, c2.* FROM comments c1 LEFT OUTER JOIN comments c2 ON c2.parent_id = c1.comment_id;
然而这个查询只能获取两层的数据。这种树的特性就是可以任意深地拓展,你需要有相应的方法来获取它的深度数据。比如,可能需要计算一个评论分支的数量,或者计算一个机械设备的所有的总开销。 某些情况下,在项目中使用邻接表正好适用。邻接表设计的优势在于能快速的获取一个给定节点的直接父子节点,它也很容易插入新节点。如果这样的需求就是你的项目对于分层数据的全部操作,那使用邻接表就可以很好的工作了。 遇到上述的树模型,有几种方案是可以考虑下的:路径枚举、嵌套集以及闭包表。这些解决方案通常看上去比邻接表复杂很多,但它们的确使得某些使用邻接表比较复杂或很低效的操作变得更简单。如果你的项目确实需要提供这些操作,那么这些设计会是邻接表更好的选择。
一、路径枚举
在comments 表中,我们使用类型varchar 的path 字段来替代原来的parent_id 字段。这个path 字段所存储的内容为当前节点的最顶层祖先到它的自己的序列,就像UNIX的路径一样,你甚至可以使用 '/' 作为路径的分隔符。| comment_id | path | author | comment |
| 1 | 1 | 小明 | 我不大认同这个观点 |
| 2 | 1/2 | 小张 | 我也不认同 |
| 3 | 1/2/3 | 小红 | 我同意楼上 |
| 4 | 1/4 | 小全 | 你为什么不认同呢 |
| 5 | 1/4/5 | 小明 | 我以前遇到过这情况 |
| 6 | 1/4/5/6 | 小张 | 那也不代表你所说是对的 |
| 7 | 1/4/5/7 | 小新 | 这个视情况而定吧 |
SELECT * FROM comments AS c WHERE '1/4/5/7' LIKE c.path || '%' ;这句话查询语句会匹配到路径为 1/4/5/%,1/4/% 以及 1/% 的节点,而这些节点就是评论#7的祖先。 同时还可以通过将LIKE 关键字两边的参数互换,来查询一个给定节点的所有后代。比如查询评论#4,路径path为 ‘1/4’ 的所有后代,可以使用如下语句:
SELECT * FROM comemnts AS c WHERE c.path LIKE '1/4' || '%' ;这句查询语句所有能找到的后台路径分别是:1/4/5、1/4/5/6、1/4/5/7。 一旦你可以很简单地获取一棵子树或者从子孙节点到祖先节点的路径,你就可以很简单地实现更多的查询,如查询一颗子树所有节点上值的总和。 插入一个节点也可以像使用邻接表一样地简单。你所需要做的只是复制一份要插入节点的父亲节点路径,并将这个新节点的ID追加到路径末尾即可。 路径枚举也存在一些缺点,比如数据库不能确保路径的格式总是正确或者路径中的节点确实存在。依赖于应用程序的逻辑代码来维护路径的字符串,并且验证字符串的正确性开销很大。无论将varchar 的长度设定为多大,依旧存在长度的限制,因而并不能够支持树结构无限扩展。 二、 嵌套集 嵌套集解决方案是存储子孙节点的相关信息,而不是节点的直接祖先。我们使用两个数字来编码每个节点,从而表示这一信息,可以将这两个数字称为nsleft 和 nsright。 每个节点通过如下的方式确定nsleft 和nsright 的值:nsleft的数值小于该节点所有后代ID,同时nsright 的值大于该节点的所有后代的ID。这些数字和comment_id 的值并没有任何关联。 确定这三个值(nsleft,comment_id,nsright)的简单方法是对树进行一次深度优先遍历,在逐层深入的过程中依次递增地分配nsleft的值,并在返回时依次递增地分配nsright的值。得到数据如下:
| comment_id | nsleft | nsright | author | comment |
| 1 | 1 | 14 | 小明 | 我不大认同这个观点 |
| 2 | 2 | 5 | 小张 | 我也不认同 |
| 3 | 3 | 4 | 小红 | 我同意楼上 |
| 4 | 6 | 13 | 小全 | 你为什么不认同呢 |
| 5 | 7 | 12 | 小明 | 我以前遇到过这情况 |
| 6 | 8 | 9 | 小张 | 那也不代表你所说是对的 |
| 7 | 10 | 11 | 小新 | 这个视情况而定吧 |
SELECT c2.* FROM comments AS c1 JOIN comments AS c2 ON c2.nsleft BETWEEN c1.nsleftAND c1.nsright WHERE c1.comment_id = 4;比如搜索评论#6及其所有祖先,可以通过搜索#6的ID在哪些节点的nsleft 和 nsright 范围之间,例:
SELECT c2.* FROM comments AS c1 JOIN comments AS c2 ON c1.nsleft BETWEEN c2.nsleftAND c2.nsright WHERE c1.comment_id = 6;
使用嵌套集设计的主要优势是,当你想要删除一个非叶子节点时,它的后代会自动替代被删除的节点,成为其直接祖先节点的直接后代。就是说已经自动减少了一层。 然而,某些在邻接表的设计中表现得很简单的查询,比如获取一个节点的直接父亲或者直接后代,在嵌套集设计中会变得比较复杂。在嵌套集中,如果需要查询一个节点的直接父亲,我们会这么做,比如要找到评论#6 的直接父亲:
SELECT parent.* FROM comments AS c JOIN co总之有些复杂。 对树进行操作,比如插入和移动节点,使用嵌套集会比其它设计复杂很多。当插入一个新节点时,你需要重新计算新插入节点的相邻兄弟节点、祖先节点和它祖先节点的兄弟,来确保他们的左右值都比这个新节点的左值大。同时,如果这个新节点时一个非叶子节点,你还要检查它的子孙节点。 如果简单快速查询是整个程序中最重要的部分,嵌套集是最好的选择,比操作单独的节点要方便快捷很多。然而,嵌套集的插入和移动节点是比较复杂的,因为需要重新分配左右值,如果你的应用程序需要频繁的插入、删除节点,那么嵌套集可能并不合适。 三、闭包表 闭包表是解决分级存储的一个简单而优雅的解决方案,它记录了树中所有节点间的关系,而不仅仅只有那些直接的父子节点。 在设计评论系统时,我们额外创建了一个叫 tree_paths 表,它包含两列,每一列都指向 comments 中的外键。 我们不再使用comments 表存储树的结构,而是将树中任何具有(祖先 一 后代)关系的节点对都存储在treepaths 表里,即使这两个节点之间不是直接的父子关系;同时,我们还增加一行指向节点自己。mments AS parent ON c.nsleft BETWEEN parent.nsleft AND parent.nsrightLEFT OUTER JOIN comments AS in_between ON c.nsleft BETWEEN in_between.nsleft AND in_between.nsrightAND in_between.nsleft BETWEEN parent.nsleft AND parent.nsright WHERE c.comment_id = 6AND in_between.comment_id IS NULL;
| 祖先 | 后代 | 祖先 | 后代 | 祖先 | 后代 | 祖先 | 后代 |
| 1 | 1 | 1 | 7 | 4 | 6 | 7 | 7 |
| 1 | 2 | 2 | 2 | 4 | 7 | ||
| 1 | 3 | 2 | 3 | 5 | 5 | ||
| 1 | 4 | 3 | 3 | 5 | 6 | ||
| 1 | 5 | 4 | 4 | 5 | 7 | ||
| 1 | 6 | 4 | 5 | 6 | 6 |
INSERT INTO treepaths (ancestor, descendant)SELECT t.ancestor, 8FROM treepaths AS tWHERE t.descendant = 6UNION ALL SELECT 8, 8;要删除一个叶子节点,比如评论#7, 应删除所有treepaths 表中后代为评论 #7 的行:
DELETE FROM treepaths WHERE descendant = 7;
要删除一颗完整的子树,比如评论#4 和它所有的后代,可删除所有在 treepaths 表中后代为 #4的行,以及那些以评论#4后代为后代的行。 闭包表的设计比嵌套集更加的直接,两者都能快捷地查询给定节点的祖先和后代,但是闭包表能更加简单地维护分层信息。这两个设计都比使用邻接表或者路径枚举更方便地查询给定节点的直接后代和祖先。 然而你可以优化闭包表来使它更方便地查询直接父亲节点或者子节点: 在 treepaths 表中添加一个 path_length 字段。一个节点的自我引用的path_length 为0,到它直接子节点的path_length 为1,再下一层为2,以此类推。这样查询起来就方便多了。 总结:你该使用哪种设计: 种设计都各有优劣,如何选择设计,依赖于应用程序的哪种操作是你最需要性能上的优化。
| 方案 | 表数量 | 查询子 | 查询树 | 插入 | 删除 | 引用完整性 |
| 邻接表 | 1 | 简单 | 困难 | 简单 | 简单 | 是 |
| 枚举路径 | 1 | 简单 | 简单 | 简单 | 简单 | 否 |
| 嵌套集 | 1 | 困难 | 简单 | 困难 | 困难 | 否 |
| 闭包表 | 2 | 简单 | 简单 | 简单 | 简单 | 是 |
# 自己的
# 我也
# 你可以
# 可以使用
# 这两个
# 小张
# 小明
# 子树
# 不代表
# 你所
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Windows怎样拦截QQ浏览器广告_Window
- c++中如何求一个数的平方根_c++ sqrt函数
- Win11怎么关闭自动调节亮度_Windows11
- 如何理解Go指针和内存分配关系_Go Pointe
- 微信JSAPI支付回调PHP怎么接收_处理JSAP
- Win10如何卸载Skype_Win10卸载Sky
- Win11怎么关闭资讯和兴趣_Windows11任
- VSC怎么配置PHP的Xdebug_远程调试设置步
- Win10怎么关闭自动更新错误重启 Win10策略
- Windows10电脑怎么设置虚拟光驱_Win10
- Win10 BitLocker加密教程 Win10
- Win11怎么关闭搜索历史 Win11清除搜索框最
- windows系统如何安装cab更新补丁_wind
- Win11怎么关闭应用权限_Windows11相机
- Windows10如何彻底关闭自动更新_Win10
- C++中的constexpr和const有什么区别
- Python包结构设计_大型项目组织解析【指导】
- MAC怎么解压RAR格式文件_MAC第三方解压工具
- 如何使用正则表达式批量替换重复的“-”模式为固定字
- Win10怎么卸载迅雷_Win10彻底卸载迅雷方法
- php接口返回数据乱码怎么办_php接口调试编码问
- Win11怎么关闭贴靠布局_Win11禁用窗口最大
- Windows10系统怎么查看运行时间_Win10
- 如何使用 Selenium 正确获取篮球参考网站球
- Win11怎么设置应用分屏_Windows11贴靠
- c++怎么用jemalloc c++替换默认内存分
- c# 在高并发下使用反射发射(Reflection
- Linux怎么禁止Root用户远程登录_Linux
- Win11怎么压缩文件 Win11自带压缩解压功能
- 一文教你快速开通网站LOGO图
- Python变量绑定机制_引用模型解析【教程】
- Win11怎么查看局域网电脑_Windows 11
- 如何使用Golang实现容器健康检查_监控和自动重
- Windows如何查看和管理已安装的字体?(字体文
- win11如何清理传递优化文件 Win11为C盘瘦
- c++输入输出流 c++ cin与cout格式化输
- Windows10电脑怎么设置虚拟内存_Win10
- Python日志系统设计与实现_高可观测性架构实战
- c++如何判断文件是否存在_c++ filesys
- Go 中 := 短变量声明的类型推导机制详解
- 如何在Golang中处理云原生事件_使用Event
- C#如何使用Channel C#通道实现异步通信
- ACF 教程:如何正确更新嵌套在多层 Group
- 微信企业付款回调PHP怎么接收_处理企业付款异步通
- windows 10专注助手怎么关闭_window
- php控制舵机角度怎么调_php发送pwm信号控制
- Win11文件扩展名怎么显示 Win11查看文件后
- Win11怎么设置默认邮件应用_Windows11
- 用Python构建微服务架构实践_FastAPI与
- 静态属性修改会影响所有实例吗_php作用域操作符下

mments AS parent ON c.nsleft BETWEEN parent.nsleft AND parent.nsrightLEFT OUTER JOIN comments AS in_between ON c.nsleft BETWEEN in_between.nsleft AND in_between.nsrightAND in_between.nsleft BETWEEN parent.nsleft AND parent.nsright WHERE c.comment_id = 6AND in_between.comment_id IS NULL;
QQ客服