有没有办法根据数组子文档中的特定键字段有条件地$addToSet
?
这是我的意思的一个例子 - 给出由以下样本引导程序产生的集合;
cls
db.so.remove();
db.so.insert({
"Name": "fruitBowl",
"pfms" : [
{
"n" : "apples"
}
]
});
n
定义了一个唯一的文档密钥。我只希望一次在数组中具有相同n
值的一个条目。所以我希望能够使用pfms
更新n
数组,以便我最终得到这个;
{
"Name": "fruitBowl",
"pfms" : [
{
"n" : "apples",
"mState": 1111234
}
]
}
这就是我现在所处的位置;
db.so.update({
"Name": "fruitBowl",
},{
// not allowed to do this of course
// "$pull": {
// "pfms": { n: "apples" },
// },
"$addToSet": {
"pfms": {
"$each": [
{
"n": "apples",
"mState": 1111234
}
]
}
}
}
)
不幸的是,这会添加另一个数组元素;
db.so.find().toArray();
[
{
"Name" : "fruitBowl",
"_id" : ObjectId("53ecfef5baca2b1079b0f97c"),
"pfms" : [
{
"n" : "apples"
},
{
"n" : "apples",
"mState" : 1111234
}
]
}
]
我需要有效地将apples
上匹配的n
文档作为唯一标识符,并设置mState
是否已存在条目。遗憾的是,我无法在同一份文档中执行$pull
和$addToSet
(我尝试过)。
我真正需要的是字典语义,但现在不是一个选项,也不是打破文档 - 任何人都可以提出另一种方式吗?
FWIW - 现有格式是语言/驱动程序序列化的结果,我没有完全选择它。
进一步
在我知道已经存在的数组元素的情况下,我已经进一步了解我可以做到这一点;
db.so.update({
"Name": "fruitBowl",
"pfms.n": "apples",
},{
$set: {
"pfms.$.mState": 1111234,
},
}
)
但当然这只有效;
第一个限制不是灾难,但如果我无法有效地将$addToSet
与之前的$set
进行升级或合并(当然我无法做到),那么我现在能想到的唯一解决方法就是两次数据库往返。
答案 0 :(得分:5)
$addToSet
运算符当然要求“添加到集合中”的“整个”文档实际上是唯一的,因此您不能更改文档的“部分”或以其他方式将其视为“部分”匹配”。
您偶然发现使用$pull
删除任何会导致“重复”的“密钥”字段的元素的最佳方法,但当然您无法在不同的更新运算符中修改相同的路径。
所以你最接近的事情是发布单独的操作,但也使用MongoDB 2.6引入的"Bulk Operations API"。这允许两者同时发送到服务器,以便最接近您将获得的“连续”操作列表:
var bulk = db.so.initializeOrderedBulkOp();
bulk.find({ "Name": "fruitBowl", "pfms.n": "apples": }).updateOne({
"$pull": { "pfms": { "n": "apples" } }
});
bulk.find({ "Name": "fruitBowl" }).updateOne({
"$push": { "pfms": { "n": "apples", "state": 1111234 } }
})
bulk.execute();
如果将元素移动到另一个集合并依赖于“upserts”和$set
以便在集合而不是数组上具有相同的功能,那么这几乎是你最好的方法
答案 1 :(得分:3)
我遇到了完全相同的情况。我在帖子中插入和删除了喜欢的内容。
我所做的是,使用mongoose findOneAndUpdate函数(类似于mongodb中的update或findAndModify函数)。
关键概念是
插入
findOneAndUpdate({ _id: theId, 'likes.userId': { $ne: theUserId }},
{ $push: { likes: { userId: theUserId, createdAt: new Date() }}},
{ 'new': true }, function(err, post) { // do the needful });
删除
findOneAndUpdate({ _id: theId, 'likes.userId': theUserId},
{ $pull: { likes: { userId: theUserId }}},
{ 'new': true }, function(err, post) { // do the needful });
这使整个操作成为原子,并且 userId 字段没有重复。
我希望这有助于此。如果您有任何疑问,请随时提出。
答案 2 :(得分:0)
作为商业分析师,我遇到了同样的问题,希望在经过数小时的调查后我能找到解决方案。
// The customer document:
{
"id" : "1212",
"customerCodes" : [
{
"code" : "I"
},
{
"code" : "YK"
}
]
}
//问题:我想将dateField“01.01.2016”插入客户文档,其中customerCodes子文档的文档包含代码“YK”但没有dateField。最终文件必须如下:
{
"id" : "1212",
"customerCodes" : [
{
"code" : "I"
},
{
"code" : "YK" ,
"dateField" : "01.01.2016"
}
]
}
//解决方案:解决方案代码分为三个步骤:
//第1部分 - 使用customerCodes“YK”查找客户,但没有dateField
// PART 2 - 在customerCodes列表中找到带有“YK”的子文档的索引。
//第3部分 - 将值插入文档
//这是代码
// PART 1
var myCursor = db.customers.find({ customerCodes:{$elemMatch:{code:"YK", dateField:{ $exists:false} }}});
// PART 2
myCursor.forEach(function(customer){
if(customer.customerCodes != null )
{
var size = customer.customerCodes.length;
if( size > 0 )
{
var iFoundTheIndexOfSubDocument= -1;
var index = 0;
customer.customerCodes.forEach( function(clazz)
{
if( clazz.code == "YK" && clazz.changeDate == null )
{
iFoundTheIndexOfSubDocument = index;
}
index++;
})
// PART 3
// What happens here is : If i found the indice of the
// "YK" subdocument, I create "updates" document which
// corresponds to the new data to be inserted`
//
if( iFoundTheIndexOfSubDocument != -1 )
{
var toSet = "customerCodes."+ iFoundTheIndexOfSubDocument +".dateField";
var updates = {};
updates[toSet] = "01.01.2016";
db.customers.update({ "id" : customer.id } , { $set: updates });
// This statement is actually interpreted like this :
// db.customers.update({ "id" : "1212" } ,{ $set: customerCodes.0.dateField : "01.01.2016" });
}
}
}
});
祝你有愉快的一天!
答案 3 :(得分:0)
据我所知,MongoDB现在(来自v 4.2
)允许使用aggregation pipelines for updates。
使它正常工作的优雅方式如下(根据问题):
db.runCommand({
update: "your-collection-name",
updates: [
{
q: {},
u: {
$set: {
"pfms.$[elem]": {
"n":"apples",
"mState": NumberInt(1111234)
}
}
},
arrayFilters: [
{
"elem.n": {
$eq: "apples"
}
}
],
multi: true
}
]
})
答案 4 :(得分:0)
在我的场景中,数据不存在时需要初始化,如果存在则更新字段,数据不会被删除。如果数据有这些状态,您可能想尝试以下方法。
// Mongoose, but mostly same as mongodb
// Update the tag to user, If there existed one.
const user = await UserModel.findOneAndUpdate(
{
user: userId,
'tags.name': tag_name,
},
{
$set: {
'tags.$.description': tag_description,
},
}
)
.lean()
.exec();
// Add a default tag to user
if (user == null) {
await UserModel.findOneAndUpdate(
{
user: userId,
},
{
$push: {
tags: new Tag({
name: tag_name,
description: tag_description,
}),
},
}
);
}
这是场景中最干净、最快速的方法。