首页 文章

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

提问于
浏览
8

有没有办法根据数组子文档中的特定键字段有条件 $addToSet

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

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

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

{
    "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
            }
        ]
    }
]

我需要有效地在 n 上匹配 apples 文档作为唯一标识符,只需设置 mState ,无论条目是否已存在 . 它's a shame I can'在同一份文件中做了 $pull$addToSet (我试过) .

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

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

further

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

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

但当然这只有作用;

  • 表示单个数组元素

  • 只要我知道它存在

第一个限制是't a disaster, but if I can' t有效地将 $addToSet 与前一个 $set (当然我不能)结合起来然后它我现在能想到的唯一解决方法就是两次数据库往返 .

3 回答

  • 3

    $addToSet运算符当然要求"whole"文档"added to the set"实际上是唯一的,因此您无法更改文档的"part"或以其他方式将其视为"partial match" .

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

    所以你最接近的事情是发布单独的操作,但也使用MongoDB 2.6引入的"Bulk Operations API" . 这允许两者同时发送到服务器,以便最接近您将获得的"contiguous"操作列表:

    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以便在集合而不是数组上具有相同的功能,那么这几乎是您最好的方法 .

  • 5

    我遇到了完全相同的情况 . 我正在插入和删除帖子中的喜欢 .

    我做的是,使用mongoose findOneAndUpdate函数(类似于mongodb中的update或findAndModify函数) .

    关键概念是

    • Insert 当字段 is not 存在时

    • Delete 当字段 is 存在时

    插入是

    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 字段没有重复 .

    我希望这有助于此 . 如果您有任何疑问,请随时提出 .

  • 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" });
                    }
                }                                       
             }
        });
    

    祝你今天愉快 !

相关问题