设为首页 收藏本站
查看: 3100|回复: 0

[经验分享] python实现简易数据库之三——join多表连接和group by分组

[复制链接]

尚未签到

发表于 2015-4-27 06:18:49 | 显示全部楼层 |阅读模式
  上一篇里面我们实现了单表查询和top N查询,这一篇我们来讲述如何实现多表连接和group by分组。
  一、多表连接
    多表连接的时间是数据库一个非常耗时的操作,因为连接的时间复杂度是M*N(M,N是要连接的表的记录数),如果不对进行优化,连接的产生的临时表可能非常大,需要写入磁盘,分多趟进行处理。
   1、双表等值join
  我们看这样一个连接sql:



select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME
from SUPPLIER,PARTSUPP
where PS_SUPPKEY = S_SUPPKEY and PS_AVAILQTY > 2000and S_NATIONKEY = 1;
  可以把这个sql理解为在SUPPLIER表的S_SUPPKEY属性和PARTSUPP表的PS_SUPPKEY属性上作等值连接,并塞选出满足PS_AVAILQTY > 2000和 S_NATIONKEY = 1的记录,输入满足条件记录的PS_AVAILQTY,PS_SUPPLYCOST,S_NAME属性。这样的理解对我们人来说是很明了的,但数据库不能照这样的方式执行,上面的PS_SUPPKEY其实是PARTSUPP的外键,两个表进行等值连接,得到的连接结果是很大的。所以我们应该先从单表查询条件入手,在单表查询过滤之后再进行等值连接,这样需要连接的记录数会少很多。
  首先根据PS_AVAILQTY > 2000找出满足条件的PARTSUPP表的记录行号集A,然后根据S_NATIONKEY = 1找出SUPPLIER表找出相应的记录行号集B,在记录集A、B上进行等值连接,看图很简单:
DSC0000.jpg
  依次扫描的时间复杂度为max(m,n),加上折半查找,总的时间复杂度为max(m,n)*(log(m1)+log(n1)),其中m1、n1表示where条件塞选出的记录数。
  来看一下执行的结果:



Input SQL:
select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME
from SUPPLIER,PARTSUPP
where PS_SUPPKEY = S_SUPPKEY
and PS_AVAILQTY > 2000
and S_NATIONKEY = 1;
{'FROM': ['SUPPLIER', 'PARTSUPP'],
'GROUP': None,
'ORDER': None,
'SELECT': [['PARTSUPP.PS_AVAILQTY', None, None],
['PARTSUPP.PS_SUPPLYCOST', None, None],
['SUPPLIER.S_NAME', None, None]],
'WHERE': [['PARTSUPP.PS_AVAILQTY', '>', '2000'],
['SUPPLIER.S_NATIONKEY', '=', '1'],
['PARTSUPP.PS_SUPPKEY', '=', 'SUPPLIER.S_SUPPKEY']]}
Quering: PARTSUPP.PS_AVAILQTY > 2000
Quering: SUPPLIER.S_NATIONKEY = 1
Quering: PARTSUPP.PS_SUPPKEY = SUPPLIER.S_SUPPKEY
Output:
The result hava 26322 rows, here is the fisrt 10 rows:
-------------------------------------------------
rows     PARTSUPP.PS_AVAILQTY PARTSUPP.PS_SUPPLYCOST SUPPLIER.S_NAME
-------------------------------------------------
1             8895       378.49 Supplier#000000003
2             4286       502.00 Supplier#000000003
3             6996       739.71 Supplier#000000003
4             4436       377.80 Supplier#000000003
5             6728       529.58 Supplier#000000003
6             8646       722.34 Supplier#000000003
7             9975       841.19 Supplier#000000003
8             5401       139.06 Supplier#000000003
9             6858       786.94 Supplier#000000003
10             8268       444.21 Supplier#000000003
-------------------------------------------------
Take 26.58 seconds.
  从Quering后面的信息可以看到我们处理where子条件的顺序,先处理单表查询,再处理多表连接。
    2、多表join
  处理完双表join后,我们看一下怎么实现三个的join,示例sql:



select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME
from SUPPLIER,PART,PARTSUPP
where PS_PARTKEY = P_PARTKEY
and PS_SUPPKEY = S_SUPPKEY
and PS_AVAILQTY > 2000
and P_BRAND = 'Brand#12'
and S_NATIONKEY = 1;
  这里进行三个表的连接,三个表连接得到的应该是三个表的记录合并的结果,那根据where条件选出的记录行号应当包含三列,每一列是一个表的行号:  
DSC0001.jpg
    三个表的连接事实上建立在两个表连接的基础上的,先进行两个表的连接后,得到两组行号表,再将这两组行号表合并:
DSC0002.jpg
  主要代码如下:



1 sortJoin(joina,cloumi)#cloumi表示公共表在joina的列号   
2 sortJoin(joinb,cloumj)#cloumj表示公共表在joinb的列号
3 i = j = 0#左右指针初试为0
4 while i < len(joina) and j < len(joinb):
5     if joina[cloumi] < joinb[j][cloumj]:
6         i += 1
7     elif joina[cloumi] > joinb[j][cloumj]:
8         j += 1
9     else:#相等,进行连接
10         lastj = j
11         while j < len(joinb) and joina[cloumi] == joinb[j][cloumj]:
12             temp = joina + joinb[j]
13             temp.remove(joina[cloumi])#删掉重复的元素               
14             mergeResult.append(temp)
15             j += 1
16         j = lastj#右指针回滚
17         i += 1
  我们分析一下这个算法的时间复杂度,首先要对两个表排序,复杂度为O(m1log(m1)),在扫描的过程中,右边指针会回溯,所以不再是O(max(m1,n1)),我们可以认为是k*O(m1*n1),这个系数k应该是很小的,因为一般右指针不会回溯太远,总的时间复杂度是O(m1log(m1))+k*O(m1*n1),应该是小于N方的复杂度。
  看一下执行的结果:



Input SQL:
select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME
from SUPPLIER,PART,PARTSUPP
where PS_PARTKEY = P_PARTKEY
and PS_SUPPKEY = S_SUPPKEY
and PS_AVAILQTY > 2000
and P_BRAND = 'Brand#12'
and S_NATIONKEY = 1;
{'FROM': ['SUPPLIER', 'PART', 'PARTSUPP'],
'GROUP': None,
'ORDER': None,
'SELECT': [['PARTSUPP.PS_AVAILQTY', None, None],
['PARTSUPP.PS_SUPPLYCOST', None, None],
['SUPPLIER.S_NAME', None, None]],
'WHERE': [['PARTSUPP.PS_AVAILQTY', '>', '2000'],
['PART.P_BRAND', '=', 'Brand#12'],
['SUPPLIER.S_NATIONKEY', '=', '1'],
['PARTSUPP.PS_PARTKEY', '=', 'PART.P_PARTKEY'],
['PARTSUPP.PS_SUPPKEY', '=', 'SUPPLIER.S_SUPPKEY']]}
Quering: PARTSUPP.PS_AVAILQTY > 2000
Quering: PART.P_BRAND = Brand#12
Quering: SUPPLIER.S_NATIONKEY = 1
Quering: PARTSUPP.PS_PARTKEY = PART.P_PARTKEY
Quering: PARTSUPP.PS_SUPPKEY = SUPPLIER.S_SUPPKEY
Output:
The result hava 1022 rows, here is the fisrt 10 rows:
-------------------------------------------------
rows     PARTSUPP.PS_AVAILQTY PARTSUPP.PS_SUPPLYCOST SUPPLIER.S_NAME
-------------------------------------------------
1             4925       854.19 Supplier#000002515
2             4588       455.04 Supplier#000005202
3             8830       852.13 Supplier#000007814
4             8948       689.89 Supplier#000002821
5             3870       488.38 Supplier#000005059
6             6968       579.03 Supplier#000005660
7             9269       228.31 Supplier#000000950
8             8818       180.32 Supplier#000003453
9             9343       785.01 Supplier#000003495
10             3364       545.25 Supplier#000006030
-------------------------------------------------
Take 50.42 seconds.
  这个查询的时间比Mysql快了很多,在mysql上运行这个查询需要10分钟(建立了索引),想想也是合理的,我们的设计已经大大简化了,完全不考虑表的修改,牺牲这么的实用性必然能提升在查询上的效率。
  二、group by分组
  在执行完where条件后,读取原始记录,然后可以按group by的属性分组,分组的属性可能有多条,比如这样一个查询:



select PS_AVAILQTY,PS_SUPPLYCOST,S_NAME,COUNT(*)
from SUPPLIER,PART,PARTSUPP
where PS_PARTKEY = P_PARTKEY
and PS_SUPPKEY = S_SUPPKEY
and PS_AVAILQTY > 2000
and P_BRAND = 'Brand#12'
and S_NATIONKEY = 1;
group by PS_AVAILQTY,PS_SUPPLYCOST,S_NAME;
  按 PS_AVAILQTY,PS_SUPPLYCOST,S_NAME这三个属性分组,我们实现时使用了一个技巧,将每个候选记录的这三个字段按字符串格式拼接成一个新的属性,拼接的示例如下:



"4925" "854.19" "Supplier#000002515" -->> "4925+854.19+Supplier#000002515"
    注意中间加了一个加号“+”,这个加号是必须的,如果没有加号,"105","201"与"10","5201"的拼接结果都是"105201",这样得到的group by结果将会出错,而添加一个加号它们两的拼接结果是不同的。
  拼接后,我们只需要按新的属性进行分组,可以使用map来实现,map的key为新的属性值,value为新属性值key的后续记录。再在组上进行聚集函数的运算。
  
  这个小项目就写到这里了,或许这压根只是一个数据处理,谈不上数据库实现,不过通过这个小项目我对数据库底层的实现还是了解了很多,以后做数据库优化理解起来也容易一些。
   谢谢关注,欢迎评论。   




作者:MyDetail
出处:http://www.iyunv.com/fengfenggirl/
本文版权归作者MyDetail和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


 

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-60894-1-1.html 上篇帖子: python模块之base64 下篇帖子: 使用 python 遍历目录下的文件
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表