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 中的聚合更新的主要内容,如果未能解决你的问题,请参考以下文章