我需要在MongoDB集合文档中添加一个对象到数组中,插入它后,我需要确保所有数组的元素按其属性之一排序。
因为我需要数组中的对象是唯一的,所以我使用$addToSet
而不是$push
。这是我正在尝试的一个例子:
db.perros.update(
{name: "Risas"},
{
$addToSet: {propiedades: {name: "cola", cantidad: 1}},
$push: { propiedades: { $each: [ ], $sort: {cantidad: -1} }}
});
但是,这将失败,并出现以下Mongo错误:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 16837,
"errmsg" : "Cannot update 'propiedades' and 'propiedades' at the same time"
}
})
当你考虑集合时,这是很明显的,然而$push操作甚至不关心数组里面是什么…如果不使用$push
,如何实现这一点?
通过正确识别需要执行的操作,您已经成功了一部分。但是当然 $sort
不是 $addToSet
的有效修饰符,因为MongoDB的咒语是"集合不被认为是有序的":
$addToSet不保证修改集合中元素的特定顺序。
这个错误指出的另一个问题是,您不能同时在同一个属性的相同路径上使用多个更新操作符(例如$addToSet
和 $push
)。实际上,不同更新操作符的执行"没有顺序",因此不能保证$addToSet
在$push
之前发生。事实上,它们很可能是并行的,这就是为什么不允许出现错误的原因。
答案当然是"两个"更新语句。一个用于$addToSet
,另一个用于$sort
,通过"push"一个空数组,$each
,
但是因为我们真的不想"等待"每次更新完成,这就是"批量"操作API的作用。因此,您可以在中向服务器发送两个指令,一个发送并获得一个响应:
var bulk = db.perros.initializeOrderedBulkOp();
bulk.find({ "name": "Risas" }).update({
"$addToSet": {
"propiedades": { "name": "cola", "cantidad": 1 }
}
});
bulk.find({ "name": "Risas" }).update({
"$push": {
"propiedades": {
"$each": [ ], "$sort": { "cantidad": -1 }
}
}
});
bulk.execute();
所以这实际上仍然是对服务器的一个请求和一个响应。它仍然是"两个"操作,但是开销和某些线程捕获更新的临时状态的可能性是可以忽略不计的。
还有一种替代方法,即将"set检测"逻辑移动到更新语句的.find()
部分,然后只应用$push
,其中要添加到"set"的成员不存在:
var bulk = db.perros.initializeOrderedBulkOp();
bulk.find({
"name": "Risas",
"propiedades": {
"$not": { "$elemMatch": { "name": "cola", "cantidad": 1 } }
}
}).update({
"$push": {
"propiedades": {
"$each": [{ "name": "cola", "cantidad": 1 }], "$sort": { "cantidad": -1 }
}
}
});
bulk.execute();
当然,如果你在这里添加"多个"数组元素,你需要在 $and
条件中包装那些 $not
和 $elemMacth
测试,然后如果这些元素中"只有一个"是有效的,那么它就不能单独添加。
你可以"先"多"项"尝试"这种操作,但是你应该对每个单独的数组元素使用与上面相同的逻辑"回退"执行,以"测试"每个元素"推入"的可能性。
所以$addToSet
使得第二部分对于多个数组条目变得简单。对于一个条目,只需"查询"和$push
就很简单,对于多个条目,使用$addToSet
和$push
空数组的"第一"模式来"排序"结果可能是更短的路径,因为应用第二个模式意味着无论如何都要进行多次更新测试。