前言

层次型数据在数据库中的存储已经是老生常谈,常见的:

  1. 邻接表
  2. 改进前序遍历树

下面我将简要介绍这两种方法的思路并附上代码,然后会探讨两种方法的优劣,以及背后原因,最后提出一种折衷的办法:改进邻接表。


方法一:邻接表

假设我们需要在数据库中存储如下的结构

(图片来自:参考1)

那么使用邻接表将会在数据库中这么存放数据:

(图片来自:参考1)

表结构很简单明了,值得一提的是实际项目中parent一般是写id号的,这里只是为了可读性。

那么我们从数据库中恢复树状结构只要使用递归的方法,代码如下

 1 #coding=utf-8 2  3 import sqlite3 as lite 4 import time 5  6 if __name__ == "__main__": 7  8     con = lite.connect('data.sqlite3') 9     allline = con.execute("select * from foods").fetchall()    10     11     def show_item(parent, level):12         sql = "select * from foods where parent='%s' " % parent13         cur = con.execute(sql)14         for one in cur.fetchall():15             print '---'*level + one[1]16             show_item(one[1], level+1)17             18     show_item('food',0)

很明显,这种方法主要的时间都耗在递归和递归中的查询,具体哪个是主要原因目前还不得知,不过在下文我们将会刨根究底。


方法二:改进前序遍历树

数据库中的存储结构,和邻接表对比发现,少了parent,多了left和right。这left和right是怎么来的呢?

主要思路如下图所示:

(图来自参考1)

我们希望将树状结构用水平结构来表示,按照如图所示的路线进行标注。

首先在food的左边标1,然后fruit左边标2,如此下去,我们发现这样一种结构

,最上方的粗线就是food,第二行的第一根是fruit,第二根是meat,这样看是不是觉得把树状结构用在数轴上表示出来了?

那么遍历这样一种结构十分方便,如果我们要查找food的字节点,那么只要

select * from foods where left > 1 and right < 18 order by left

“order by left”刚好就是按照正常的顺序进行排列,剩下一个问题就是缩进!?

解决办法就是运用stack:访问到哪个节点,如果发现节点的right比栈顶的大,那么出栈,直到栈顶比节点的right小,然后只要计算栈的大小就是缩进的长度了。

(这算法比较难理解,看下面的代码)

 1 # coding=utf-8 2  3 import sqlite3 as lite, time 4  5 if __name__ == "__main__": 6      7     def show_tree(isShow = False): 8         sql = 'select * from foods1 where left>1 and right<18' 9         con = lite.connect('data.sqlite3')10         lines = con.execute(sql).fetchall()11 12         stack = []13         for line in lines:14             title = line[0]15             left  = line[1]16             right = line[2]17 18             while stack and right > stack[-1]:19                 stack.pop()20 21             if isShow:  print '--'*len(stack) + title22             stack.append(right)23     24     show_tree(True)


折衷方法:改进邻接表

对比以上两个算法,我让两个脚本同样运行50000次,发现两者差别并不大。(list0.py是邻接表, list2.py是改进前序遍历)

那么为什么两者的效率会差不多呢,如果说改进前序是因为复杂的算法,那么我们可以大胆假设邻接表主要耗损在数据库查询上。

所以考虑以下原因:

  • 我不希望在数据库中放和数据无关的其他信息,例如前序遍历树加入的left和right,因为会影响数据库的独立性。
  • 普通邻接表的主要费时在数据库查询
  • 改进邻接表的主要费时在层次判断
综合以上几点,比较好的方法就是改进邻接表了。以下上代码。
 1 #coding=utf-8 2  3 import sqlite3 as lite 4 import time 5  6 def show_item(parent, level): 7     def find_childs(parent): 8         return [line for line in allline if line[0] == parent ] 9         10     lines = find_childs(parent)11     for line in lines:12         title = line[1]13         # print '---'*level + title14         show_item(title, level+1)15             16 if __name__ == "__main__":17     con = lite.connect('data.sqlite3')18     allline = con.execute("select * from foods").fetchall()    19 20     start = time.time()21     for i in range(0,50000):22         show_item('food',0)23     print time.time() - start

需要解释的是find_childs(parent, level)函数,它的功能其实就是代替数据库查询:
select * from foods where parent='%parent'

这样将这部分逻辑移到代码中,大量的减少了数据库查询次数,效果很显著:(list0.py:普通邻接表, list1.py:改进邻接表, list2.py:改进前序遍历)


下面是三个文件的源代码:
list0.py
View Code

 1 #coding=utf-8 2  3 import sqlite3 as lite 4 import time 5  6 if __name__ == "__main__": 7  8     con = lite.connect('data.sqlite3') 9     allline = con.execute("select * from foods").fetchall()    10     11     def show_item(parent, level):12         sql = "select * from foods where parent='%s' " % parent13         cur = con.execute(sql)14         for one in cur.fetchall():15             # print '---'*level + one[1]16             show_item(one[1], level+1)17 18     start = time.time()19     for i in range(0,50000):20         show_item('food',0)21     print time.time() - start22     

list1.py
View Code

 1 #coding=utf-8 2  3 import sqlite3 as lite 4 import time 5  6 def show_item(parent, level): 7     def find_childs(parent): 8         return [line for line in allline if line[0] == parent ] 9         10     lines = find_childs(parent)11     for line in lines:12         title = line[1]13         # print '---'*level + title14         show_item(title, level+1)15             16 if __name__ == "__main__":17     con = lite.connect('data.sqlite3')18     allline = con.execute("select * from foods").fetchall()    19 20     start = time.time()21     for i in range(0,50000):22         show_item('food',0)23     print time.time() - start

list2.py
View Code

 1 # coding=utf-8 2  3 import sqlite3 as lite, time 4  5 if __name__ == "__main__": 6      7     def show_tree(isShow = False): 8         sql = 'select * from foods1 where left>1 and right<18' 9         con = lite.connect('data.sqlite3')10         lines = con.execute(sql).fetchall()11 12         stack = []13         for line in lines:14             title = line[0]15             left  = line[1]16             right = line[2]17 18             while stack and right > stack[-1]:19                 stack.pop()20 21             if isShow:  print '--'*len(stack) + title22             stack.append(right)23 24     start = time.time()25     for i in range(0,50000):26         show_tree(False)27     print time.time() - start28     29     

参考

  1. http://www.sitepoint.com/hierarchical-data-database/
  2. http://www.blogjava.net/kiant/articles/319878.html

转载于:https://www.cnblogs.com/gaott/archive/2012/03/04/2378945.html

在数据库中存储层次型数据相关推荐

  1. 如何在SQL中处理层次型数据

    最近在做公司的认证系统,看了开源项目如apache shiro跟spring security,还不知道是自己构建还是用上述代码.最近的考虑点是如何处理层次型数据,因为打算给user构造一个有层次的g ...

  2. 数据库中存储Json格式数据

    在数据库中存储Json格式数据 1.表字段类型 json 2.Java代码有两种方式: 方式一 :属性定义成String类型. 往数据库中存储的值 必须为JSON格式的字符串,因为数据库中会做一次校验 ...

  3. Oracle数据库中插入日期型数据

    如果插入Oracle的当前系统时间可以用 SYSDATE INSERT INTO FLOOR VALUES ( SYSDATE ) ; 往Oracle数据库中插入日期型数据(to_date的用法) 今 ...

  4. 如何在数据库中存储大的数据文件

    1. 什么是大的数据? 所谓大的数据,就是大的字节数据,或大的字符数据.标准SQL中提供了如下类型来保存大数据类型: 类型 长度 tinyblob 28–1B(256B) blob 216-1B(64 ...

  5. 向SQL Server数据库中插入日期型数据

    数据库中字段类型是:datetime 1.采用java.util.Date类 import java.sql.Connection; import java.util.Date; import jav ...

  6. oracle中插入日期型数据,ORACLE插入日期数据

    ORACLE插入日期数据 oracle数据库插入日期型数据 往Oracle数据库中插入日期型数据(to_date的用法) INSERT  INTO  FLOOR  VALUES  ( to_date ...

  7. java向mysql写入数据慢_通过java代码往mysql数据库中写入日期相关数据少13个小时...

    通过show variables like '%time_zone%'; 查看时区: CST 时区 名为 CST 的时区是一个很混乱的时区,有四种含义: 美国中部时间 Central Standard ...

  8. android studio数据库存储数据,如何使用API​​ 23在android studio中的数据库中存储数据?...

    大多数时候我不会发布任何内容,因为我可以在其他帖子中找到我需要的所有内容,但是现在我已经有几天了,您如何在数据库中存储任何内容?这是我的Java代码如何使用API​​ 23在android studi ...

  9. 将不同数据存储到数据库中_如何将数据存储在兔子洞中

    将不同数据存储到数据库中 Starting with databases and venturing into how the physical components of a computer st ...

  10. 在SQL数据库中存储纬度和经度数据时要使用的数据类型是什么? [重复]

    本文翻译自:What datatype to use when storing latitude and longitude data in SQL databases? [duplicate] Th ...

最新文章

  1. 对Struts2的认识(-)
  2. gin redis 链接不上_内存优化,Redis是如何实现的!
  3. oracle学习之三--多表查询
  4. Use tcode ST01 to log authorization check
  5. 为什么spyder这么慢_微区成分分析为什么这么慢?
  6. Samba服务器安装测试
  7. 详解 :Spring Boot 最核心的 3 个注解
  8. 高分3号介绍及PIE使用
  9. 图形美不胜收,25 个可视化案例,Matplotlib 始终都是数据可视化绕不开的 Python 库
  10. android 应用开启以后,动态检测并或者相机权限。
  11. 名企招聘面试考题集锦
  12. 离散数学——coq学习笔记(二)
  13. Python量化选股入门:资本资产定价模型(CAPM)
  14. ARM通用寄存器及状态寄存器详解
  15. android sqlite 存储对象,SQLite存储对象
  16. 你,我和“拥抱”:探戈作为关系治疗法
  17. Unity3D摄像机Camera参数详解
  18. 常见Web安全漏洞类型
  19. 基于JAVA甜趣网上蛋糕店订购系统计算机毕业设计源码+数据库+lw文档+系统+部署
  20. SATA 3.0 中 6GB/s = 600Mb/s ,怎么来的?

热门文章

  1. jQuery.extend函数详细用法![转]
  2. .NET实现中英文验证码
  3. Xcode 12: building for iOS Simulator, but linking in object file built fo... for architecture arm64
  4. PhpSpreadsheet使用
  5. python_基础知识回顾总结
  6. 杂项-协议-HTTP:GET/POST/PUT/DELETE/INPUT/TRACE/OPTIONS/HEAD方法
  7. 【第二篇】ASP.NET MVC快速入门之数据注解(MVC5+EF6)
  8. ASP.NET MVC 学习笔记(1)
  9. 【bzoj3514】Codechef MARCH14 GERALD07加强版
  10. 洛谷 P1168 中位数 堆