MongoDB同时查询多个集合

103
users
{
 "_id":"12345",
 "admin":1
},
{
 "_id":"123456789",
 "admin":0
}

posts
{
 "content":"Some content",
 "owner_id":"12345",
 "via":"facebook"
},
{
 "content":"Some other content",
 "owner_id":"123456789",
 "via":"facebook"
}

这是来自我的mongodb的示例。 我想获取所有"via"属性等于"facebook"并由管理员发布("admin": 1)的帖子。 我无法找出如何获取此查询。 由于mongodb不是关系型数据库,我无法执行联接操作。 有什么解决方案?


使用 $lookup 执行无关联子查询 - ChanOnly123
7个回答

77

您可以使用$lookup(多重)从多个集合中获取记录:

例如:

如果您有更多的集合(我在这里为演示而有3个集合, 您可以拥有超过3个), 我想要从3个集合中获取数据到单个对象中:

集合如下所示:

db.doc1.find().pretty();

{
    "_id" : ObjectId("5901a4c63541b7d5d3293766"),
    "firstName" : "shubham",
    "lastName" : "verma"
}

db.doc2.find().pretty();

{
    "_id" : ObjectId("5901a5f83541b7d5d3293768"),
    "userId" : ObjectId("5901a4c63541b7d5d3293766"),
    "address" : "Gurgaon",
    "mob" : "9876543211"
}

db.doc3.find().pretty();

{
    "_id" : ObjectId("5901b0f6d318b072ceea44fb"),
    "userId" : ObjectId("5901a4c63541b7d5d3293766"),
    "fbURLs" : "http://www.facebook.com",
    "twitterURLs" : "http://www.twitter.com"
}

现在您的查询将如下所示:

db.doc1.aggregate([
    { $match: { _id: ObjectId("5901a4c63541b7d5d3293766") } },
    {
        $lookup:
        {
            from: "doc2",
            localField: "_id",
            foreignField: "userId",
            as: "address"
        }
    },
    {
        $unwind: "$address"
    },
    {
        $project: {
            __v: 0,
            "address.__v": 0,
            "address._id": 0,
            "address.userId": 0,
            "address.mob": 0
        }
    },
    {
        $lookup:
        {
            from: "doc3",
            localField: "_id",
            foreignField: "userId",
            as: "social"
        }
    },
    {
        $unwind: "$social"
    },

  {   
    $project: {      
           __v: 0,      
           "social.__v": 0,      
           "social._id": 0,      
           "social.userId": 0
       }
 }

]).pretty();

那么您的结果将是:

{
    "_id" : ObjectId("5901a4c63541b7d5d3293766"),
    "firstName" : "shubham",
    "lastName" : "verma",

    "address" : {
        "address" : "Gurgaon"
    },
    "social" : {
        "fbURLs" : "http://www.facebook.com",
        "twitterURLs" : "http://www.twitter.com"
    }
}

如果您希望从每个集合获取所有记录,则应从查询中删除以下行:

{
            $project: {
                __v: 0,
                "address.__v": 0,
                "address._id": 0,
                "address.userId": 0,
                "address.mob": 0
            }
        }

{   
        $project: {      
               "social.__v": 0,      
               "social._id": 0,      
               "social.userId": 0
           }
     }

移除上述代码后,您将得到以下所有记录:

{
    "_id" : ObjectId("5901a4c63541b7d5d3293766"),
    "firstName" : "shubham",
    "lastName" : "verma",
    "address" : {
        "_id" : ObjectId("5901a5f83541b7d5d3293768"),
        "userId" : ObjectId("5901a4c63541b7d5d3293766"),
        "address" : "Gurgaon",
        "mob" : "9876543211"
    },
    "social" : {
        "_id" : ObjectId("5901b0f6d318b072ceea44fb"),
        "userId" : ObjectId("5901a4c63541b7d5d3293766"),
        "fbURLs" : "http://www.facebook.com",
        "twitterURLs" : "http://www.twitter.com"
    }
}

嗨,@Shubham Verma,如何在同一查询中从“social”或“address”集合中获取多个记录。就像我在社交集合中为单个用户有多个记录一样。 - Venkatesh Somu
1
这是一个非常好的答案。简单、清晰、明了! - M E S A B O
1
好的解释。 - undefined

60
尝试在MongoDB中使用JOIN会使MongoDB的使用变得无意义。但是,您可以使用DBref并编写应用程序级代码(或库),以使其自动获取这些引用。
或者,您可以更改模式并使用嵌入式文档
最后选择是保持现状并执行两个查询。

1
如何编写两个查询? - Tri Nguyen
1
在MongoJS中,如何使用普通的查询实现这个功能? - Pille
2
现在您可以使用 $lookup 进行连接。 - Corbfon
8
抱歉,但我必须说,“如果在使用数据库时没有联接(JOIN)的概念,那就失去了使用数据库的意义。”Mongo添加了$lookup功能,是因为错误地认为这不是一个问题。嵌入式文档不是最好的解决方案,因为它们与应该等同于的记录不同步,会导致数据重复。 - eggmatters
这里需要强调的一个重要点是,DBRefs是由客户端驱动程序实现的,这意味着需要多次获取数据。如上所述,使用$lookup可以避免多次网络请求。 - dev
显示剩余2条评论

32

这里是您问题的答案。

db.getCollection('users').aggregate([
    {$match : {admin : 1}},
    {$lookup: {from: "posts",localField: "_id",foreignField: "owner_id",as: "posts"}},
    {$project : {
            posts : { $filter : {input : "$posts"  , as : "post", cond : { $eq : ['$$post.via' , 'facebook'] } } },
            admin : 1

        }}

])

或者您可以选择使用MongoDB的分组选项。

db.getCollection('users').aggregate([
    {$match : {admin : 1}},
    {$lookup: {from: "posts",localField: "_id",foreignField: "owner_id",as: "posts"}},
    {$unwind : "$posts"},
    {$match : {"posts.via":"facebook"}},
    { $group : {
            _id : "$_id",
            posts : {$push : "$posts"}
    }}
])

这是做什么的?这是一个查询还是将元数据添加到集合中? - ViniciusPires
1
$lookup运算符自3.2版本以来是新的,类似于左外连接。请参阅https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/。 - almoraleslopez
你好,能否告诉我第一个查询中的filter是做什么用的? - Muhammad Usama Mashkoor

11

如前所述,在MongoDB中,您无法在集合之间进行JOIN操作。

对于您的示例,一种解决方案可能是:

var myCursor = db.users.find({admin:1});
var user_id = myCursor.hasNext() ? myCursor.next() : null;
db.posts.find({owner_id : user_id._id});

请参阅参考手册 - 游标部分:http://es.docs.mongodb.org/manual/core/cursors/

另一种解决方案是将用户嵌入到帖子集合中,但我认为对于大多数 Web 应用程序来说,出于安全原因,用户集合需要独立于其他集合。 用户集合可能具有角色、权限等。

posts
{
 "content":"Some content",
 "user":{"_id":"12345", "admin":1},
 "via":"facebook"
},
{
 "content":"Some other content",
 "user":{"_id":"123456789", "admin":0},
 "via":"facebook"
}

然后:

db.posts.find({user.admin: 1 });

4

执行多个查询或使用嵌入式文档或查看“数据库引用”。


3
一种解决方案:在您的帖子收集文档中添加isAdmin: 0/1标志。
另一种解决方案:使用DB引用。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接