MongoDB中的子查询

You*_*dri 4 join mongodb objectid

我在MongoDB中有两个集合,一个是用户,另一个是动作.用户看起来大致如下:

{_id: ObjectId("xxxxx"), country: "UK",...} 
Run Code Online (Sandbox Code Playgroud)

和行动一样

{_id: ObjectId("yyyyy"), createdAt: ISODate(), user: ObjectId("xxxxx"),...}
Run Code Online (Sandbox Code Playgroud)

我试图计算按国家/地区划分的事件和不同用户.上半部分工作正常,但是当我尝试添加一个子查询来拉动国家时,我只能获得国家/地区的空值

db.events.aggregate({
    $match: {
        createdAt: { $gte: ISODate("2013-01-01T00:00:00Z") },
        user: { $exists: true }
    }
},
{
    $group: {
        _id: {
            year: { $year: "$createdAt" },
            user_obj: "$user"
        },
        count: { $sum: 1 }
    }
},
{
    $group: {
        _id: {
            year: "$_id.year",
            country: db.users.findOne({ 
                _id: { $eq: "$_id.user_obj" },
                country: { $exists: true } 
            }).country
        },
        total: { $sum: "$count" },
        distinct: { $sum: 1 }
    }
})
Run Code Online (Sandbox Code Playgroud)

Bla*_*ven 8

没有加入这里,只有我们承担


所以MongoDB"不做连接".你可能在shell中尝试过类似的东西,例如:

db.events.find().forEach(function(event) {
    event.user = db.user.findOne({ "_id": eventUser });
    printjson(event)
})
Run Code Online (Sandbox Code Playgroud)

但这并不像你认为的那样做.它实际上完全按照它的样子运行,并对"用户"集合运行查询,从"事件"集合返回的每个项目,"往返于"客户端"并且不在服务器上运行.

出于同样的原因,聚合管道中的"嵌入"语句不能像那样工作.与上述不同,"整个管道"逻辑在执行之前被发送到服务器.所以如果你做了这样的事情来"选择"英国"用户:

db.events.aggregate([
    { "$match": {
        "user": { 
            "$in": db.users.distinct("_id",{ "country": "UK" })
        }
    }}
])
Run Code Online (Sandbox Code Playgroud)

然后,该.distinct()查询实际上在"客户端"而不是服务器上进行评估,因此不具有聚合管道中任何文档值的可用性.所以.distinct()首先运行,将它的数组作为参数返回,然后将整个管道发送到服务器.这是执行的顺序.

更正


对于要运行的查询类型,至少需要一定程度的反规范化.所以你通常有两个选择:

  1. 将整个用户对象数据嵌入事件数据中.

  2. 至少在事件数据中嵌入"一些"用户对象数据.在这种情况下,"国家"因为你将使用它.

那么如果你按照那里的"第二"案例,至少"扩展"你现有的数据,包括像这样的"国家":

{
    "_id": ObjectId("yyyyy"), 
    "createdAt": ISODate(), 
    "user": {
        "_id": ObjectId("xxxxx"),
        "country": "UK"
    }
}
Run Code Online (Sandbox Code Playgroud)

然后"聚合"过程变得简单:

db.events.aggregate([
    { "$match": {
        "createdAt": { "$gte": ISODate("2013-01-01T00:00:00Z") },
        "user": { "$exists": true }
    }},
    { "$group": {
        "_id": {
            "year": { "$year": "$createdAt" },
            "user_id": "$user._id"
            "country": "$user.country"
        },
        "count": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.country",
        "total": { "$sum": "$count" },
        "distinct": { "$sum": 1 }
    }}
])
Run Code Online (Sandbox Code Playgroud)

我们不正常


修复数据以在我们"不进行连接"的单个集合中包含所需的信息是一个相对简单的过程.只是上面原始查询示例的一个变种:

var bulk = db.events.intitializeUnorderedBulkOp(),
    count = 0;

db.users.find().forEach(function(user) {
    // update multiple events for user
    bulk.find({ "user": user._id }).update({
        "$set": { "user": { "_id": user._id, "country": user.country } }
    });
    count++;

    // Send batch every 1000 
    if ( count % 1000 == 0 ) {
        bulk.execute();
        bulk = db.events.intitializeUnorderedBulkOp();
    }
});

// Clear any queued
if ( count % 1000 != 0 )
    bulk.execute();
Run Code Online (Sandbox Code Playgroud)

这就是它的全部意义所在.对MongoDB服务器的单独查询可以使用"一个集合"和"仅一个集合".即使是如上所示的精彩"批量操作"仍然只能在一个集合上"批量".

如果您想执行"聚合相关属性"之类的操作,那么您"必须"在要聚合数据的集合中包含这些属性.将数据放在不同的集合中是完全可以的,例如"用户"通常会附加更多信息而不仅仅是"_id"和"国家".

但这里的重点是,如果您需要"国家"来"分析""用户"的"事件"数据,那么也将其包含在数据中.最有效的服务器连接是"预连接",这是一般的实践理论.