转文:

本篇博客翻译自:

http://blog.mongodb.org/post/87200945828/6-rules-of-thumb-for-mongodb-schema-design-part-1?mkt_tok=3RkMMJWWfF9wsRonsq7Ldu%2FhmjTEU5z14uUsUKGxhokz2EFye%2BLIHETpodcMTcVnM7zYDBceEJhqyQJxPr3FLdcN0tJuRhTrCw%3D%3D

备注:本译文不是严格意义上的翻译,只是在基于对该原文的理解之上,尽可能表达清楚。如有疑问或不妥,请参考原文。

很多刚从传统SQL开发转向MongoDB开发的朋友都会问到一个问题:如何用MongoDB表达传统关系数据库中的一对多(1 to n)关系?

基于MongoDB丰富的表达力,我们不能说我们必须采用一个标准的方法来进行1 to n的建模。稍后我们从3个具体场景来展开讲解。

首先,我们将1 to n中的n进行场景细化。这个n究竟代表多大的量级呢?是几个到几十个?还是几个到几千个?还是成千上万个?

1) 1 to n(n代表好几个,或几十个,反正不太多)

比如每个Person会有多个Address。此种情况下,我们采用最简单的嵌入式文档来建模。

{
name: 'Kate Monster',
id: '123-456-7890',
addresses : [
{ street: '123 Sesame St', city: 'Anytown', cc: 'USA' },
{ street: '123 Avenue Q', city: 'New York', cc: 'USA' }
]
}

这种建模的方式包含了显而易见的优点和缺点:

优点:你不需要执行单独的查询就可以获得某个Person的所有Address信息。

缺点:你无法像操作独立文档那样来操作Address信息。你必须首先操作(比如查询)Person文档后,才有可能继续操作Address。

在本实例中,我们不需要对Address进行独立的操作,且Address信息只有在关联到某一个具体Person后才有意义。所以结论是:采用这种embedded(嵌入式)建模是非常适合Person-Address场景的。

2)1 to n(n代表好些个,比如几十个,甚至几百个)

比如产品(Product)和零部件(part),每个产品会有很多个零部件。这种场景下,我们可以采用引用方式来建模,如下:

零部件(Part):
{
_id : ObjectID('AAAA'),
partno : '123-aff-456',
name : '#4 grommet',
qty: 94,
cost: 0.94,
price: 3.99
}
 
产品(Product):
{
name : 'left-handed smoke shifter',
manufacturer : 'Acme Corp',
catalog_number: 1234,
parts : [ // array of references to Part documents
ObjectID('AAAA'), // reference to the #4 grommet above
ObjectID('F17C'), // reference to a different Part
ObjectID('D2AA'),
// etc
]
}

首先每个part作为单独的文档存在。每个产品中包含一个数组类型字段(parts),这个数组中存放的是所有该产品包含的零部件的编号(_id主键)。当你需要根据某一个产品编号查询该产品包含的所有部件信息时,你可以执行以下操作:

> product = db.products.findOne({catalog_number: 1234});
// Fetch all the Parts that are linked to this Product
> product_parts = db.parts.find({_id: { $in : product.parts } } ).toArray() ;

这种建模方式的优缺点也非常明显:

优点:部件是作为独立文档(document)存在的,你可以对某一部件进行独立的操作,比如查询或更新。

缺点:如上,你必须通过两次查询才能找到某一个产品所属的所有部件信息。

在本例中,这个缺点是可以接受的,本身实现起来也不难。而且,通过这种建模,你可以轻易的将1 to n扩展到n to n,即一个产品可以包含多个部件,同时一个部件也可以被多个产品所引用(即同一部件可以被多个产品使用)。

3)1 to n(这个n代表很大的数值,比如成千上万,甚至更大)

比如,每一个机器(host)会产生很大数量的日志信息(logmsg)。在这种情况下,如果你采用嵌入式建模,则一个host文档会非常庞大,从
而轻易超过MongoDB的文档大小限制,所以不可行。如果你采用第二中方式建模,用数组来存放所有logmsg的_id值,这种方式同样不可行,因为当
日止很多时,即使单单引用objectId也会轻易超过文档大小限制。所以此时,我们采用以下方式:

机器(hosts):
{
_id : ObjectID('AAAB'),
name : 'goofy.example.com',
ipaddr : '127.66.66.66'
}
 
日志(logmsg):
{
time : ISODate("2014-03-28T09:42:41.382Z"),
message : 'cpu is on fire!',
host: ObjectID('AAAB') // Reference to the Host document
}

我们在logsmg中,存放对host的_id引用即可。

综上所述,在对1 to n关系建模时,我们需要考虑:

1)n代表的数量级很小,且n代表的实体不需要单独操作时,可以采用嵌入式建模。

2)n代表的数量级比较大,或者n代表的实体需要单独进行操作时,采用在1中用Array存放引用的方式建模。

3)n代表的数量级非常大时,我们没有选择,只能在n端添加一个引用到1端。

MongoDB 一对多关系建模的更多相关文章

  1. 《Entity Framework 6 Recipes》翻译系列 (5) -----第二章 实体数据建模基础之有载荷和无载荷的多对多关系建模

    2-3 无载荷(with NO Payload)的多对多关系建模 问题 在数据库中,存在通过一张链接表来关联两张表的情况.链接表仅包含连接两张表形成多对多关系的外键,你需要把这两张多对多关系的表导入到 ...

  2. 数据库建模软件ERStudio-表关系建模详解

    ERStudio是优秀的数据库建模软件,它不仅可以建立表.视图等模型,还可以建立多表间各种关系的模型,另外还可以根据模型生成表到数据库,下面具体讲解一下它的表关系建模. 1. 首先讲一下怎么建立表关系 ...

  3. 《Entity Framework 6 Recipes》中文翻译系列 (10) -----第二章 实体数据建模基础之两实体间Is-a和Has-a关系建模、嵌入值映射

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 2-11 两实体间Is-a和Has-a关系建模 问题 你有两张有Is-a和Has-a ...

  4. 10.Configure One-to-Many(配置一对多关系)【Code-First系列】

    现在,我们将学习怎么配置一对多的关系. Visit Entity Relationship section to understand how EF manages one-to-one, one-t ...

  5. Mybatis框架中实现双向一对多关系映射

    学习过Hibernate框架的伙伴们很容易就能简单的配置各种映射关系(Hibernate框架的映射关系在我的blogs中也有详细的讲解),但是在Mybatis框架中我们又如何去实现 一对多的关系映射呢 ...

  6. [NHibernate]一对多关系(级联删除,级联添加)

    目录 写在前面 文档与系列文章 一对多关系 一个例子 级联删除 级联保存 总结 写在前面 在前面的文章中,我们只使用了一个Customer类进行举例,而在客户.订单.产品中它们的关系,咱们并没有涉及, ...

  7. [NHibernate]一对多关系(关联查询)

    目录 写在前面 文档与系列文章 一对多查询 总结 写在前面 上篇文章介绍了nhibernate的一对多关系如何配置,以及级联删除,级联添加数据的内容.这篇文章我们将学习nhibernate中的一对多关 ...

  8. [Fluent NHibernate]一对多关系处理

    目录 写在前面 系列文章 一对多关系 总结 写在前面 上篇文章简单介绍了,Fluent Nhibernate使用代码的方式生成Nhibernate的配置文件,以及如何生成持久化类的映射文件.通过上篇的 ...

  9. hibernate中一对多关系中的inverse,cascade属性

    举例说明: 一对多关系的两张表:boy.girl(一个男孩可以多个女朋友) boy表结构 Field   Type        ------  -----------  name    varcha ...

随机推荐

  1. Android——例子:屏幕切换

    效果图如下:                          Xml文件代码: <?xml version="1.0" encoding="utf-8" ...

  2. underscore的封装和扩展

    // 1. 不污染全局环境 (function() { // 2. 保留之前同名变量 var previousUnderscore = window._; var _ = function(obj) ...

  3. windows上配置git

    windows上配置git1.下载mysisigit进入http://msysgit.github.io/,下载,安装,下一步下一步即可. 2.下载tortoisegit进入http://downlo ...

  4. MySQl的几个配置项

    对对于MySQL的日志功能,我们可以完全自己控制到底写还是不写.一般来说,binlog我们一般会开启,而对于慢查询我们一般会在开发的时候调试和观 察SQL语句的执行速度.但今天发现一个问题.在使用sh ...

  5. C#泛型专题系列文章目录导航

    [C#泛型系列文章] 目录导航 第一部分:O'Reilly 出版的<C# Cookbook>泛型部分翻译 第一回:理解泛型 第二回:获取泛型类型和使用相应的泛型版本替换ArrayList ...

  6. chrome的input默认样式黄色背景以及选中加粗的边框处理

    问题描述: chrome输入用户名和密码,然后浏览器自己有记忆功能,等再次登录的时候,他会显示chrome默认的黄色背景色,还有选中时周围会有很粗的边框 解决方案: 去掉黄色背景,给input添加au ...

  7. 对于syncedmen类的代码分析

    对于数据在cpu与GPU之间同步的问题,caffe中用syncedMemory这个类来解 决:在GPU模式下,并且使用CUDA时,可以用CaffeMallocHost函数与CaffeFreeHost函 ...

  8. 如何查看与刷新DNS本地缓存

    如何查看与刷新DNS本地缓存 一.查看DNS本地缓存 在cmd窗口输入:ipconfig/displaydns 二.刷新DNS本地缓存 在cmd窗口输入:ipconfig/flushdns 之后输入: ...

  9. Android设置Activity启动和退出时的动画

    业务开发时遇到的一个小特技,要求实现Activity启动时自下向上弹出,退出时自上向下退出. 此处不关注启动和退出时其他Activity的动画效果,实现方法有两种: 1.代码方式,通过Activity ...

  10. FragmentPagerAdapter与FragmentStatePagerAdapter区别

    在一个 Android 应用中,我使用 FragmentPagerAdapter 来处理多 Fragment 页面的横向滑动.不过我碰到了一个问题,即当 Fragment 对应的数据集发生改变时,我希 ...