从一组客户端考虑此文档:
client: {
services: [
{
_id: 111,
someField: 'someVal'
},
{
_id: 222,
someField: 'someVal'
}
... // More services
]
staff: [
{
_id: 'aaa',
someField: 'someVal',
servicesProvided: [111, 222, 333, ...]
},
{
_id: 'bbb',
someField: 'someVal',
servicesProvided: [111, 555, 666, ...]
},
{
_id: 'ccc',
someField: 'someVal',
servicesProvided: [111, 888, 999, ...]
}
... // More staff
]
}
客户可以有很多员工。每位员工都参考他或她提供的服务。如果删除服务,则还需要在所有员工中删除对此服务的引用。
我想从services
删除(拉取)对象(服务),并在同一查询中删除所有 {{1} servicesProvided
中的可能引用对象`
例如,如果我删除了staff
111的服务,我还想删除提供此服务的员工中对此服务的所有引用。
如何编写此查询。
答案 0 :(得分:1)
所以这就是事情变得有点令人讨厌的地方。你究竟如何更新与单个文档中的条件匹配的“多个”数组项?
这里的一些背景来自positional $
运营商documentation:
嵌套数组 位置$运算符不能用于遍历多个数组的查询,例如遍历嵌套在其他数组中的数组的查询,因为$ placeholder的替换是单个值
这说明了故事的“部分”,但这个问题的主要观点是“更多那个”。
因此,即使“嵌套”部分由于需要做什么而未明确true
,因此重要的因素是“不止一个”。为了演示,让我们考虑一下:
{
services: [
{
_id: 111,
someField: 'someVal'
},
{
_id: 222,
someField: 'someVal'
}
],
staff: [
{
_id: 'aaa',
someField: 'someVal',
servicesProvided: [111, 222, 333, ...]
},
{
_id: 'bbb',
someField: 'someVal',
servicesProvided: [111, 555, 666, ...]
},
{
_id: 'ccc',
someField: 'someVal',
servicesProvided: [111, 888, 999, ...]
}
]
}
现在要求删除111
值。这是始终您示例中提供的“第一个”值。所以,我们可以假设这样的情况,那么更新就是“看似简单:
db.collection.update(
{
"_id": ObjectId("542ea4991cf4ad425615b84f"),
},
{
"$pull": {
"services": { "_id": 111 },
"staff.servicesProvided": 111
}
}
)
但是。这不符合您的预期,因为元素不会像您预期的那样从所有“staff”数组元素中提取。事实上,没有一个。唯一可行的是:
db.collection.update(
{
"_id": ObjectId("542ea4991cf4ad425615b84f"),
"staff.servicesProvided": 111
},
{
"$pull": {
"services": { "_id": 111 },
"staff.$.servicesProvided": 111
}
}
)
但猜猜是什么!实际上只更新了“第一个”数组元素。因此,当你看上面的陈述时,这基本上就是它所说的。
然而,假设我们只是在一个带有MongoDB 2.6或更高版本服务器的现代MongoDB shell中测试它。那么这就是我们得到的回应:
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
所以请等一下。我们刚刚被告知最后一个声明“修改”了多少文件。因此,即使我们一次只能更改数组中的一个元素,这里也会有一些重要的反馈。
从“批量操作API”操作获得的新“WriteResult”对象真正的好处,实际上这是在shell中进行的,实际上是你被告知是否某些内容被前一个语句“修改”或不。比“遗留”写入响应更好的方式,现在为我们提供了一个基础,可以对循环考虑做出一些重要的决定。例如“我们的上一次操作实际上'修改'了一个文件,然后我们应该继续吗?”
所以这是一个重要的“流控制”点,即使一般的MongoDB API本身不能只是一次“更新所有元素”。现在有一个可测试的案例来决定在循环中“继续”的位置。这就是我最后通过“结合”你已经学到的东西来表达的意思。所以最终我们可以来这样的列表:
var bulk = db.collection.initializeOrderedBulkOp();
var modified = 1;
async.whilst(
function() { return modified },
function(callback) {
bulk.find(
{
"_id": ObjectId("542ea4991cf4ad425615b84f"),
"staff.servicesProvided": 111
}
).updateOne(
{
"$pull": {
"services": { "_id": 111 },
"staff.$.servicesProvided": 111
}
}
);
bulk.execute(function(err,result) {
modified = result.nModfified();
callback(err);
});
},
function(err) {
// did I throw something! Suppose I should so something about it!
}
);
或者基本上像这样可爱的东西。因此,您要求从“批量操作”.execute()
获取的“结果”对象告诉您是否修改了某些内容。它仍然存在,那么你在这里再次“重新迭代”循环并执行相同的更新并再次询问结果。
最终,更新操作会告诉您“没有”被修改。这是当你退出循环并继续正常操作时。
现在处理这个问题的备用方法可能是读取整个对象,然后进行所需的所有修改:
db.collection.findOne(
{
"_id": ObjectId("542ea4991cf4ad425615b84f"),
"staff.servicesProvided": 111
},
function(err,doc) {
doc.services = doc.services.filter(function(item) {
return item._id != 111;
});
doc.staff = doc.staff.filter(function(item) {
item.serviceProvided = item.servicesProvided.filter(function(sub) {
return sub != 111;
});
return item;
});
db.collection.save( doc );
}
);
有点矫枉过正。不完全是原子的,但足够接近测量。
所以你不能在一次写操作中真正做到这一点,至少在没有处理“读取”文档然后在修改内容之后“写”整个事情。但你可以采取“迭代”的方法,并且有一些工具可以让你控制它。
另一种可能的方法是改变你这样建模的方式:
{
"services": [
{
"_id": 111,
"someField": "someVal"
},
{
"_id": 222,
"someField": "someVal"
}
],
"provided": [
{ "_id": "aaa", "service": 111 },
{ "_id": "aaa", "service": 222 },
{ "_id": "aaa", "service": 111 }
]
}
等等。那么查询就变成这样:
db.collection.update(
{ "_id": ObjectId("542ea4991cf4ad425615b84f") },
{
"$pull": {
"services": { "_id": 111 },
"provided": { "_id": 111 }
}
}
);
这真的是一个单一的更新操作,可以一次性删除所有内容,因为每个元素都包含在单个数组中。
所以有办法做到这一点,但你的模型实际上取决于你的应用程序数据访问模式。选择最适合您的解决方案。这就是您首先选择MongoDB的原因。