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

时间:2010-02-28 08:15:43

标签: mongodb 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"  }

有没有办法用一个字段拉出所有评论(例如......查找({pid:444}))以及与每个评论相关的用户信息?

目前,我首先得到符合我标准的评论,然后找出该结果集中的所有uid,获取用户对象,并将它们与评论的结果合并。好像我做错了。

19 个答案:

答案 0 :(得分:267)

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

当然,Mongo 不是关系数据库,并且开发人员正在小心推荐$ lookup的特定用例,但至少从3.2开始就可以使用MongoDB进行连接。

答案 1 :(得分:137)

官方mongodb网站上的这个页面正好 这个问题:

http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails/

  

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

     

关系纯粹主义者可能已经感到不安,好像我们违反了一些普遍的法律。但是请记住,MongoDB集合不等同于关系表;每个都有一个独特的设计目标。规范化表提供原子的,孤立的数据块。然而,文档更接近地代表整个对象。在社交新闻网站的情况下,可以认为用户名是发布的故事所固有的。

答案 2 :(得分:130)

我们可以使用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" : [ ]
}

我希望这句话可以帮到你。

答案 3 :(得分:38)

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

答案 4 :(得分:18)

以下是“加入” * 演员电影收藏品的示例:

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

它使用.mapReduce()方法

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

答案 5 :(得分:16)

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

答案 6 :(得分:10)

这取决于你想要做什么。

您目前已将其设置为规范化数据库,这很好,并且您采用的方式也是合适的。

但是,还有其他方法可以做到。

您可以拥有一个帖子集合,其中包含针对每个帖子的嵌入评论,并引用了您可以迭代查询的用户。您可以将用户的名称与注释一起存储,您可以将它们全部存储在一个文档中。

NoSQL的用途是它设计用于灵活的模式和非常快速的读写。在一个典型的大数据农场中,数据库是最大的瓶颈,你拥有的数据库引擎比应用程序和前端服务器要少......它们更昂贵但功能更强大,硬盘空间也比较便宜。规范化来自于试图节省空间的概念,但它带来了使数据库执行复杂连接和验证关系完整性,执行级联操作的成本。如果他们正确地设计数据库,所有这些都会使开发人员感到头疼。

使用NoSQL,如果您认为冗余和存储空间不是问题,因为它们的成本(执行更新所需的处理器时间和存储额外数据的硬盘驱动器成本),非正规化不是问题(对于嵌入式成为数十万个项目的数组可能是性能问题,但大部分时间都不是问题。此外,您将为每个数据库集群提供多个应用程序和前端服务器。让他们完成连接的繁重工作,让数据库服务器坚持读写。

TL; DR:你正在做的事情很好,还有其他方法可以做到。查看mongodb文档的数据模型模式以获取一些很好的示例。 http://docs.mongodb.org/manual/data-modeling/

答案 7 :(得分:10)

您可以使用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中的左右连接一样。

答案 8 :(得分:9)

有一个规范,许多驱动程序支持这个名为DBRef。

  

DBRef是一种更正式的规范,用于在文档之间创建引用。 DBRefs(通常)包括集合名称和对象ID。如果集合可以从一个文档更改为下一个文档,则大多数开发人员仅使用DBRef。如果您引用的集合始终相同,则上面列出的手动参考更有效。

取自MongoDB文档:数据模型&gt;数据模型参考&gt; Database References

答案 9 :(得分:9)

通过正确组合 $ lookup $ project $ match ,您可以在多个参数上加入多个表格。这是因为它们可以链接多次。

假设我们想要关注(reference

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

                        ])

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

答案 10 :(得分:6)

$ lookup(聚合)

对同一数据库中的未整数集合执行左外连接,以从“已连接”集合中过滤文档以进行处理。对于每个输入文档,$ lookup阶段添加一个新的数组字段,其元素是来自“已连接”集合的匹配文档。 $ lookup阶段将这些重新整形的文档传递给下一个阶段。 $ lookup阶段具有以下语法:

平等匹配

要在输入文档中的字段与“已连接”集合的文档中的字段之间执行相等匹配,$ 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>
     }
}

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

答案 11 :(得分:5)

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

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

答案 12 :(得分:3)

您可以运行SQL查询,包括使用Postgres中的mongo_fdw加入MongoDB。

答案 13 :(得分:2)

MongoDB不允许连接,但您可以使用插件来处理它。检查mongo-join插件。这是最好的,我已经使用过它。您可以像npm install mongo-join一样使用npm直接安装它。您可以查看full documentation with examples

(++)当我们需要加入(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;


            });

    });

答案 14 :(得分:1)

您可以使用聚合管道来完成它,但是自己编写它会很痛苦。

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

这是您的查询的样子:

-v

您的结果将在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)) ); 字段中包含用户对象,您可以根据需要链接多个级别。您可以填充对用户的引用,该引用引用了一个Team,它引用了其他内容等。

免责声明:我写了uid来解决这个问题。

答案 15 :(得分:0)

playORM可以使用S-SQL(可伸缩SQL)为您完成,它只是添加分区,以便您可以在分区中进行连接。

答案 16 :(得分:-3)

不,你似乎并没有做错。 MongoDB加入是&#34;客户端&#34;。就像你说的那样:

  

目前,我首先收到符合我标准的评论,然后找出该结果集中的所有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.

它不是真实的&#34;加入,但实际上它比SQL连接更有用,因为你不必处理重复的行,而且很多都是#34;很多&#34;双面连接,而不是你装饰最初选择的一套。

此页面上有很多废话和FUD。 5年后,MongoDB仍然是一件事。

答案 17 :(得分:-3)

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

但是我已经Git为MOngo做了解决方案 顺便说一下,在插入代码中 - 它有电影的名字,但是noi电影的ID

问题

你有一系列演员,他们已经完成了一系列电影。

您想要生成一组电影,每个电影都有一系列演员。

一些示例数据

 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 / reduce,将其输出到“pivot”集合并打印结果:

printjson(db.actors.mapReduce(map,reduce,“pivot”));   db.pivot.find()的forEach(printjson);

以下是样本输出,请注意“漂亮女人”和“失控新娘”同时拥有“Richard Gere”和“Julia Roberts”。

{ "_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" ] } }

答案 18 :(得分:-4)

我们可以使用mongoDB子查询合并两个集合。这是一个例子, Commentss -

`db.commentss.insert([
  { uid:12345, pid:444, comment:"blah" },
  { uid:12345, pid:888, comment:"asdf" },
  { uid:99999, pid:444, comment:"qwer" }])`

Userss -

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

JOIN的MongoDB子查询 -

`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"
        }
    ]
}`

希望这会有所帮助。