使用数据的时候,一个数据项常常和另外的一个或多个数据项产生关系,比如一个“人”对象,有一个名字,可能有多个电话号码,以及多个子女,等等。
在传统的SQL数据库中,关系被分为一个个表(table),在表中,每个数据项以主键(primary key)标识,而一个表的主键又作为另一个表的外键(reference key),在两个表之间引用。当遇上多对多关系的时候,还需要一个额外的关联表(reference table),将多对多关系转化成两个一对多关系。
而在MongoDB中,表示关系有两种办法:
一种是嵌套(embedded),既是将一个文档包裹一个子文档;
而另一种是引用链接(reference link),使用MongoDB的DBRef对象建立文档和文档之间的关系。
除此之外,MongoDB的关系比起传统的SQL表关系更丰富一些,可以有 1对1 , 1对N , N对1 和 N对N 几种关系。
本文的目的就是探讨MongoDB表示关系的方法。
首先,让我们来看看MongoDB表示数据关系的两种方式:嵌套和引用链接。
嵌套
每个MongoDB文档都由BSON文档组成,有类似JSON格式一样的数据类型,其中String、Int、Float称为基本类型(或常量),而Hash和Array称之为复合类型。
所谓的嵌套,就是说文档中,利用复合类型,包裹一个多或多个其他类型的值,这些值称之为子文档。
文档嵌套的数量和深度没有限制,但MongoDB目前版本限制一个文档最大为16MB。
下面是一个典型的个人档案文档(profile),其中有一个 name 常量域,还嵌套了一个朋友(firends)子数组,数组里面每个项是一个字典。
1. >>> huangz
2. {'friends': [{'name': 'peter'}, {'name': 'john'}, {'name': 'marry'}], 'name': 'huangz'}
嵌套的好处是显而易见的:嵌套文档维持了数据逻辑上的完整性,可以将一整项数据作为一个整体来操纵。
对比在关系式的数据库中, 为了设计出符合范式的表,我们常常要将多个数据项分拆为几个表,然后通过外键获取数据。
当你阅读一个单独的表的数据时,你通常只看到了其中一部分数据,而其他的都是外键id,就像这样:
1. ['name': 'huangz', 'friends_reference_id': [12, 26, 30]]
这种数据给人的感觉就像打开了一本电话簿,却发现里面只有电话号码,没有联系人姓名,真是太糟糕了阿。
引用链接
比起嵌套,引用链接更接近传统意义上的(也就是,关系型数据库术语中的)“引用”,它是两个文档之间的一种关系。
引用链接通过DBRef对象建立,DBRef对象储存了如何找到目标文档的信息,就像现实世界中的门牌号码一样(也类似关系型数据库中的外键)。
如果在一个文档A中,有一个DBRef对象,而这个DBRef对象储存了关于如何找到文档B的信息,那么文档A就可以通过解释这个DBRef对象(称之为解引用)来获取文档B的数据。
下面是建立一个文档的引用,以及解引用的过程,这次我们同样表示一个一对多的朋友关系,但这次,我们用链接来建立关系。
1. >>> # 数据
2. >>> peter = {'name':'peter'}
3. >>> marry = {'name':'marry'}
4. >>> john = {'name':'john'}
5. >>>
6. >>> # 插入数据
7. >>> c.test.people.insert([peter, john, marry])
8. [ObjectId('4e98075224b7d408dc000004'), ObjectId('4e98075224b7d408dc000005'), ObjectId('4e98075224b7d408dc000006')]
9. >>>
10. >>> # 建立huangz文档,以及指向各个朋友的链接
11. >>> huangz = {'name': 'huangz',
12. ... 'friends': [ DBRef('people', peter['_id']),
13. ... DBRef('people', john['_id']),
14. ... DBRef('people', marry['_id']) ]}
15. >>>
16. >>> c.test.people.insert(huangz)
17. ObjectId('4e9807d924b7d408dc000007')
18. >>>
19. >>> # 查看huangz文档
20. >>> huangz
21. {'_id': ObjectId('4e9807d924b7d408dc000007'), 'friends': [DBRef('people', ObjectId('4e98075224b7d408dc000004')), DBRef('people', ObjectId('4e98075224b7d408dc000005')), DBRef('people', ObjectId('4e98075224b7d408dc000006'))], 'name': 'huangz'}
22. >>>
23. >>> # 对friends中的所有域进行解引用
24. >>> [ c.test.dereference(friend) for friend in huangz['friends'] ]
25. [{u'_id': ObjectId('4e98075224b7d408dc000004'), u'name': u'peter'}, {u'_id': ObjectId('4e98075224b7d408dc000005'), u'name': u'john'}, {u'_id': ObjectId('4e98075224b7d408dc000006'), u'name': u'marry'}]