如何在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" }
Run Code Online (Sandbox Code Playgroud)
有没有办法用一个字段拉出所有评论(例如......查找({pid:444}))以及与每个评论相关的用户信息?
目前,我首先得到符合我标准的评论,然后找出该结果集中的所有uid,获取用户对象,并将它们与评论的结果合并.好像我做错了.
Cla*_*ick 286
从Mongo 3.2开始,这个问题的答案大多不再正确.添加到聚合管道的新$ lookup运算符与左外连接基本相同:
https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup
来自文档:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
Run Code Online (Sandbox Code Playgroud)
当然,Mongo 不是一个关系数据库,并且开发人员正在谨慎地推荐$ lookup的特定用例,但至少从3.2开始就可以使用MongoDB进行连接.
Wil*_*ein 140
官方mongodb网站上的这个页面正好解决了这个问题:
http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails/
当我们显示故事列表时,我们需要显示发布故事的用户的姓名.如果我们使用关系数据库,我们可以在用户和商店上执行连接,并在单个查询中获取所有对象.但是MongoDB不支持连接,因此有时需要一些非规范化.在这里,这意味着缓存'username'属性.
关系纯粹主义者可能已经感到不安,好像我们违反了一些普遍的法律.但是请记住,MongoDB集合不等同于关系表; 每个都有一个独特的设计目标.规范化表提供原子的,孤立的数据块.然而,文档更接近地代表整个对象.在社交新闻网站的情况下,可以认为用户名是发布的故事所固有的.
小智 134
我们可以使用mongodb客户端控制台将只有一行的简单函数合并/加入一个集合中的所有数据,现在我们可以执行所需的查询.下面是一个完整的例子
.-作者:
db.authors.insert([
{
_id: 'a1',
name: { first: 'orlando', last: 'becerra' },
age: 27
},
{
_id: 'a2',
name: { first: 'mayra', last: 'sanchez' },
age: 21
}
]);
Run Code Online (Sandbox Code Playgroud)
.-分类:
db.categories.insert([
{
_id: 'c1',
name: 'sci-fi'
},
{
_id: 'c2',
name: 'romance'
}
]);
Run Code Online (Sandbox Code Playgroud)
.-书籍
db.books.insert([
{
_id: 'b1',
name: 'Groovy Book',
category: 'c1',
authors: ['a1']
},
{
_id: 'b2',
name: 'Java Book',
category: 'c2',
authors: ['a1','a2']
},
]);
Run Code Online (Sandbox Code Playgroud)
.-图书借阅
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'
}
]);
Run Code Online (Sandbox Code Playgroud)
.- 魔法:
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);
}
);
Run Code Online (Sandbox Code Playgroud)
.-获取新的收集数据:
db.booksReloaded.find().pretty()
Run Code Online (Sandbox Code Playgroud)
.-回应:)
{
"_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" : [ ]
}
Run Code Online (Sandbox Code Playgroud)
我希望这条线可以帮到你.
Ott*_*ger 40
你必须按照你描述的方式去做.MongoDB是一个非关系型数据库,不支持连接.
ant*_*xic 17
这是一个"加入"*演员和电影集合的例子:
https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt
它利用了.mapReduce()方法
*join - 加入面向文档的数据库的替代方案
gre*_*pit 17
正如其他人已经指出你试图从无关系数据库创建一个关系数据库,你真的不想这样做,但是如果你有一个案例你必须这样做,这是一个你可以使用的解决方案.我们首先在集合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
}
});
Run Code Online (Sandbox Code Playgroud)
Sha*_*rti 16
通过$ 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
Run Code Online (Sandbox Code Playgroud)
第1步:链接所有表
您可以根据需要查找任意数量的表.
$ lookup - 查询中每个表一个
$ unwind - 因为数据是非正规化的,否则包装在数组中
Python代码..
db.LeftTable.aggregate([
# connect all tables
{"$lookup": {
"from": "RightTable",
"localField": "ID",
"foreignField": "ID",
"as": "R"
}},
{"$unwind": "R"}
])
Run Code Online (Sandbox Code Playgroud)
第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
}}
])
Run Code Online (Sandbox Code Playgroud)
第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
}}
])
Run Code Online (Sandbox Code Playgroud)
几乎任何表,条件和连接的组合都可以这种方式完成.
Sno*_*rnt 11
这取决于你想要做什么.
您目前已将其设置为规范化数据库,这很好,并且您采用的方式也是合适的.
但是,还有其他方法可以做到这一点.
您可以拥有一个帖子集合,其中包含对每个帖子的嵌入评论,并引用您可以迭代查询的用户.您可以将用户的名称与注释一起存储,您可以将它们全部存储在一个文档中.
NoSQL的用途是它设计用于灵活的模式和非常快速的读写.在一个典型的大数据农场中,数据库是最大的瓶颈,你拥有的数据库引擎比应用程序和前端服务器少......它们更昂贵但功能更强大,硬盘空间也相对便宜.规范化来自于试图节省空间的概念,但它带来了使数据库执行复杂连接和验证关系完整性,执行级联操作的成本.如果他们正确地设计数据库,所有这些都会使开发人员感到头疼.
使用NoSQL,如果您认为冗余和存储空间不是问题,因为它们的成本(更新所需的处理器时间和存储额外数据的硬盘驱动器成本),非规范化不是问题(对于嵌入式阵列而言)数十万个项目可能是性能问题,但大多数时候这不是问题).此外,您将为每个数据库集群提供多个应用程序和前端服务器.让他们完成连接的繁重工作,让数据库服务器坚持读写.
TL; DR:你正在做的事情很好,还有其他方法可以做到.查看mongodb文档的数据模型模式以获取一些很好的示例.http://docs.mongodb.org/manual/data-modeling/
jar*_*ery 11
您可以使用3.2版本中提供的查找在Mongo中加入两个集合.在您的情况下,查询将是
db.comments.aggregate({
$lookup:{
from:"users",
localField:"uid",
foreignField:"uid",
as:"users_comments"
}
})
Run Code Online (Sandbox Code Playgroud)
或者您也可以加入用户,然后会有一点变化,如下所示.
db.users.aggregate({
$lookup:{
from:"comments",
localField:"uid",
foreignField:"uid",
as:"users_comments"
}
})
Run Code Online (Sandbox Code Playgroud)
它将与SQL中的左右连接一样工作.
对同一数据库中的未分片集合执行左外部联接,以从“联接”集合中过滤文档以进行处理。在每个输入文档中,$ lookup阶段都会添加一个新的数组字段,其元素是“ joined”集合中的匹配文档。$ lookup阶段将这些经过重整的文档传递到下一个阶段。$ lookup阶段具有以下语法:
为了在输入文档的字段与“ joined”集合的文档的字段之间执行相等匹配,$ lookup阶段具有以下语法:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
Run Code Online (Sandbox Code Playgroud)
该操作将对应于以下伪SQL语句:
SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT <documents as determined from the pipeline>
FROM <collection to join>
WHERE <pipeline> );
Run Code Online (Sandbox Code Playgroud)
在3.2.6之前,Mongodb不像mysql那样支持连接查询.以下解决方案适合您.
db.getCollection('comments').aggregate([
{$match : {pid : 444}},
{$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}},
])
Run Code Online (Sandbox Code Playgroud)