【问题标题】:MongoDB conditionally $addToSet sub-document in array by specific fieldMongoDB有条件地按特定字段在数组中$addToSet子文档
【发布时间】:2014-10-08 12:49:30
【问题描述】:

有没有办法根据数组上子文档中的特定键字段有条件地$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
            }
        ]
    }
]

我需要有效地将匹配napples 文档作为唯一标识符,并设置mState 是否已存在条目。很遗憾我不能在同一个文档中使用$pull$addToSet(我试过了)。

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

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

进一步

在我知道数组元素已经存在的情况下,我可以做到这一点;

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

当然只有这样才有效;

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

第一个限制不是灾难,但如果我不能有效地将$addToSet 与之前的$set 组合起来(当然我不能),那么这是我能想到的唯一解决方法现在意味着两次数据库往返。

【问题讨论】:

    标签: mongodb mongodb-query


    【解决方案1】:

    $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 以获得相同的功能但依赖于集合而不是数组,那么这几乎是您最好的方法。

    【讨论】:

    • 谢谢 - 我正在使用 C# 驱动程序,因此假设 InertBatch() 与我今天早上决定尝试的相同。事实证明,即使我可以进行有条件的更新,MongoDB 对字段排序的重要性无论如何都会抵消这些好处。首先(令人沮丧地)删除数组元素似乎是唯一合理的选择。
    • 哦等等 - 显然我累了,InsertBatch 根本不是一回事 :(
    • @cirrus BulkWriteRequestBuilder 是开始查找的地方,以及其他“批量”相关方法。
    • 谢谢 Neil - 顺便说一下,我的本地 mongo shell 中没有 initializeOrderedBulkOp() 作为函数 - 那是什么版本?
    • @cirrus Bulk 操作是在 MongoDB 2.6 中引入的,当然至少需要该版本的服务器才能支持它们。包含所有信息的链接在响应中。考虑升级有令人信服的理由,因为除了一些修复和改进之外,这些操作的返回结果要好得多。例如,现在可以判断$addToSet 操作是否实际上“添加”了一个新成员。对于遗留的写关注响应,这是不可能的。如果你不能升级,另一个系列会更适合你。
    【解决方案2】:

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

    我所做的是,使用 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 字段没有重复。

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

    【讨论】:

    • 如果存在更新数据如何?我错过了什么吗?
    【解决方案3】:

    据我所知,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】:

      作为一名业务分析师,我遇到了同样的问题,希望经过数小时的调查后我能找到解决方案。

      // 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”的子文档的索引。

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

      祝你有美好的一天!

      【讨论】:

      • 谢谢,这种方法可行。尽管如果可能的话,我试图在没有额外往返的情况下做到这一点。
      【解决方案5】:

      在我的场景中,数据不存在时需要初始化,如果存在则更新字段,数据不会被删除。如果数据有这些状态,不妨试试下面的方法。

      // 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,
                  }),
                },
              }
            );
          }
      

      这是场景中最干净、最快速的方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-23
        • 1970-01-01
        • 2013-01-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-16
        • 1970-01-01
        相关资源
        最近更新 更多