SQL与NoSQL最大的不一样之一就是不支持JOIN,在传统的数据库中,SQL JOIN子句容许你使用普通的字段,在两个或者是更多表中的组合表中的每行数据。例如,若是你有表books和publishers
,你能够像下面这样写命令:
sql
SELECT book.title, publisher.name
FROM book
LEFT JOIN book.publisher_id ON publisher.id;
换句话说,book表中的publisher_id字段引用了publishers
表中的id字典。这些都是很常见的例子:对于每一个
mongodbpublisher均可以拥有成千上万本书,若是你想更新
publisher的信息的时候,咱们只须要更改一条记录。数据的冗余是很小的,由于咱们不须要为每本书来重复更新他的publisher信息,这种技术已基本当作一种规范化的东西了。SQL数据库提供了一些列的规范与约束条件来保障数据关联性。
并不都是这样吧。。。。。数据库
面向文档的数据库,例如MongoDB,被设计用来存储非结构化的数据,理想状况下,这些数据是在数据集合中是相互没有关联的,若是一条数据包含两次或者更屡次,那数据就重复了。由于大部分状况下咱们仍是须要数据关联的,只有不多的状况下才会不须要关联数据,数组
,看来NoSQL这些特性看来让人失望啊。幸运的是MongoDB 3.2 介绍了一个新的$lookup操做,这个操做能够提供一个相似于LEFT OUTER JOIN的操做在两个或者是更多的条件下。nosql
$lookup仅仅在 aggregation操做中才被容许使用,想一想他做为一个管道操做:查询,过滤,组合结果。一个操做的输出被做为下一个的输入。Aggregation比简单的查询操做更难于理解,并且这些操做一般运行很慢,然而他们很高效,Aggregation可使用一个很好的例子来解释,假设咱们使用user数据集合来建立一个社交平台,在每一个独立的文档中存储没个用户的信息,例如:post
{ "_id": ObjectID("45b83bda421238c76f5c1969"), "name": "User One", "email: "userone@email.com", "country": "UK", "dob": ISODate("1999-09-13T00:00:00.000Z") }
咱们能够向user这个集合中添加足够多的用户,可是每一个MongoDB文档都必须有一个为一个_id字段值,这个_id字段值就像SQL中的键,在咱们没有明确指定_id的时候会被自动的加入到文档中。咱们的社交网站如今须要一个post集合,这个结合存储用户的评论,这个文档存储纯文本,时间,评分,一个被写到user_id字段的玩家引用。网站
{ "_id": ObjectID("17c9812acff9ac0bba018cc1"), "user_id": ObjectID("45b83bda421238c76f5c1969"), "date: ISODate("2016-09-05T03:05:00.123Z"), "text": "My life story so far", "rating": "important" }
咱们如今想要显示最近具备important评论的二十条数据,这些数据来自全部的用户,而且是按照时间排序的。每个返回的文档中应该包含评论的文本,发布评论的时间,以及相关的用户的名字和国家。spa
MongoDB数据库的aggregate查询是经过传递管道操做的数组,这个数组中顺序的定了每一个操做。首先,咱们须要从全部的post集合中提取出全部的文档,这些文档使用$match记性准确rating过滤。设计
{ "$match": { "rating": "important" } }
咱们如今须要对过滤出来的文档按照时间,使用$sort操做进行排序。code
{ "$sort": { "date": -1 } }
由于咱们要仅仅返回二十条数据,咱们可使用$limit来限制咱们须要处理的文档数量。
{ "$limit": 20 }
咱们如今使用$lookup操做从user集合中链接数据,这个操做须要一个四个参数的对象:
一、localField:在输入文档中的查找字段
二、from:须要链接的集合
三、foreignField:须要在from集合中查找的字段
四、as:输出的字段名字
因此咱们的操做是这样的:
{ "$lookup": { "localField": "user_id", "from": "user", "foreignField": "_id", "as": "userinfo" } }
在咱们的输出中将会建立一个名为userinfo的新字段,他是一个数组,其中每一个元素都是在user集合中匹配的元素。
"userinfo": [ { "name": "User One", ... } ]
在post.user_id与user._id之间,咱们具备一对一的关系,由于对于每个post只有一个用户。所以咱们的userinfo数组将会仅仅包含一个元素,咱们能够说使用 $unwind操做来解构他并插入到一个自文档中。
{ "$unwind": "$userinfo" }
如今的输出将会转化成更加经常使用的结构:
"userinfo": { "name": "User One", "email: "userone@email.com", … }
最终咱们能够在管道中使用 $project操做
返回评论信息,评论的时间,评论的用户名,国家等。
{ "$project": { "text": 1, "date": 1, "userinfo.name": 1, "userinfo.country": 1 } }
合并上面全部的操做
咱们最终的聚合查询匹配的评论,按照顺序排序,限制最新的二十条信息,链接用户的数据,扁平用户数组,最后只返回咱们须要的必须数据,总的命令以下:
db.post.aggregate([ { "$match": { "rating": "important" } }, { "$sort": { "date": -1 } }, { "$limit": 20 }, { "$lookup": { "localField": "user_id", "from": "user", "foreignField": "_id", "as": "userinfo" } }, { "$unwind": "$userinfo" }, { "$project": { "text": 1, "date": 1, "userinfo.name": 1, "userinfo.country": 1 } } ]);
结果是一个拥有二十个文档的集合,例如:
[ { "text": "The latest post", "date: ISODate("2016-09-27T00:00:00.000Z"), "userinfo": { "name": "User One", "country": "UK" } }, { "text": "Another post", "date: ISODate("2016-09-26T00:00:00.000Z"), "userinfo": { "name": "User One", "country": "UK" } } ... ]
MongoDB的$lookup很好用并且很高效,可是上面这个基础的例子只是一个组合的集合查询。他不是一个对SQL中的更加高效的JOIN子句的替代。并且MongoDB也提供了一些限制,若是user集合被删除了,post文档仍是会保留。
理想状况下,这个$lookup操做应该不会常用,若是你须要常用它,那么你就使用了错误的数据存储了(数据库):若是你有相关联的数据,应该使用关联数据库(SQL)。
也就是说$lookup是一个MongoDB 3.2新加入的,他解决了当在Nosql数据库中使用一些小的相关联的数据查询的时候一些使人失望的问题。