Mongodb 中的聚合更新

Posted

技术标签:

【中文标题】Mongodb 中的聚合更新【英文标题】:Update on Aggregate in Mongodb 【发布时间】:2015-03-13 16:15:05 【问题描述】:

我正在尝试在一个对象(它是一个子文档)内切换布尔值,我很难更新数组中的特定对象。

文档:

"_id" : ObjectId("54afaabd88694dc019d3b628")
"Invitation" : [ 
    
        "__v" : 0,
        "ID" : ObjectId("54af6ce091324fd00f97a15f"),
        "__t" : "USER",
        "_id" : ObjectId("54b4ceb50fc380001bea1752"),
        "Accepted" : false
    , 
    
        "__v" : 0,
        "ID" : ObjectId("54afac5412f5fdcc007a5c4d"),
        "__t" : "USER",
        "_id" : ObjectId("54b4cebe0fc380001bea1753"),
        "Accepted" : false
    
],

控制器:

User.aggregate([$match: _id: ObjectId(54afaabd88694dc019d3b628),$unwind: '$Invitation',$project: _id: '$_id',Invitation: '$Invitation'],function(err,results)

    function updateInvitation(_id)

                var query = '_id': _id, 'Invitation.ID': ObjectId("54af6ce091324fd00f97a15f");
                var operator = $inc: 'Invitation.Accepted': 1; 
                User.update(query,operator,multi:true,function(err,updated)
                    if(err)
                        console.log(err);
                    
                    console.log('updating'+updated);
                );
            
            res.jsonp(results);
            updateInvitation(results[0]._id);
);

我尝试使用 $set 但没有成功,因为整个 Invitation 数组被替换为 'Accepted = 1' 如何使用特定的“ID”切换文档的“Accepted”字段。

Invitation.$.Accepted

位置运算符不适用于包含数组的字段,因此无法迭代到 Accepted 字段

编辑:

User.find(_id: req.user._id,'Invitation',function(err,docs)
    if(err)
        console.log(err);
    
    console.log(docs);
    var results = [];
    async.each(docs,function(doc,err) 
        if(err)
            console.log('error'+ err);
        
        async.each(docs.Invitation,function(invite,callback) 
console.log('second async');
            User.update(
                 '_id': doc._id, 'Invitation._id': invite._id ,
                 '$set': 'Invitation.$.Accepted': !invite.Accepted,
                function(err,doc) 
                    results.push(doc);
                    console.log('updated'+doc);
                    callback(err);
                
            );
        );
    ,function(err) 
        if (err)
            console.log(err);
        console.log(results);
    );

);

控件没有进入第二个 async.each,在第一个 async 上抛出错误,这是错误:

error-function () 
        if (called) throw new Error("Callback was already called.");
        called = true;
        fn.apply(root, arguments);
    

【问题讨论】:

【参考方案1】:

我真的不认为即使作为馈线查询,聚合框架也不是在这里使用的正确操作。您所做的就是将数组“非规范化”为单个文档。真的应该没有必要。只需获取文档即可:

var query = ; // whatever criteria

Users.find(query,"Invitation",function(err,docs) 
    if (err) 
        console.log(err);

    var results = [];        

    async.each(docs,function(doc,callback) 
        async.each(docs.Invitation,function(invite,callback) 
            Users.findOneAndUpdate(
                 "_id": doc._id, "Invitation._id": invite._id ,
                 "$set":  "Invitation.$.Accepted": !invite.Accepted  ,
                function(err,doc) 
                   results.push( doc );
                   callback(err);
                
            );
        ,callback);
    ,function(err) 
        if (err)
            console.log(err);

        console.log(results);
    );    

);

因此,在响应您正在做的事情时迭代文档列表没有问题,只是您还想迭代数组成员。问题是,当您发出任何需要注意的.update() 时,异步调用就完成了。

所以我使用async.each,但您可能希望async.eachLimit 控制循环。元素的匹配来自positional $运算符,对应查询中匹配的数组元素。

这只是 javascript 代码,所以只需用!invite.accepted“切换”该值,这将反转它。为了获得更多乐趣,请通过从 .findOneAndUpdate() 推送修改后的文档来返回“结果”数组。

【讨论】:

感谢您的回答,但我在 async.each github.com/caolan/async/issues/610 上收到此错误 @shaileshshekhawat 那个问题是关于一个未定义的数组对象?那么这里未定义哪个数组对象呢? .find() 的结果应该是返回文档或者查询错误。如果内部“邀请”数组未定义,那么您可以在提交到 async.each 之前测试该值。 @NeilLunn-query on .find() 返回已定义的邀请数组我在console.log(docs) 上对其进行了测试,但仍然出现这个奇怪的错误 @shaileshshekhawat 听起来您正在做与清单中的代码不同的事情。尝试首先使用给定的列表,看看还有什么地方可能出错。 @NeilLunn-你能解释一下使用两个 async.each 的逻辑吗?【参考方案2】:

为此使用位置更新运算符: http://docs.mongodb.org/manual/reference/operator/update/positional/

User.update(
    
        "_id": ObjectId("54afaabd88694dc019d3b628"),
        "Invitation": "$elemMatch": "ID" : ObjectId("54af6ce091324fd00f97a15f"), "Accepted":false
    ,
    
        "$set" : 
            "Invitation.$.Accepted" : true
        
    ,multi:false, upsert:false, safe:true, function (err, numAffectedDocuments)
      // TODO
    
);

注意:您不需要聚合步骤,因为它实际上什么都不做。

【讨论】:

我已经尝试使用位置运算符,但收到此错误“无法应用包含数组的位置运算符字段” @shaileshshekhawat 发生这种情况是因为要更新的字段也需要在查询中。我编辑了答案中的代码来纠正这个问题。

以上是关于Mongodb 中的聚合更新的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB 更新和聚合问题:跳过聚合

MongoDB聚合操作总结

MongoDB聚合操作总结

04 MongoDB各种查询操作 以及聚合操作总结

MongoDB 中的计算

python3操作MongoDB的crud以及聚合案例,代码可直接运行(python经典编程案例)