如何在MongoDB中执行SQL Join等效项?
例如,假设你有两个集合(用户和评论),我想用pid = 444以及每个集合的用户信息来提取所有评论.
comments { uid:12345, pid:444, comment="blah" } { uid:12345, pid:888, comment="asdf" } { uid:99999, pid:444, comment="qwer" } users { uid:12345, name:"john" } { uid:99999, name:"mia" }
有没有办法用一个字段拉出所有评论(例如......查找({pid:444}))以及与每个评论相关的用户信息?
目前,我首先得到符合我标准的评论,然后找出该结果集中的所有uid,获取用户对象,并将它们与评论的结果合并.好像我做错了.
从Mongo 3.2开始,这个问题的答案大多不再正确.添加到聚合管道的新$ lookup运算符与左外连接基本相同:
https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup
来自文档:
{ $lookup: { from:, localField: , foreignField: , as:
当然,Mongo 不是一个关系数据库,并且开发人员正在谨慎地推荐$ lookup的特定用例,但至少从3.2开始就可以使用MongoDB进行连接.
官方mongodb网站上的这个页面正好解决了这个问题:
http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails/
当我们显示故事列表时,我们需要显示发布故事的用户的姓名.如果我们使用关系数据库,我们可以在用户和商店上执行连接,并在单个查询中获取所有对象.但是MongoDB不支持连接,因此有时需要一些非规范化.在这里,这意味着缓存'username'属性.
关系纯粹主义者可能已经感到不安,好像我们违反了一些普遍的法律.但是请记住,MongoDB集合不等同于关系表; 每个都有一个独特的设计目标.规范化表提供原子的,孤立的数据块.然而,文档更接近地代表整个对象.在社交新闻网站的情况下,可以认为用户名是发布的故事所固有的.
我们可以使用mongodb客户端控制台将只有一行的简单函数合并/加入一个集合中的所有数据,现在我们可以执行所需的查询.下面是一个完整的例子
.-作者:
db.authors.insert([ { _id: 'a1', name: { first: 'orlando', last: 'becerra' }, age: 27 }, { _id: 'a2', name: { first: 'mayra', last: 'sanchez' }, age: 21 } ]);
.-分类:
db.categories.insert([ { _id: 'c1', name: 'sci-fi' }, { _id: 'c2', name: 'romance' } ]);
.-书籍
db.books.insert([ { _id: 'b1', name: 'Groovy Book', category: 'c1', authors: ['a1'] }, { _id: 'b2', name: 'Java Book', category: 'c2', authors: ['a1','a2'] }, ]);
.-图书借阅
db.lendings.insert([ { _id: 'l1', book: 'b1', date: new Date('01/01/11'), lendingBy: 'jose' }, { _id: 'l2', book: 'b1', date: new Date('02/02/12'), lendingBy: 'maria' } ]);
.- 魔法:
db.books.find().forEach( function (newBook) { newBook.category = db.categories.findOne( { "_id": newBook.category } ); newBook.lendings = db.lendings.find( { "book": newBook._id } ).toArray(); newBook.authors = db.authors.find( { "_id": { $in: newBook.authors } } ).toArray(); db.booksReloaded.insert(newBook); } );
.-获取新的收集数据:
db.booksReloaded.find().pretty()
.-回应:)
{ "_id" : "b1", "name" : "Groovy Book", "category" : { "_id" : "c1", "name" : "sci-fi" }, "authors" : [ { "_id" : "a1", "name" : { "first" : "orlando", "last" : "becerra" }, "age" : 27 } ], "lendings" : [ { "_id" : "l1", "book" : "b1", "date" : ISODate("2011-01-01T00:00:00Z"), "lendingBy" : "jose" }, { "_id" : "l2", "book" : "b1", "date" : ISODate("2012-02-02T00:00:00Z"), "lendingBy" : "maria" } ] } { "_id" : "b2", "name" : "Java Book", "category" : { "_id" : "c2", "name" : "romance" }, "authors" : [ { "_id" : "a1", "name" : { "first" : "orlando", "last" : "becerra" }, "age" : 27 }, { "_id" : "a2", "name" : { "first" : "mayra", "last" : "sanchez" }, "age" : 21 } ], "lendings" : [ ] }
我希望这条线可以帮到你.
你必须按照你描述的方式去做.MongoDB是一个非关系型数据库,不支持连接.
这是一个"加入"*演员和电影集合的例子:
https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt
它利用了.mapReduce()
方法
*join - 加入面向文档的数据库的替代方案
正如其他人已经指出你试图从无关系数据库创建一个关系数据库,你真的不想这样做,但是如果你有一个案例你必须这样做,这是一个你可以使用的解决方案.我们首先在集合A(或在你的情况下是用户)中进行foreach查找然后我们将每个项目作为对象然后我们使用对象属性(在你的情况下为uid)来查找我们的第二个集合(在你的案例评论中)如果我们可以找到它然后我们有一个匹配,我们可以打印或做一些事情.希望这能帮助你,祝你好运:)
db.users.find().forEach( function (object) { var commonInBoth=db.comments.findOne({ "uid": object.uid} ); if (commonInBoth != null) { printjson(commonInBoth) ; printjson(object) ; }else { // did not match so we don't care in this case } });
通过$ lookup,$ project和$ match的正确组合,您可以在多个参数上加入多个表.这是因为它们可以链接多次.
假设我们想做以下(参考)
SELECT S.* FROM LeftTable S LEFT JOIN RightTable R ON S.ID =R.ID AND S.MID =R.MID WHERE R.TIM >0 AND S.MOB IS NOT NULL
第1步:链接所有表
您可以根据需要查找任意数量的表.
$ lookup - 查询中每个表一个
$ unwind - 因为数据是非正规化的,否则包装在数组中
Python代码..
db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "R"} ])
第2步:定义所有条件
$ project:在此定义所有条件语句,以及您要选择的所有变量.
Python代码..
db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "R"}, # define conditionals + variables {"$project": { "midEq": {"$eq": ["$MID", "$R.MID"]}, "ID": 1, "MOB": 1, "MID": 1 }} ])
第3步:加入所有条件
$ match - 使用OR或AND等加入所有条件.可以有多个这些条件.
$ project:取消定义所有条件
Python代码..
db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "$R"}, # define conditionals + variables {"$project": { "midEq": {"$eq": ["$MID", "$R.MID"]}, "ID": 1, "MOB": 1, "MID": 1 }}, # join all conditionals {"$match": { "$and": [ {"R.TIM": {"$gt": 0}}, {"MOB": {"$exists": True}}, {"midEq": {"$eq": True}} ]}}, # undefine conditionals {"$project": { "midEq": 0 }} ])
几乎任何表,条件和连接的组合都可以这种方式完成.
这取决于你想要做什么.
您目前已将其设置为规范化数据库,这很好,并且您采用的方式也是合适的.
但是,还有其他方法可以做到这一点.
您可以拥有一个帖子集合,其中包含对每个帖子的嵌入评论,并引用您可以迭代查询的用户.您可以将用户的名称与注释一起存储,您可以将它们全部存储在一个文档中.
NoSQL的用途是它设计用于灵活的模式和非常快速的读写.在一个典型的大数据农场中,数据库是最大的瓶颈,你拥有的数据库引擎比应用程序和前端服务器少......它们更昂贵但功能更强大,硬盘空间也相对便宜.规范化来自于试图节省空间的概念,但它带来了使数据库执行复杂连接和验证关系完整性,执行级联操作的成本.如果他们正确地设计数据库,所有这些都会使开发人员感到头疼.
使用NoSQL,如果您认为冗余和存储空间不是问题,因为它们的成本(更新所需的处理器时间和存储额外数据的硬盘驱动器成本),非规范化不是问题(对于嵌入式阵列而言)数十万个项目可能是性能问题,但大多数时候这不是问题).此外,您将为每个数据库集群提供多个应用程序和前端服务器.让他们完成连接的繁重工作,让数据库服务器坚持读写.
TL; DR:你正在做的事情很好,还有其他方法可以做到.查看mongodb文档的数据模型模式以获取一些很好的示例.http://docs.mongodb.org/manual/data-modeling/
您可以使用3.2版本中提供的查找在Mongo中加入两个集合.在您的情况下,查询将是
db.comments.aggregate({ $lookup:{ from:"users", localField:"uid", foreignField:"uid", as:"users_comments" } })
或者您也可以加入用户,然后会有一点变化,如下所示.
db.users.aggregate({ $lookup:{ from:"comments", localField:"uid", foreignField:"uid", as:"users_comments" } })
它将与SQL中的左右连接一样工作.
有一个规范,许多驱动程序支持称为DBRef.
DBRef是用于在文档之间创建引用的更正式的规范.DBRefs(通常)包括集合名称和对象ID.如果集合可以从一个文档更改为下一个文档,则大多数开发人员仅使用DBRef.如果您引用的集合始终相同,则上述手动引用更有效.
摘自MongoDB文档:数据模型>数据模型参考> 数据库参考
对同一数据库中的未分片集合执行左外部联接,以从“联接”集合中过滤文档以进行处理。在每个输入文档中,$ lookup阶段都会添加一个新的数组字段,其元素是“ joined”集合中的匹配文档。$ lookup阶段将这些经过重整的文档传递到下一个阶段。$ lookup阶段具有以下语法:
为了在输入文档的字段与“ joined”集合的文档的字段之间执行相等匹配,$ lookup阶段具有以下语法:
{ $lookup: { from:, localField: , foreignField: , as:
该操作将对应于以下伪SQL语句:
SELECT *,
Mongo URL
在3.2.6之前,Mongodb不像mysql那样支持连接查询.以下解决方案适合您.
db.getCollection('comments').aggregate([ {$match : {pid : 444}}, {$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}}, ])