MongoDB有条件地按特定字段在数组中添加$ addToSet子文档

cir*_*rus 9 mongodb mongodb-query

有没有办法有条件地$addToSet基于数组子文档中的特定键字段?

这是我的意思的一个例子 - 给出由以下示例引导程序生成的集合;

cls
db.so.remove();
db.so.insert({
    "Name": "fruitBowl",
    "pfms" : [
        {
            "n" : "apples"
        }
    ]
});
Run Code Online (Sandbox Code Playgroud)

n定义唯一的文档密钥.我只希望一次n在数组中有一个具有相同值的条目.所以我希望能够使用更新pfms数组,n以便我最终得到这个;

{
    "Name": "fruitBowl",
    "pfms" : [
        {
            "n" : "apples",
            "mState": 1111234
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

这就是我现在所处的位置;

db.so.update({
  "Name": "fruitBowl",
},{
// not allowed to do this of course
//    "$pull": {
//  "pfms": { n: "apples" },
//    },

"$addToSet": {
  "pfms": {
    "$each": [
      {
        "n": "apples",
        "mState": 1111234
      }
    ]
   }
  }
 }
)
Run Code Online (Sandbox Code Playgroud)

不幸的是,这增加了另一个数组元素

db.so.find().toArray();

[
    {
        "Name" : "fruitBowl",
        "_id" : ObjectId("53ecfef5baca2b1079b0f97c"),
        "pfms" : [
            {
                "n" : "apples"
            },
            {
                "n" : "apples",
                "mState" : 1111234
            }
        ]
    }
]
Run Code Online (Sandbox Code Playgroud)

我需要有效地将apples匹配的文档n作为唯一标识符进行插入,并且只需设置mState条目是否已存在.这是一个耻辱,我不能做$pull,并$addToSet在同一文档中(我想).

我真正需要的是字典语义,但现在不是一个选项,也不是打破文档 - 任何人都可以提出另一种方式吗?

FWIW - 现有格式是语言/驱动程序序列化的结果,我没有完全选择它.

进一步

在我知道数组元素已经存在的情况下我已经进一步了解我能做到这一点;

db.so.update({
    "Name": "fruitBowl",
    "pfms.n": "apples",
},{
  $set: {
   "pfms.$.mState": 1111234,
  },
 }
)
Run Code Online (Sandbox Code Playgroud)

但当然这只有作用;

  1. 对于单个数组元素
  2. 只要我知道它存在

第一个限制不是灾难,但如果我不能有效地升级或$addToSet与前一个$set(当然我不能)结合,那么我现在能想到的唯一解决方法就是两次数据库往返.

Nei*_*unn 5

该$addToSet过程的操作要求"整体"的文件被"添加到组"实际上是独一无二的,所以你不能改变文档的"一部分"或以其他方式认为这是一个"部分匹配".

您偶然发现了使用$pull"key"字段删除任何元素的最佳方法,这会导致"重复",但当然您无法在不同的更新运算符中修改相同的路径.

因此,最接近的事情是发布单独的操作,但也使用MongoDB 2.6引入的"批量操作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();
Run Code Online (Sandbox Code Playgroud)

如果将元素移动到另一个集合并依赖于"upserts"并且$set为了具有相同的功能但是在集合而不是数组上,这几乎是你最好的方法.


Suh*_*rty 5

我也遇到过完全相同的情况。我在帖子中插入和删除点赞。

我所做的是,使用 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 }); 
Run Code Online (Sandbox Code Playgroud)

删除的是

findOneAndUpdate({ _id: theId, 'likes.userId': theUserId},
{ $pull: { likes: { userId: theUserId }}},
{ 'new': true }, function(err, post) { // do the needful }); 
Run Code Online (Sandbox Code Playgroud)

这使得整个操作原子化,并且userId字段没有重复。

我希望这会有所帮助。如果您有任何疑问,请随时询问。