我如何在MongoDB中执行SQL Join等效?

我如何在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"  }

是否有一种方法可以一次性提取具有特定字段的所有评论(例如. ...find({pid:444}))和与每个评论相关的用户信息?

目前,我首先获取符合条件的注释,然后找出结果集中的所有uid,获取用户对象,并将它们与注释的结果合并。看来我做错了。

512861 次浏览

你必须按照你描述的方法去做。MongoDB是非关系数据库,不支持连接。

有一个很多驱动程序都支持的规范叫做DBRef。

DBRef是用于在文档之间创建引用的更正式的规范。DBRefs(通常)包括集合名称和对象id。大多数开发人员只在集合可以从一个文档更改到下一个文档时才使用DBRefs。如果您引用的集合总是相同的,那么上面概述的手动引用更有效。

摘自MongoDB文档:数据模型>数据模型参考> # EYZ0 < / em > < / p >

mongodb官方网站上的这个页面解决了完全这个问题:

https://mongodb-documentation.readthedocs.io/en/latest/ecosystem/tutorial/model-data-for-ruby-on-rails.html

当我们显示故事列表时,我们需要显示发布故事的用户的名称。如果使用关系数据库,则可以对用户和存储执行连接,并在单个查询中获得所有对象。但是MongoDB不支持连接,因此,有时需要一些非规范化。这里,这意味着缓存'username'属性。

关系纯粹主义者可能已经感到不安,好像我们违反了一些普遍的法则。但是我们要记住,MongoDB集合并不等同于关系表;每一个都服务于一个独特的设计目标。规范化表提供原子的、孤立的数据块。然而,文档更紧密地表示对象作为一个整体。在一个社会新闻网站的情况下,可以认为用户名是内在的故事被发布。

下面是一个“加入” * 演员电影集合的例子:

https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt

它使用了.mapReduce()方法

*加入 -加入面向文档的数据库的替代方案

playORM可以为您使用S-SQL(可伸缩SQL),它只是添加分区,这样您就可以在分区内进行连接。

你可以使用Postgres的mongo_fdw在MongoDB上运行SQL查询,包括join。

这取决于你想做什么。

目前您已经将其设置为规范化数据库,这很好,而且您的操作方式也很合适。

然而,还有其他的方法。

您可以有一个帖子集合,其中为每个帖子嵌入了评论,并引用了您可以迭代查询以获得的用户。您可以将用户名与注释一起存储,您可以将它们都存储在一个文档中。

NoSQL的特点在于它是为灵活的模式和非常快的读写而设计的。在典型的大数据场中,数据库是最大的瓶颈,数据库引擎比应用程序和前端服务器要少……它们更贵,但功能更强大,而且硬盘空间相对来说非常便宜。规范化来自于试图节省空间的概念,但它带来了成本,使数据库执行复杂的join和验证关系的完整性,执行级联操作。如果正确地设计数据库,所有这些都为开发人员节省了一些麻烦。

对于NoSQL,如果你接受冗余和存储空间不是问题,因为它们的成本(更新所需的处理器时间和存储额外数据的硬盘成本),反规格化不是问题(对于包含数十万项的嵌入式数组来说,这可能是一个性能问题,但大多数时候这不是问题)。此外,每个数据库集群都有几个应用程序和前端服务器。让它们完成连接的繁重工作,而让数据库服务器坚持读写。

TL;DR:你现在做的很好,还有其他的方法。查看mongodb文档的数据模型模式以获得一些很棒的示例。# EYZ0

我们可以使用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子查询合并两个集合。举个例子, 评论——< / p >
`db.commentss.insert([
{ uid:12345, pid:444, comment:"blah" },
{ uid:12345, pid:888, comment:"asdf" },
{ uid:99999, pid:444, comment:"qwer" }])`

用户——

db.userss.insert([
{ uid:12345, name:"john" },
{ uid:99999, name:"mia"  }])

MongoDB子查询JOIN——

`db.commentss.find().forEach(
function (newComments) {
newComments.userss = db.userss.find( { "uid": newComments.uid } ).toArray();
db.newCommentUsers.insert(newComments);
}
);`

从新生成的Collection中获取结果

db.newCommentUsers.find().pretty()

结果——

`{
"_id" : ObjectId("5511236e29709afa03f226ef"),
"uid" : 12345,
"pid" : 444,
"comment" : "blah",
"userss" : [
{
"_id" : ObjectId("5511238129709afa03f226f2"),
"uid" : 12345,
"name" : "john"
}
]
}
{
"_id" : ObjectId("5511236e29709afa03f226f0"),
"uid" : 12345,
"pid" : 888,
"comment" : "asdf",
"userss" : [
{
"_id" : ObjectId("5511238129709afa03f226f2"),
"uid" : 12345,
"name" : "john"
}
]
}
{
"_id" : ObjectId("5511236e29709afa03f226f1"),
"uid" : 99999,
"pid" : 444,
"comment" : "qwer",
"userss" : [
{
"_id" : ObjectId("5511238129709afa03f226f3"),
"uid" : 99999,
"name" : "mia"
}
]
}`

希望这能有所帮助。

MongoDB不允许连接,但是你可以使用插件来处理。检查mongo-join插件。这是最好的,我已经用过了。你可以像npm install mongo-join这样直接使用npm安装它。你可以看看包含示例的完整文档

(++)非常有用的工具,当我们需要加入(N)个集合

(——)我们可以只在查询的顶层应用条件

例子

var Join = require('mongo-join').Join, mongodb = require('mongodb'), Db = mongodb.Db, Server = mongodb.Server;
db.open(function (err, Database) {
Database.collection('Appoint', function (err, Appoints) {


/* we can put conditions just on the top level */
Appoints.find({_id_Doctor: id_doctor ,full_date :{ $gte: start_date },
full_date :{ $lte: end_date }}, function (err, cursor) {
var join = new Join(Database).on({
field: '_id_Doctor', // <- field in Appoints document
to: '_id',         // <- field in User doc. treated as ObjectID automatically.
from: 'User'  // <- collection name for User doc
}).on({
field: '_id_Patient', // <- field in Appoints doc
to: '_id',         // <- field in User doc. treated as ObjectID automatically.
from: 'User'  // <- collection name for User doc
})
join.toArray(cursor, function (err, joinedDocs) {


/* do what ever you want here */
/* you can fetch the table and apply your own conditions */
.....
.....
.....




resp.status(200);
resp.json({
"status": 200,
"message": "success",
"Appoints_Range": joinedDocs,




});
return resp;




});


});
正如其他人指出的那样,你正试图从没有关系数据库创建一个关系数据库,你真的不想这样做,但无论如何,如果你有一个情况,你必须这样做,这里是一个解决方案,你可以使用。我们首先在集合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
}
});

不,看起来你并没有做错。MongoDB连接是“客户端”。就像你说的

目前,我首先获取符合条件的注释,然后找出结果集中的所有uid,获取用户对象,并将它们与注释的结果合并。看来我做错了。

1) Select from the collection you're interested in.
2) From that collection pull out ID's you need
3) Select from other collections
4) Decorate your original results.

它不是一个“真正的”连接,但它实际上比SQL连接有用得多,因为您不必处理“多”面连接的重复行,而是修饰最初选择的集合。

这一页上有很多废话和FUD。结果5年后,MongoDB仍然存在。

我认为,如果你需要规范化的数据表-你需要尝试一些其他的数据库解决方案。

但我已经找到解决方案MOngo上Git 顺便说一下,在插入代码-它有电影的名字,但没有电影的ID.

问题

你有一个演员集合和他们所做的电影数组。

您希望生成一个Movies集合,每个Movies中都包含一个actor数组。

一些示例数据

 db.actors.insert( { actor: "Richard Gere", movies: ['Pretty Woman', 'Runaway Bride', 'Chicago'] });
db.actors.insert( { actor: "Julia Roberts", movies: ['Pretty Woman', 'Runaway Bride', 'Erin Brockovich'] });

解决方案

我们需要循环遍历Actor文档中的每个电影,并分别发出每个电影。

这里的问题是在减少阶段。我们不能从reduce阶段发出一个数组,因此必须在返回的“value”文档中构建一个Actors数组。

的代码
map = function() {
for(var i in this.movies){
key = { movie: this.movies[i] };
value = { actors: [ this.actor ] };
emit(key, value);
}
}


reduce = function(key, values) {
actor_list = { actors: [] };
for(var i in values) {
actor_list.actors = values[i].actors.concat(actor_list.actors);
}
return actor_list;
}

注意,actor_list实际上是一个包含数组的javascript对象。还要注意map发出相同的结构。

执行以下命令执行map / reduce,将其输出到“pivot”集合并打印结果:

< p > printjson (db.actors。mapReduce(map, reduce, "pivot")); db.pivot.find () .forEach (printjson); < / p >

以下是输出示例,请注意《风月俏佳人》和《逃跑新娘》中都有“理查德·基尔”和“茱莉亚·罗伯茨”。

{ "_id" : { "movie" : "Chicago" }, "value" : { "actors" : [ "Richard Gere" ] } }
{ "_id" : { "movie" : "Erin Brockovich" }, "value" : { "actors" : [ "Julia Roberts" ] } }
{ "_id" : { "movie" : "Pretty Woman" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } }
{ "_id" : { "movie" : "Runaway Bride" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } }

从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>
}
}

当然,MongoDB是关系数据库,开发人员正在谨慎地推荐$lookup的特定用例,但至少在3.2时,使用MongoDB可以进行连接。

你可以在Mongo中使用3.2版本提供的查找来连接两个集合。在您的情况下,查询将是

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中的左连接和右连接一样。

3.2.6之前,Mongodb不像mysql那样支持连接查询。下面是适合你的解决方案。

 db.getCollection('comments').aggregate([
{$match : {pid : 444}},
{$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}},
])

使用美元的查找美元的项目$匹配的正确组合,您可以在多个参数上连接多个表。这是因为它们可以被链接多次。

假设我们想要执行以下操作(参考)

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:链接所有表

您可以根据需要查找任意数量的表。

美元的查找 -查询中的每个表一个

美元的放松 -正确地反规格化数据,否则它将被包装在数组中

Python代码. .

db.LeftTable.aggregate([
# connect all tables


{"$lookup": {
"from": "RightTable",
"localField": "ID",
"foreignField": "ID",
"as": "R"
}},
{"$unwind": "R"}
                   

])

步骤2:定义所有条件

美元的项目:定义这里所有的条件语句,加上所有你想选择的变量。

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
}}
])

第三步:连接所有的条件句

$匹配 -使用OR或AND等连接所有条件可以有很多个。

美元的项目:取消所有条件

完整的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
}}


])

几乎任何表、条件和连接的组合都可以用这种方式完成。

您可以使用聚合管道来实现它,但是自己编写它很麻烦。

您可以使用mongo-join-query从您的查询自动创建聚合管道。

这是你的查询的样子:

const mongoose = require("mongoose");
const joinQuery = require("mongo-join-query");


joinQuery(
mongoose.models.Comment,
{
find: { pid:444 },
populate: ["uid"]
},
(err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);

您的结果将在uid字段中有用户对象,您可以链接任意多的层次。您可以填充对用户的引用,从而引用一个Team,再引用其他东西,等等。

免责声明:我写了mongo-join-query来解决这个问题。

查找美元(聚合)

对同一数据库中的未分片集合执行左外连接,以从“已连接”集合中筛选文档进行处理。$查找阶段向每个输入文档添加一个新的数组字段,其元素是“已加入”集合中的匹配文档。$查找阶段将这些重新塑造的文档传递给下一个阶段。 $查找阶段的语法如下:

平等的比赛

要在输入文档中的字段与" joined "集合中的文档中的字段之间执行相等匹配,$lookup stage的语法如下:

{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}

该操作将对应于以下伪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> );

Mongo URL